MySQL Issue with combined SELECT statements - php

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 |
+--------------+-------------------+

Related

MySql: Get number of row from query where id matches

I have a complicated select like:
select id from table
left join...
left join... (a lot of joins)
where ... (a lot of ANDs and ORs)
order by... (a lot of orders)
and I'm getting a result like:
1234
5565
7212
2212
etc.
I have an id which belongs to the result-set, like 7212, and want to know which row in the result-set matches the id (starting with row 0 this would be row 2 in my example).
Right now I'm reading all data and compare it in php, but I was wondering if there is a SQL-statement which does that for me and results 2 when entering 7212.
In fact I want to get the previous and next ids (row 1 and row 3 = 5565 and 2212), if thats somehow possible in 1 query that would be even better.
You can select the number row:
select #rownum:=#rownum+1 No, foo, bar from table, (SELECT #rownum:=0) r;
It's a possible duplicate of the question asked here: How to show sequential number in MySQL query result
Add an auto_increment index for each selected rows:
SELECT
#i:=#i+1 as index,
id
FROM table, (SELECT #i:= 0) AS i
LEFT JOIN...
LEFT JOIN...(a lot of joins)
WHERE ... (a lot of ANDs and ORs)
ORDER BY... (a lot of orders)
Give you this:
index id
1 1234
2 5565
3 7212
4 2212

Change Group by Order

Consider this table:
id | machine | manual| etc.
1 1 0
2 2 0
3 1 1
I want to retrieve the records grouped by Machine. I know that the manual records are always inserted after the automatic ones.
In MySQL I can do this:
SELECT * FROM table GROUP BY machine DESC
And the records retrieved are:
id | machine | manual| etc.
2 2 0
3 1 1
Because the GROUP BY of MySQL works in ASC and the first record it finds is the one it considers. So by placing the DESC in the GROUP BY it works in reverse and since I know that the manuals always come after this would work for my solution. But how can I do this in Doctrine?
$qb = Query Builder 'b'
$qb->groupBy('b.machine DESC');
Does anyone know how to do this without sub querys?
Thank you
Your solution with group by is against the sql standard, and may not work if only full group by sql mode is configured in MySQL. This is the default setting of the recent versions of MySQL.
Since you want the latest record identified by an auto increment id, you can achieve the expected outcome with a self left join:
select t1.*
from table t1
left join table t2 on t1.machine=t2.machine and t1.id<t2.id
where t2.id is null
Basically, with the t1.id<t2.id join condition you get all records where t2.id is greater for the same machine. If a greater t2.id is not found, a null value is returned - indicating that this is the latest record for the given machine. This is the record we keep with the where t2.id is null clause.

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 Inner Join Returning Multiples of the Same Row

I have two MySql Tables as follows:
resource
-----------------------------------------------
id name group owner_id
-----------------------------------------------
1 MyResource1 hs 11
2 MyResource2 ms 24
3 MyResource3 ps 11
...
resource_access
-----------------------------------------------
id resource_id user_id
-----------------------------------------------
1 1 12
2 2 24
3 2 11
4 3 15
...
Now, the first table is a list of resources, of course, and their respective owners in the owner_id column. The second table is the result of "sharing" this resource with another user. The table resource_access may contain records with a user_id that is equivalent to the owner_id in a row of the resource_access as a result of messy cleanup from an owner exchange.
I simply want to get the id, name, and group of any resource that a user has access to, whether they are the owner or it has been shared with them. Here is my MySQL query for an example user (24):
SELECT resource.id, resource.name, resource.group
FROM `resource`
INNER JOIN resource_access ON (
resource.owner_id='24'
OR (
resource_access.user_id='24' AND
resource_access.resource_id=resource.id
)
)
Right now, it returns the id, name, and group for resource number 2 multiple times (like twelve). Is there a possible cause for this? I have tried LEFT and RIGHT joins and am getting the same result. There are many records in the resource table, but none with the id of 2. There are no duplicate rows in resource_access sharing the resource with the same user twice.
Thanks in advance.
Use:
SELECT DISTINCT resource.id, resource.name, resource.group
to remove duplicates.
The way an inner join conceptually works is that it produces a full cross-product between the two tables. This cross-product contains a row for each pair of rows in the input tables. Then it keeps the rows that match all the ON and WHERE conditions, and returns this as the result set. If there are multiple matching rows between the two tables, you'll get multiple rows in the result set.
If you were selecting columns from both tables, you would see that they're not actually the same row. They just have the same data from the resource table, but different data from the resource_access table. But you're not showing those latter columns in your result. Using DISTINCT merges all these rows in the result.
Because you are only selecting from the resource table, I would suggest putting the conditions in the where clause rather than using an explicit join:
SELECT r.id, r.name, r.group
FROM `resource` r
WHERE r.owner_id='24' or
EXISTS (select 1
from resource_access ra
where ra.resource_id = r.id and
ra.user_id = '24'
);
With this logic, the "join" cannot product duplicates.
Select the ownership of resources then union it to resources with access.
Resulting user_id column that is different from your WHERE RA.user_id value just means that resource was shared to them instead of them owning the resource. Hope this helps.
SELECT resource.name,resource.group,resource.owner_id AS user_id
FROM resource
WHERE resource.owner_id = '11'
UNION
SELECT R.name,R.group,R.owner_id AS user_id
FROM resource_access RA
LEFT JOIN resource R
ON (R.id=RA.resource_id)
WHERE RA.user_id = '11';

Categories