Strange MySQL problems with LIMIT and JOIN - mysql

I have this query:
SELECT s.*
FROM #mcmodlist_servers s
LEFT OUTER JOIN #mcmodlist_tag_server ts
ON ts.server_id = s.id
(don't mind the #mcmodlist_ bits, it's converted by PHP into the actual table names).
When executed as written as above it gives a result of 5 records, as it should, but when I add LIMIT 10 it suddenly returns 4.
But wait, it gets even better: If I change it to LIMIT 12 there's suddenly 5 records again (LIMIT 11 still returns 4).
Left outer should join only if it has a matching record and otherwise return null, right?
Why is LIMIT behaving like this? it works just fine without the JOIN clause

I think if you run the query in a MySQL client, with limit 10, you will find that it is in fact returning 10 rows in the resultset.
I suspect that there are multiple rows in #mcmodlist_tag_server with a server_id that matches a row from #mcmodlist_servers. When there are multiple matching rows, you are going to get "duplicate" rows from #mcmodlist_servers.
Given that there are no columns returned from the #mcmodlist_tag_server table, and that this is an OUTER join, it's not at all clear why this table would be included in the query at all.
And no, LEFT JOIN does not mean what you said it means.
Q: Left outer should join only if it has a matching record and otherwise return null, right?
A: No. That's not what LEFT JOIN means. A LEFT JOIN will return all rows from the table on the left side, along with matching rows from the right side, just like an INNER JOIN. But with the LEFT JOIN, if there's a row from the left side that doesn't have a matching row from the right side, the row from the left is returned. Yes, when that happens, the columns from the rightside table will consist of NULL placeholders.
The LIMIT clause applies to the total number of rows returned in the resultset. It does not mean the number of distinct rows from a given table.

Related

left join with group by returns no rows if table does not contains the specific id

I want to find out how many people rated a specific location. the problem is that my sql ONLY works if the hasVotedLocation table CONTAINS THE ID OF THE SPECIFIC LOCATION. Meaning if nobody voted the specific location i get zero rows.
My sql statement:
select count(*),l.idLoc from Location l
left join hasVotedLocation hvl
on hvl.idLoc=l.idLoc where l.idLoc=2
group by l.idLoc
For idLoc=1 the query should return one row with count(*)=3 and idLoc=1
For idLoc=2 the query should return one row with count(*)=0 and idLoc=2
For idLoc=3 the query should return one row with count(*)=1 and idLoc=3
You can remove the GROUP BY:
select <someid> as idLoc, count(x)
from Location l left join
hasVotedLocation hvl
on hvl.idLoc = l.idLoc ;
This will always return one row. The count() will be 0 if there are no matching rows.
In a GROUP BY query COUNT(*) returns the number of rows in the group. However when the condition in the ON clause of the LEFT JOIN doesn't match any row in the right table, the result will be one row with all columns from the right table filled with NULLs. That is still one row - not zero rows. Thus COUNT(*) will never return a value less than 1 in this query. But the result will also not be "zero rows" (as you stated in your question), as long as the condition in the WHERE clause matches any row in the left table.
To get the number of matches in the right table (the number of votes in your case), you should count the number of non NULL values from the right table. It is usually best to use the same column as in the ON clause (this way the engine can use the same index without a second lookup), which would be COUNT(hvl.idLoc).
select count(hvl.idLoc), l.idLoc from Location l
left join hasVotedLocation hvl
on hvl.idLoc=l.idLoc where l.idLoc = ?
group by l.idLoc
However - If all you need is the number of votes for a specific idLoc value, you can just use a simple COUNT(*) query.
select count(*)
from hasVotedLocation
where idLoc = ?
Note: In this query COUNT(*) will return 0, if the WHERE condition doesn't match any row. This is because SELECT * would return zero rows.

Access 2010 query still showing results when record empty

