Using left join to get total rows in other table - php

Hi I all I have two tables, yalladb_hotel and yalladb_room_types and their structure are
yalladb_hotel
-----------------------------------------
| id | name | address | fax | telephone |
---------------------------------------------
And yalladb_room_types
-----------------------------
|id | hotel_id | roomtype_name | rate |
Now I want to get all information from hotel table and want to get total number of room types related to hotel table. I used left join as it is not necessary that all hotels have room types. So I used following query
SELECT
h.*,
count(rm.*) as total_room_types
FROM
yalladb_hotel h
LEFT JOIN yalladb_room_types rm
ON h.id=rm.hotel_id
LIMIT
0,5
But it is producing following error and I am totally unable to understand the error is....
#1064 - You have an error in your SQL syntax;
check the manual that corresponds to your MySQL server version
for the right syntax to use near '*) as total_room_types FROM
yalladb_hotel h LEFT JOIN yalladb_room_types rm ON h' at line 1
Can any one tell what is there?
Regards

If you are using agregate function you should put every columns(except agregate column) to GROUP BY section
http://dev.mysql.com/doc/refman/5.0/en/group-by-functions.html
Explanation:
Select (these columns should be in group by section), count(agr_col)
from table
group by (here should be those columns also)

Just do
COUNT(rm.id) as Total_Room_Types
Unless you have a specific classification to differentiate between, Double, Queen, King size bed rooms.
If your Hotel room "Name" is the classification of room type as described above, you should pre-query the room types first and join to that.
SELECT
h.*,
COALESCE( PreQuery.Name, " " ) as RoomType,
COALESCE( PreQuery.RoomTypeCount, 0 ) as RoomTypeCount
FROM
yalladb_hotel h
LEFT JOIN ( select rm.hotel_id,
rm.name,
count(*) as RoomTypeCount
from
yalladb_room_types rm
group by
rm.hotel_id,
rm.name ) PreQuery
ON h.id=PreQuery.hotel_id
LIMIT
0,5
EDIT CLARIFICATIONS...
To clarify my answer. Instead of just a count of how many rooms, you wanted them per room type. Per your original listed structure, you had "Name" as a column which is now listed as roomType_Name per your edits. I suspected this column to describe the type of room. So my inner query (as opposed to an inner join) tells the query to pre-aggregate this stuff first, grouping by the criteria and let its results be known as an alias of "PreQuery" for the join condition. THEN, back to the main hotel table LEFT joined to "PreQuery" on the hotel ID.
Since a left join will otherwise result in NULL values if no such matches are found in whatever the "OTHER" table is, COALESCE() says... Get the value from parameter 1. If that is null, get the second value... and put that into a final query column called ... RoomType or RoomTypeCount as in this example. So your final query will not have any "NULL" as part of the result, but at least of proper data type expected (char and numeric respectively).

Related

MySQL join query duplicates users in output

