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
)
Related
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
;
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
I have two primary SELECT statements that return the desired results when run individually, but when combined, don't return the desired result.
Query 1
This works fine, returning the expected result.
SELECT feed_mode_id FROM user WHERE id=2;
+--------------+
| feed_mode_id |
+--------------+
| 1 |
+--------------+
Query 2
This is also fine. Sometimes the result will be empty, sometimes not.
SELECT
answer.id AS answer_id
FROM
answer
WHERE
answer.question_id = (
SELECT
question.id
FROM
question
ORDER BY
datetime_added_utc DESC
LIMIT 1
)
AND answer.user_id = 2;
Empty set (0.00 sec)
Query 1 and 2 combined
When combining these into two sub-SELECT statements as shown below, feed_mode_id is NULL, but the result for x.feed_mode_id should be as shown in Query 1. This is my lack of understanding as to how these kind of combined statements work.
SELECT
x.feed_mode_id,
IF (COUNT(y.answer_id) < 1, 0, 1) AS answered_question
FROM
(SELECT
user.feed_mode_id
FROM
user
WHERE
user.id = 2) AS x,
(SELECT
answer.id AS answer_id
FROM
answer
WHERE
answer.question_id = (
SELECT
question.id
FROM
question
ORDER BY
datetime_added_utc DESC
LIMIT 1
)
AND answer.user_id = 2) AS y
+--------------+-------------------+
| feed_mode_id | answered_question |
+--------------+-------------------+
| NULL | 0 |
+--------------+-------------------+
Why is feed_mode_id producing NULL and not 1? I'm open to different approaches to re-writing the query altogether as well. The desired result would be:
+--------------+-------------------+
| feed_mode_id | answered_question |
+--------------+-------------------+
| 1 | 0 |
+--------------+-------------------+
This is somehow related to the fact that Query 2's result is empty for this case. For cases where Query 2 returns a value (not empty) then the combined query works as desired.
You have a Cartesian product between x and y. As long as each of those rowsources return only one row, the query will return a single row.
I recommend you ditch the old-school comma syntax for the join operation, and use the JOIN keyword instead.
Also, it's not clear why you need inline views and subqueries, apart from the subquery that returns that "most recently added" question.
I'm not entirely clear what resultset you are actually attempting to return, but it looks like you are determining whether a particular user (uniquely identified by the "id" column of the "user" table), has provided an "answer" to the "most recently added question".
If that's the result you are trying to return, I believe this query will return that result:
SELECT u.feed_mode_id
, IF( COUNT(a.id) < 1, 0, 1) AS answered_question
FROM ( SELECT q.id
FROM question q
ORDER BY q.datetime_added_utc DESC, q.id DESC
LIMIT 1
) r
JOIN user u
ON u.id = 2
LEFT
JOIN answer a
ON a.user_id = u.id
AND a.question_id = r.id
GROUP BY u.id
NOTES: The inline view aliased as r return the id of the "most recently added" question. (In the original query, if two or more question have the same datetime_added_utc, it's indeterminate which row will be returned. This query makes it determinate by adding another expression to the ORDER BY clause. (The inline view query could be pulled out and run separately, to verify it's returning the expected result.)
The row returned from r (if there is one) is then joined to a row retrieved from the "user" table u. We're assuming here that the id column in the "user" table is a unique identifier, and likely the primary key of the "user" table.
If we have a "most recently added" question (i.e. a row from r), and there's a row from "user" that matches the u.id=2 predicate in the ON clause, then so far, we are guaranteed that the query is going to return a single row.
Next, we perform an "outer join" operation, to find matching rows from the "answer" table. The predicates in the ON clause restrict the rows returned to only those that have a user_id matching the id from u (in this example, equivalent to specifying a.user_id=2, and having a question_id that matches the id of the "most recently added" question r.
The LEFT keyword identifies this as an "outer join"; if there are no matching rows from the "answer" table, then the query will still return the row from r and u. (If this were an inner join, that is, if we removed the LEFT keyword, then if there were no matching rows from the "answer" table, then the query would not return a row.)
We add a GROUP BY u.id clause, in case we get more than one matching row from answer; the GROUP BY causes all rows that have the same value of u.id to be collapsed into a single row.
The COUNT() aggregate counts the number of non-null occurrences of the id from the "answer" table. If there were no matching rows found, then a.id will be NULL, so COUNT(a.id) will return 0.
This same query would also work if we were looking for multipler users, if we specified multiple values for user.id to match, e.g.
ON u.id IN (2,3,5,7)
or also if we left that predicate off entirely, so we got a row back for every user. This query would still work.
But in either of those cases, we'd also want to add u.id AS user_id to the SELECT list of the query, so we would know which row was for which user.
If we wanted to return the two most recently added questions, we could change the LIMIT clause in r, and then add r.id to the GROUP BY clause. Again, we'd then likely also want to return r.id AS question_id in the SELECT list, so we knew which row was for which question.
Exactly query
SELECT
x.feed_mode_id,
IF (COUNT(y.answer_id) < 1, 0, 1) AS answered_question
FROM
(SELECT
user.feed_mode_id
FROM
user
WHERE
user.id = 2) AS x,
(SELECT
answer.id AS answer_id
FROM
answer
WHERE
answer.question_id IN (
SELECT
question.id
FROM
question
ORDER BY
datetime_added_utc DESC
LIMIT 1
)
AND answer.user_id = 2) AS y
answer.question_id = to answer.question_id IN
I don't like to answer my own question, but I found a solution that works. I've moved the IF statement to the sub-SELECT rather than the primary SELECT. I don't know why this works and the previous attempt doesn't, but it's producing the desired result now.
SELECT
x.feed_mode_id,
y.question_answered
FROM
(SELECT
user.feed_mode_id
FROM
user
WHERE
user.id = 2) AS x,
(SELECT
IF (COUNT(answer.id < 1), 1, 0) AS question_answered
FROM
answer
WHERE
answer.question_id = (
SELECT
question.id
FROM
question
ORDER BY
datetime_added_utc DESC
LIMIT 1
)
AND answer.user_id = 2) AS y;
+--------------+-------------------+
| feed_mode_id | question_answered |
+--------------+-------------------+
| 1 | 0 |
+--------------+-------------------+
That title is really not useful, but its a complex question (in my head, maybe) ... anywho...
Say I have a MySQL table of Countries (A-Z all countries in the world) with id & name
Then I have a table where I am tracking which countries a user has been to: Like so:
Country Table
id name
1 india
2 luxembourg
3 usa
Visited Table
id user_id country_id
1 1 1
2 1 3
Now here's what I want to do, when I present the form to add to the list of visited countries I want country.id 1 & 3 to be excluded from the query result.
I know I can filter this using PHP ... which is something I have done in the past ... but surely there must be a way to structure a query in such a way that 1 & 3 are excluded from the returned results, like:
SELECT *
FROM `countries`
WHERE `id`!= "SELECT `country_id`
FROM `visited`
WHERE `user_id`='1'"
I suspect it has something to do with JOIN statements but I can't quite figure it out.
Bonus gratitude if someone can point me in the right direction with Laravel.
Thanks you all :)
Is this what you want?
select c.*
from countries c left join
visited v
on c.id = v.country_id and v.user_id = 1
where v.country_id is null;
You can also express this as a not in or not exists, but the left join method typically has pretty good performance.
The left outer join keeps all records in the first table regardless of whether or not the on clause evaluates to true. If there are no matches in the second table, then the columns are populated with NULL values. The where clause simply chooses these records -- the ones that do not match.
Here is another way of expressing this that you might find easier to follow:
select c.*
from countries c
where not exists (select 1 from visited where c.id = v.country_id and v.user_id = 1)
You can use your query like this.
SELECT *
FROM `countries` c LEFT JOIN `visited` v on c.id = v.country_id
WHERE v.`country_id` is null
AND v.`user_id` = 1
This is a operation of a LEFT JOIN. What is means is that I'm selecting all registries from the table countries that may or may not is on the table visited based on the ID of the country.
So it will bring you this group
from country from visited
1 1
2 no registry
3 3
So on the where condition (v.country_id is null) I'm saying: I only want the ones that on this left join operation is only on the country table but it is not on visited table so it brings me the id 2. Plus the condition that says that those registries on visited must be from the user_id=1
SELECT * FROM COUNTRIES LEFT JOIN
VISITED ON
countries.id = visited.country_id and visited.country_id NOT IN ( SELECT country_id FROM visited )
if i understand right maybe you need something like this ?
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).