I am trying to make a query joining 4 tables. One table will only fill out some records, not all. How do I get the criteria to be:
If record is null still show the field..... because right now if the record is null in any one of the fields it will not show in my query results.
If records are missing in one table, then you must use an outer join. Say, you have a table A with 5 records and a table B with only 3 records and you want to make a query joining the two tables showing 5 records
SELECT * FROM
A
LEFT JOIN B
ON A.ID = B.FK
In the query designer, right click on the join-line and enter this:
It sounds like you want your filter conditions to ignore NULLs. (That is, by default a NULL wouldn't match your filter, but the desired output is that it should.)
If this is correct, you want to modify the filters to allow NULLs, for example by adding OR IS NULL.
It sounds like you need to use a LEFT (or "outer") Join instead of an INNER join.
Outer joins (like LEFT JOINs) will return all results whether there are matching records in the other table or not. You'll just have null column values in the results for the joined tables.
More info here: http://pcmcourseware.com/blog/2010/11/10/modifying-query-joins-in-microsoft-access/

Mysql ORDER BY RAND() LIMIT 1 problem

The problem is the bellow query.
SELECT RSV.Value
FROM tblsubmitedservice SS
LEFT JOIN (SELECT ServiceID, Value
FROM tblservicevalue
ORDER BY RAND()
LIMIT 1) RSV ON RSV.ServiceID = SS.ServiceID
This query must retrieve 1 random value from tblservicevalue in JOIN with tblsubmitedservice like above. But sometimes (I do not know why sometimes) the query return null. If I move the "LIMIT 1" to the end of query (no more inside the subquery), the query run correctly.
This query is simplified to understand and in the original query this solution is not possible.
May be all the ServiceID's in tblservicevalue don't have corresponding ServiceIDs in tblsubmittedservice i.e., there is not a strict one-to-one relation between the tables.
You can check the tables using this:
1>Check the number of rows of tblservicevalue and tblsubmittedservice are equal.
2>Next check if
SELECT
RSV.Value
FROM tblsubmitedservice SS
LEFT JOIN (SELECT ServiceID, Value FROM tblservicevalue) RSV ON RSV.ServiceID=SS.ServiceID
has the same no. of rows as tblservicevalue,tblsubmittedservice.
If either of 1,2 fail, clearly the behaviour is due to the reason I explained above.
Check if below query returns any rows:
SELECT RSV.Value
FROM tblsubmitedservice SS
LEFT JOIN tblservicevalue RSV ON RSV.ServiceID = SS.ServiceID
WHERE RSV.ServiceID IS NULL
If it does return any rows it means that some rows from tblsubmitedservice have no correspoding rows in tblservicevalue (with regard to ServiceID field). In case of LEFT JOIN if rows on the right side can't be found (ie. there's no such RSV.ServiceID in RSV table) NULLs are used instead.
Contrary to previous comment number of rows does not have to be equal.

Two SQL Joins, Two Different Results

Hows is it possible for these two queries to be different. I mean the first query didn't include all the rows from my left table so I put the conditions within the join part.
Query 1
SELECT COUNT(*) as opens, hours.hour as point
FROM hours
LEFT OUTER JOIN tracking ON hours.hour = HOUR(FROM_UNIXTIME(tracking.open_date))
WHERE tracking.campaign_id = 83
AND tracking.open_date < 1299538799
AND tracking.open_date > 1299452401
GROUP BY hours.hour
Query 2
SELECT COUNT(*) as opens, hours.hour as point
FROM hours
LEFT JOIN tracking ON hours.hour = HOUR(FROM_UNIXTIME(tracking.open_date))
AND tracking.campaign_id = 83
AND tracking.open_date < 1299538799
AND tracking.open_date > 1299452401
GROUP BY hours.hour
The difference is that the first query gives me 18 rows where there are no rows between point 17 to 22. But when I run the second query, it shows the fully 24 rows but for rows between 17 and 22 it has a value of 1! I would of expected it to be 0 or NULL? If it really is 1 should it not have appeared in the first query?
How has this happened?
the first JOIN is really an INNER JOIN, the outer joined table should not appear in the WHERE clause like you have in the top query, instead of COUNT(*), pick a column from the outer joined table
You're using COUNT(*), which will count every row in your result set (as it's written), since even without data in tracking, you do have data in hours.
Try changing COUNT(*) to COUNT(tracking.open_date) (or any non-nullable column within tracking; it doesn't matter which one).
COUNT(*) counts the number of rows resulted in the query.
You can use count(tracking.open_date), basically any column from tracking table (right table)
The problem is that the first query will do an outer join, with some rows containing NULL in all tables from the tracking table. Then it will apply a filter on those tracking columns, and since they are null, the corresponding row from the result set will be filtered out.
The second query will do a proper outer join on all columns.

Left Outer Join doesn't return all rows from my left table?

I am trying to get the number of page opens on a per day basis using the following query.
SELECT day.days, COUNT(*) as opens
FROM day
LEFT OUTER JOIN tracking ON day.days = DAY(FROM_UNIXTIME(open_date))
WHERE tracking.open_id = 10
GROUP BY day.days
The output I get it is this:
days opens
1 9
9 2
The thing is, in my day table, I have a single column that contains the number 1 to 30 to represent the days in a month. I did a left outer join and I am expecting to have all days show on the days column!
But my query is doing that, why might that be?
Nanne's answer given explains why you don't get the desired result (your WHERE clause removes rows), but not how to fix it.
The solution is to change WHERE to AND so that the condition is part of the join condition, not a filter applied after the join:
SELECT day.days, COUNT(*) as opens
FROM day
LEFT OUTER JOIN tracking
ON day.days = DAY(FROM_UNIXTIME(open_date))
AND tracking.open_id = 10
GROUP BY day.days
Now all rows in the left table will be present in the result.
You specify that the connected tracking.open_id must be 10. For the other rows it will be NULL, so they'll not show up!
The condition is in the WHERE clause. After joining the tables the WHERE conditions are evaluated to filter out everything matching the criteria.Thus anything not matching tracking.open_id = 10 gets discarded.
If you want to apply this condition while joining the two tables, a better way is to use it with the ON clause (i.e. joining condition) than the entire dataset condition.