I have the following tables
ea_users
id
first_name
last_name
email
password
id_roles
ea_user_cfields
id
c_id = custom field ID
u_id = user ID
data
ea_customfields
id
name = name of custom field
description
I want to get all users which have a certain role, but I also want to retrieve all the custom fields per user. This is for the backend of my software where all the ea_users and custom fields should be shown.
I tried the following, but for each custom field, it duplicates the same user
$this->db->join('(SELECT GROUP_CONCAT(data) AS custom_data, id AS dataid, u_id, c_id
FROM ea_user_cfields userc
GROUP BY id) AS tt', 'tt.u_id = ea.id','left');
$this->db->join('(SELECT GROUP_CONCAT(name) AS custom_name, id AS customid
FROM ea_customfields AS cf
GROUP BY id) AS te', 'tt.c_id = te.customid','left');
$this->db->where('id_roles', $customers_role_id);
return $this->db->get('ea_users ea')->result_array();
the problem that u did not understand properly how join works.
its ok, that u have duplicates in select when u have relation one to many.
in few words your case: engine tries to fetch data from table "A" (ea_users) then JOIN according to the conditions another table "B" (ea_customfields). If u have one to many relation between tables (it means that one record from table "A" (lets say that we have in this table A1 record) can contain few related rows in table "B", lets call them as B1.1, B1.2 and B1.3 and B1.4), in this case it will join this records and put join result in memory. So in memory u would see something like
| FromTable A | FromTableB |
| A1 | B1.1 |
| A1 | B1.2 |
| A1 | B1.3 |
| A1 | B1.4 |
if u have 10 records in table "B", which related to the table "A" it would put 10 times in memory copy of data from table "A" during fetching. And then will render it to u.
depending on join type rows, with missing related records, can be skipped at all (INNER JOIN), or can be filled up with NULLs (LEFT JOIN or RIGHT JOIN), etc.
When u think about JOINs, try to imagine yourself, when u try to join on the paper few big tables. U would always need to mark somehow which data come from which table in order to be able to operate with it later, so its quite logically to write row "A1" from table "A" as many times as u need to fill up empty spaces when u find appropriate record in table "B". Otherwise u would have on your paper something like:
| FromTable A | FromTableB |
| A1 | B1.1 |
| | B1.2 |
| | B1.3 |
| | B1.4 |
Yes, its looks ok even when column "FromTable A" contains empty data, when u have 5-10 records and u can easily operate with it (for example u can sort it in your head - u just need to imagine what should be instead of empty space, but for it, u need to remember all the time order how did u wrote the data on the paper). But lets assume that u have 100-1000 records. if u still can sort it easily, lets make things more complicated and tell, that values in table "A" can be empty, etc, etc.. Thats why for mysql engine simpler to repeat many times data from table..
Basically, I always stick to examples when u try to imagine how would u join huge tables on paper or will try to select something from this tables and then make sorting there or something, how would u look through the tables, etc.
GROUP_CONCAT, grouping
Then, next mistake, u did not understand how GROUP_CONCAT works:
The thing is that mysqlEngine fetch on the first step structure into memory using all where conditions, evaluating subqueries + appends all joins. When structure is loaded, it tried to perform GROUPing. It means that it will select from temporary table all rows related to the "A1". Then will try to apply aggregation function to selected data. GROUP_CONCAT function means that we want to apply concatenation on selected group, thus we would see something like "B1.1, B1.2, B1.3, B1.4". Its in few words, but I hope it will help a little to understand it.
I googled table structure so u can write some queries there.
http://www.mysqltutorial.org/tryit/query/mysql-left-join/#1
and here is example how GROUP_CONCAT works, try to execute there query:
SELECT
c.customerNumber, c.customerName, GROUP_CONCAT(orderNumber) AS allOrders
FROM customers c
LEFT JOIN orders o ON (c.customerNumber = o.customerNumber)
GROUP BY 1,2
;
can compare with results with previous one.
power of GROUP in aggregation functions which u can use with it. For example, u can use "COUNT()", "MAX()", "GROUP_CONCAT()" or many many others.
or example of fetching of count (try to execute it):
SELECT c.customerName, count(*) AS ordersCount
FROM customers AS c
LEFT JOIN orders AS o ON (c.customerNumber = o.customerNumber)
GROUP BY 1
;
so my opinion:
simpler and better to solve this issue on client side or on backend, after fetching. because in term of mysql engine response with duplication in column is absolutely correct. BUT of course, u can also solve it using grouping with concatenations for example. but I have a feeling that for your task its overcomplicating of logic
PS.
"GROUP BY 1" - means that I want to group using column 1, so after selecting data into memory mySql will try to group all data using first column, better not to use this format of writing on prod. Its the same as "GROUP BY c.customerNumber".
PPS. Also I read comments like "use DISTINCT", etc.
To use DISTINCT or order functions, u need to understand how does it work, because of incorrect usage it can remove some data from your selection, (same as GROUP or INNER JOINS, etc). On the first look, you code might work fine, but it can cause bugs in logic, which is the most complicated to find out later.
Moreover DISTINCT will not help u, when u have one-to-many relation(in your particular case). U can try to execute queries:
SELECT
c.customerName, orderNumber AS nr
FROM customers c
INNER JOIN orders o ON (c.customerNumber = o.customerNumber)
WHERE c.customerName='Alpha Cognac'
;
SELECT
DISTINCT(c.customerName), orderNumber AS nr
FROM customers c
INNER JOIN orders o ON (c.customerNumber = o.customerNumber)
WHERE c.customerName='Alpha Cognac'
;
the result should be the same. Duplication in customer name column and orders numbers.
and example how to loose data with incorrect query ;):
SELECT
c.customerName, orderNumber AS nr
FROM customers c
INNER JOIN orders o ON (c.customerNumber = o.customerNumber)
WHERE c.customerName='Alpha Cognac'
GROUP BY 1
;

