Mysql ORDER BY RAND() LIMIT 1 problem - mysql

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.

Related

MySQL JOINED table query that adds a Count() column returns 1 when zero match

SELECT sg.date, sg.groupName, sg.highlights, user.display_name as displayName,
COUNT(*) as cmtcnt
FROM `saved_groups` as sg
LEFT JOIN `user` on user.email = sg.userName
LEFT JOIN `user_comments` as uc on sg.groupName = uc.groupName
GROUP BY sg.groupName
I have two tables, saved_groups and user_comments and the second line of the query should (does, it works) return an extra column with the number of comments associated with each group.
However, when a group has zero comments associated with it, the count returns 1 when it should return zero.
How can I fix this query?
I tried: COUNT(*)-1 as cmtcnt -- and that returns zero for the groups without comments, but it also returns an incorrect number (-1) for groups that have associated comments.
I also tried: NULL(Count(*), 0) -- but that errors out with #1582 - Incorrect parameter count in the call to native function 'ISNULL'
I also tried: COALESCE(COUNT(*), 0) as cmtcnt -- and that made no difference whatsoever (returned 1 for all groups with zero comments, correct number for the others)
Suggestions?
COUNT() aggregate function never returns null and COUNT(*) always returns a value greater than 0 because it counts all the rows of the resultset.
What you want is to count the rows of user_comments that match the conditions in the ON clause and you can do this by specifying inside the parentheses the column groupName:
COUNT(uc.groupName)
This way only non-null rows will be counted and you can get 0 if for a specific sg.groupName there is no matching row in user_comments.
You are counting all things from all tables when you say count(*).
Because saved_groups has a row, you get the 1 returned, which is correct.
What you want is to only count the comments. So instead of COUNT(*) as cmtcnt you should use COUNT(uc.*) as cmtcnt.
This tells the query to only count rows from the uc table.

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.

mySQL bringing back result it should not

I have a table filled with tasting notes written by users, and another table that holds ratings that other users give to each tasting note.
The query that brings up all notes that are written by other users that you have not yet rated looks like this:
SELECT tastingNotes.userID, tastingNotes.beerID, tastingNotes.noteID, tastingNotes.note, COALESCE(sum(tasteNoteRate.Score), 0) as count,
CASE
WHEN tasteNoteRate.userVoting = 1162 THEN 1
ELSE 0
END AS userScored
FROM tastingNotes
left join tasteNoteRate on tastingNotes.noteID = tasteNoteRate.noteID
WHERE tastingNotes.userID != 1162
Group BY tastingNotes.noteID
HAVING userScored < 1
ORDER BY count, userScored
User 1162 has written a note for note 113. In the tasteNoteRate table it shows up as:
noteID | userVoting | score
113 1162 0
but it is still returned each time the above query is run....
MySQL allows you to use group by in a rather special way without complaining, see the documentation:
If ONLY_FULL_GROUP_BY is disabled, a MySQL extension to the standard SQL use of GROUP BY permits the select list, HAVING condition, or ORDER BY list to refer to nonaggregated columns even if the columns are not functionally dependent on GROUP BY columns. [...] In this case, the server is free to choose any value from each group, so unless they are the same, the values chosen are indeterminate, which is probably not what you want.
This behaviour was the default behaviour prior to MySQL 5.7.
In your case that means, if there is more than one row in tasteNoteRate for a specific noteID, so if anyone else has already voted for that note, userScored, which is using tasteNoteRate.userVoting without an aggregate function, will be based on a random row - likely the wrong one.
You can fix that by using an aggregate:
select ...,
max(CASE
WHEN tasteNoteRate.userVoting = 1162 THEN 1
ELSE 0
END) AS userScored
from ...
or, because the result of a comparison (to something other than null) is either 1 or 0, you can also use a shorter version:
select ...,
coalesce(max(tasteNoteRate.userVoting = 1162),0) AS userScored
from ...
To be prepared for an upgrade to MySQL 5.7 (and enabled ONLY_FULL_GROUP_BY), you should also already group by all non-aggregate columns in your select-list: group by tastingNotes.userID, tastingNotes.beerID, tastingNotes.noteID, tastingNotes.note.
A different way of writing your query (amongst others) would be to do the grouping of tastingNoteRates in a subquery, so you don't have to group by all the columns of tastingNotes:
select tastingNotes.*,
coalesce(rates.count, 0) as count,
coalesce(rates.userScored,0) as userScored
from tastingNotes
left join (
select tasteNoteRate.noteID,
sum(tasteNoteRate.Score) as count,
max(tasteNoteRate.userVoting = 1162) as userScored
from tasteNoteRate
group by tasteNoteRate.noteID
) rates
on tastingNotes.noteID = rates.noteID and rates.userScored = 0
where tastingNotes.userID != 1162
order by count;
This also allows you to get the notes the user voted on by changing rates.userScored = 0 in the on-clause to = 1 (or remove it to get both).
Change to an inner join.
The tasteNoteRate table is being left joined to the tastingNotes, which means that the full tastingNotes table (matching the where) is returned, and then expanded by the matching fields in the tasteNoteRate table. If tasteNoteRate is not satisfied, it doesn't prevent tastingNotes from returning the matched fields. The inner join will take the intersection.
See here for more explanation of the types of joins:
What's the difference between INNER JOIN, LEFT JOIN, RIGHT JOIN and FULL JOIN?
Make sure to create an index on noteID in both tables or this query and use case will quickly explode.
Note: Based on what you've written as the use case, I'm still not 100% certain that you want to join on noteID. As it is, it will try to give you a joined table on all the notes joined with all the ratings for all users ever. I think the CASE...END is just going to interfere with the query optimizer and turn it into a full scan + join. Why not just add another clause to the where..."and tasteNoteRate.userVoting = 1162"?
If these tables are not 1-1, as it looks like (given the sum() and "group by"), then you will be faced with an exploding problem with the current query. If every note can have 10 different ratings, and there are 10 notes, then there are 100 candidate result rows. If it grows to 1000 and 1000, you will run out of memory fast. Eliminating a few rows that the userID hasn't voted on will remove like what 10 rows from eventually 1,000,000+, and then sum and group them?
The other way you can do it is to reverse the left join:
select ...,sum()... from tasteNoteRate ... left join tastingNotes using (noteID) where userID != xxx group by noteID, that way you only get tastingNotes information for other users' notes.
Maybe that helps, maybe not, but yeah, SCHEMA and specific use cases/example data would be helpful.
With this kind of "ratings of ratings", sometimes its better to maintain a summary table of the vote totals and just track which the user has already voted on. e.g. Don't sum them all up in the select query. Instead, sum it up in the insert...on duplicate key update (total = total + 1); At least thats how I handle the problem in some user ranking tables. They just grow so big so fast.