Why MySQL's LEFT JOIN is returning "NULL" records when with WHERE clause?

Today I've tried some more complex MySQL queries and I've noticed that MySQL's LEFT JOIN is not working with WHERE clause. I mean, it does return some records but it does not return the ones which are empty on the right side.
For example let's say we've got to tables:
albums ; albums_rap
id artist title tracks ; id artist title rank
---- -------- ---------- --------------- ; ---- --------- ----- --------------
1 John Doe Mix CD 20 ; 3 Mark CD #7 15
2 Mark CD #7 35 ;
And when I run this query:
SELECT
t1.artist as artist,
t1.title as title,
t1.tracks as tracks,
t2.rank as rank,
FROM
albums as t1
LEFT JOIN
albums_rap as t2
ON
t1.artist LIKE t2.artist
AND
t1.title LIKE t2.title
WHERE
t2.rank != 17
I get this:
artist title tracks rank
------ ----- ------ -----
Mark CD #7 35 15
but when I replace "WHERE" with "AND" in this query I get:
artist title tracks rank
------ --------- ------ -----
Mark CD #7 35 15
John Doe Mix CD 20 NULL
Why the first one is not returning records with "NULL" (null is not equal to 17...)
I hope You understood what I meant and you'll explain somehow me the difference. Sorry for my bad english, it's not my mother tongue.
A left join condition and where condition filter are not both same. Data is filtered by the where clause after the physical join is done. if you look a left join it will normally return every row from your left table, but once you have a where clause, it will filter the output of the join so the result is like an inner join. You will want to focus on the two diagrams on the left side of the image below.
The solution to this question becomes quite intuitive once one is aware of the execution order or Logical Query Processing Phases of the SQL statement. The order is: -
1. FROM
2. ON
3. OUTER
4. WHERE
5. GROUP BY
6. CUBE | ROLLUP
7. HAVING
8. SELECT
9. DISTINCT
10. ORDER BY
11. TOP
As ON is performed before the OUTER(LEFT/RIGHT) part(adding NULL valued rows) of the JOIN, the 1st case has rows with NULL values in rank column.
In the 2nd case the rows get filtered out based on the values of the rank column after the OUTER JOIN(LEFT JOIN here) is performed. Hence, the rows with NULL values in the rank column are filtered out.
One very important thing that can be noticed in your SQL query is the comparison with NULL
This area requires special attention as the NULL values when with NULL/NON-NULL values using the normal arithmetic operators result NULL(it is neither TRUE nor FALSE) because NULL means no value is available for comparison. This behavior is defined in the ANSI SQL-92 standard.(This can be overridden by turning ansi null(actual name may vary) parameter off in some SQL processors)
So, your SQL query with where clause filters out rows with NULL value in rank column which might seem counter-intuitive due to "17 != NULL" seems TRUE
ex-
NULL = NULL results NULL/UNKNOWN
17 = NULL results NULL/UNKNOWN
17 != NULL results NULL?UNKNOWN
Some interesting posts/blogs for reference are:
http://blog.sqlauthority.com/2009/04/06/sql-server-logical-query-processing-phases-order-of-statement-execution/
http://blog.sqlauthority.com/2009/03/15/sql-server-interesting-observation-of-on-clause-on-left-join-how-on-clause-effects-resultset-in-left-join/
http://www.xaprb.com/blog/2006/05/18/why-null-never-compares-false-to-anything-in-sql/
First Case :
After joining, the records are getting filtered by the (WHERE clause). So only 1 result.
Second Case (when you replace WHERE with AND)
Will return all join entries + entries satisfied in second condition (t2.rank != 17). That is why here you get 2 records ( one from join + another from AND clause)
"The LEFT JOIN keyword returns all rows from the left table (table1), with the matching rows in the right table (table2). The result is NULL in the right side when there is no match."
The above quote is from w3schools.com
However, when you place a WHERE clause on a LEFT JOIN, SQL now treats that as an INNER JOIN and:
"The INNER JOIN keyword selects all rows from both tables as long as there is a match between the columns in both tables."
Also quoted from w3schools.com
TLDR;
The introduction of the WHERE clause to a LEFT OUTER JOIN gives the join the behavior of an INNER JOIN

MYSQL find rows where foreign key are not in related table

I have reservations that can be made in advance. On the day of the reservation, the devices reserved can be checked out.
I have two tables, reservations and checkouts. Reservations have many checkouts.
How would I construct a query that would return all reservations for a particular date that do NOT have associated checkout records?
To put it another way, all rows from reservations where reservation_id column does not contain the reservation's ID?
So far, my best guess is
SELECT * FROM reservations WHERE reservations.id NOT IN (SELECT reservation_id
FROM checkouts)
But that returns empty. Here's a rough idea what the tables look like
|reservations| |checkouts |
|id = 1 | |reservation_id = 1|
|id = 2 | |reservation_id = 2|
|id = 3 |
My result should be reservation 3.
P.S. If php is required, that's fine.
Most likely explanation for the query returning no rows is that there are rows in checkouts that have a NULL value for reservation_id. Consider:
4 NOT IN (2,3,5,NULL,11)
In interpreting this, the NULL value is seen as meaning "unknown what the value is". Is 4 in that list? The answer (coming back from SQL) is basically "unknown" whether 4 matches the "unknown" value in the list.
If that's what's causing the behavior, you can "fix" your current query by including WHERE reservation IS NOT NULL in the subquery.
SELECT r.*
FROM reservations r
WHERE r.id NOT IN ( SELECT c.reservation_id
FROM checkouts c
WHERE c.reservation_id IS NOT NULL
)
This may not be the most efficient approach to returning the specified result. An anti-join is a common pattern for returning this type of result. In your case, that would be an outer join, to return all rows from reservations, along with matching rows from checkouts, and then a predicate in the WHERE clause to filter out all the rows that had a match, leaving us with rows from reservations that didn't have a match.
For example:
SELECT r.*
FROM reservations r
LEFT
JOIN checkouts c
ON c.reservation_id = r.reservation_id
WHERE c.reservation_id IS NULL
It's also possible to get an equivalent result with a NOT EXISTS with a correlated subquery.
SELECT r.*
FROM reservations r
WHERE NOT EXISTS ( SELECT 1
FROM checkouts c
WHERE c.reservation_id = r.reservation_id
)

MySQL join multiple rows of query into one column