Strange MySQL problems with LIMIT and JOIN

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.

Order a query with two keys SQL Server 2008

I am trying to order a query by two keys. The query is built with several subqueries. The table contains, beside columns with other data, two columns, Key and Key_Father. So I need to order the results since SQL to print the results in a report. This is an example:
Key Key_Father
4 NULL
1 4
2 4
7 NULL
1 7
2 7
As you can see is a structure father-son, where a row is a father if the Key_Father is NULL and the Key column start from one for each son with a different father.
The first subquery gives the data in order, because is stored on that order in the table, but the second subquery that uses a group by, no. So I tried adding a extra column with Row_Number on the first subquery to keep that order, but the second subquery does the same thing.
This is the query:
SELECT Orden,INV_Key,Key_Padre,INV.INV_ID,INV.BOD_Bodega_ID,
CASE WHEN MAX(HIS_Ventas) > 0 OR max(HIS_Disponible) > 0 THEN 1 ELSE 0 END AS Participacion,MAX(ISNULL(HIS_Ventas,0)) AS Ventas
FROM(SELECT ROW_NUMBER() OVER (ORDER BY C.INV_Compra_ID) Orden,C.BOD_Bodega_ID,INV_Key,Key_Padre,CD.INV_ID
FROM dbo.INV_COMPRAS_USADOS C
INNER JOIN dbo.INV_COMPRAS_USADOS_DET CD ON C.INV_Compra_ID = CD.INV_Compra_ID
WHERE C.INV_Compra_ID = #Compra_ID
AND ((Key_Padre IS NULL AND CD.INV_Catalogo_Codigo = ISNULL(#Cod_Catalogo,CD.INV_Catalogo_Codigo)
AND INV_Key IN (SELECT DISTINCT Key_Padre
FROM dbo.INV_COMPRAS_USADOS_DET
WHERE INV_Compra_ID = #Compra_ID AND Key_Padre IS NOT NULL))
OR Key_Padre IN (SELECT DISTINCT INV_Key
FROM dbo.INV_COMPRAS_USADOS_DET
WHERE INV_Compra_ID = #Compra_ID AND (Key_Padre IS NULL AND CD.INV_Catalogo_Codigo = ISNULL(#Cod_Catalogo,CD.INV_Catalogo_Codigo))))) INV
LEFT JOIN DBO.HIS_HISTORICO_DETALLE HD ON INV.INV_ID = HD.INV_ID AND HD.BOD_Bodega_ID = INV.BOD_Bodega_ID
LEFT JOIN DBO.HIS_HISTORICO_INVENTARIO H on H.HIS_Historico_ID= HD.HIS_Historico_ID AND (CONVERT(datetime,(convert(varchar(20),HIS_Historico_Ano) + '/' + convert(varchar(20),HIS_Historico_Mes) + '/01')) BETWEEN #FechaDesde AND #FechaHasta)
WHERE H.HIS_Historico_Mes IS NOT NULL OR INV.INV_ID IS NULL
GROUP BY Orden,INV_Key,Key_Padre,INV.INV_ID,INV.BOD_Bodega_ID,HIS_Historico_Ano,HIS_Historico_Mes
Another interesting thing (well for me) is that when I change the #Variables for Constant values, the second query keeps the correct order, even when the constant values are the same that the #variables. This is just a portion of the total query, is a subquery that needs of another two selects, and I need to keep the order from those selects too.
So I hope that someone could help me with this. Thanks!
To order the results you need to place an ORDER BY clause on the outermost SELECT statement. Using ORDER BY in a nested SELECT is generally not permitted but even if you work around it (e.g. by using TOP), you can't rely on the results being ordered in any particular way.
Without an ORDER BY the results may appear to be coming out in the order you want but this cannot be relied upon. Running the same query on a different server or at some point in the future may produce a different order where differences in statistics, server load, etc can affect how the query optimizer actually executes the statement.
The portion of the query you've provided is outputting the following columns. Which are the ones you want to order by?
Orden (although this is just an alias for INV_Compra_ID as far as orderin is concerned)
INV_Key
Key_Padre
INV_ID
BOD_Bodega_ID
Participacion
Ventas
Let's say you want to order by just thre of them, then you need to append the following clause to the outermost SELECT:
ORDER BY
Orden,
INV_Key,
Key_Padre,
This should do it. I'm not sure if I'm missing an obvious simplification though.
ORDER BY ISNULL(Key_Father,[Key]), ISNULL(Key_Father,-1),[Key]