Table structure
client_commands (the "main" table):
id | completed
command_countries:
id | command_id | country_code
command_os:
id | command_id |OS
command_id on references the id column on client_commands.
Problem
I can add client commands with filters based on countries and operating systems. To try and normalise my DB structure, for each new command added:
Add a new row to client_commands
For each country, I add a new row to command_countries, each referencing client_command.id
For each OS, I add a new row to command_os, each referencing client_command.id
For one of the pages on my site, I need to display all client_commands (where completed = 0) as well as all the countries and operating systems for that command. My desired output would be something like:
id | countries | OS
1 | GB, US, FR| 2, 3
2 | ES, HU | 1, 3
I'm not sure how to go about doing this. The current query I'm using returns multiple rows:
SELECT a.id, b.country_code, c.OS
FROM client_commands a
LEFT JOIN command_countries b on b.command_id = a.id
LEFT JOIN command_os c on c.command_id = a.id
WHERE a.completed = 0
Any help?
Thanks!
EDIT: I forgot to mention (if you couldn't infer from above) - there can be a different number of operating systems and countries per command.
--
Also: I know I could do this by pulling all the commands, then looping through and running 2 additional queries for each result. But if I can, I'd like to do it as efficiently as possible with one query.
You can do this in one query by using GROUP_CONCAT
SELECT a.id,
GROUP_CONCAT(DISTINCT b.country_code SEPARATOR ' ,') `countries`,
GROUP_CONCAT(DISTINCT c.OS SEPARATOR ' ,') `os`,
FROM client_commands a
LEFT JOIN command_countries b on b.command_id = a.id
LEFT JOIN command_os c on c.command_id = a.id
WHERE a.completed = 0
GROUP BY a.id
if you want the ordered results in in a row you can use ORDER BY in GROUP_CONCAT like
GROUP_CONCAT(b.country_code ORDER BY b.command_id DESC SEPARATOR ' ,') `countries`
But be aware of that fact it has a limit of 1024 character to concat set by default but this can be increased b,steps provided in manual

need a help with joining multiple tables from SQL in PHP

Let's say I have 3 tables:
Table 1 called "states":
id | state
1 italy
2 netherlands
3 russia
Table 2 called "hotels":
id | hotel name | belongsToCountry
1 Green Hotel 2
2 Luxurious 2
3 Get Warm! 3
Table 3 called "free rooms":
id | roomID | belongsToHotel
1 815 2
2 912 2
3 145 1
4 512 1
5 1200 3
Now, what I need to echo is this:
Netherlands has 4 free rooms.
Russia has 1 free room.
In words:
I need to make a list of all states which have at least 1 free room and I need to return the exact value of how many free rooms there are.
If anyone can help me with this, I'd be so grateful!
Let's build the query step by step.
First, let's assemble the list of hotels and their free room count.
SELECT hotels.id, COUNT(*)
FROM hotels
INNER JOIN free_rooms ON(hotels.id = free_rooms.belongsToHotel)
GROUP BY hotels.id
INNER JOINs force rows from the table on the "left" side of the join (hotels) only to be included in the result set when there is a corresponding table on the "right" (free_rooms). I'm assuming here that there will only be a row in free_rooms when the room is free.
Having this, we can now join against the list-o-nations.
SELECT hotels.id, COUNT(*), states.state
FROM hotels
INNER JOIN free_rooms ON(hotels.id = free_rooms.belongsToHotel)
INNER JOIN states ON(hotels.belongsToCountry = states.id)
GROUP BY hotels.id
It should be noted, by the way, that you've made poor choices in naming these columns. states should be composed of id and state_name, hotels should be id, hotel_name, state_id, and free_rooms should be id, room_name and hotel_id. (I could also argue that states.id should be states.state_id, hotels.id should be hotels.hotel_id and free_rooms.id should be free_rooms.room_id because that makes the joins much easier...)
If you need to represent a "belongs to" relationship, you're actually looking for foreign key restraints. You should use those instead of special naming.
*ahem* Where was I? Oh yes. The second query will result in a result set with three columns - the hotel id, the number of rooms in it, and the country it's in. But, you just need the number of rooms per country, so let's do one last change.
SELECT COUNT(*), states.state
FROM hotels
INNER JOIN free_rooms ON(hotels.id = free_rooms.belongsToHotel)
INNER JOIN states ON(hotels.belongsToCountry = states.id)
GROUP BY states.state
Only two changes. First, we're now grouping together by state. Second, we're no longer including the hotel id in the result set. This should get you the data you need, again assuming that there will never be a row in free_rooms when the room is not free.
raw query - not tested:
SELECT state, COUNT( roomID ) AS rooms
FROM states
INNER JOIN hotels ON belongsToCountry = state.id
INNER JOIN free_rommms ON belongsToHotel = hotels.id
GROUP BY state
SELECT state, COUNT(*) AS nb
FROM states AS S, hotels AS H, rooms AS R
WHERE S.id = H.belongsToCountry
AND H.id = R.belongsToHotel
GROUP BY state
HAVING nb >= 1

Categories