mySQL带回来的结果不应该

时间:2017-03-07 02:54:32

标签: mysql left-join coalesce

我有一张桌子,上面写着用户写的品酒笔记,另一张桌子上有其他用户给每张品酒笔记的评分。

显示您尚未评级的其他用户编写的所有笔记的查询如下所示:

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

用户1162已为笔记113写了一个笔记。在tasteNoteRate表中,它显示为:

noteID | userVoting | score
  113       1162        0

但每次运行上述查询时仍会返回....

2 个答案:

答案 0 :(得分:2)

MySQL允许您以一种相当特殊的方式使用group by而不抱怨,请参阅documentation

  

如果禁用ONLY_FULL_GROUP_BY,则对GROUP BY的标准SQL使用的MySQL扩展允许选择列表,HAVING条件或ORDER BY列表引用非聚合列,即使这些列在功能上不依赖于GROUP BY列。 [...] 在这种情况下,服务器可以自由选择每个组中的任何值,因此除非它们相同,否则所选的值是不确定的,这可能不是您想要的

此行为是MySQL 5.7之前的默认行为。

在您的情况下,如果特定tasteNoteRate的{​​{1}}中有多行,那么如果其他人已经投票支持该注释noteID,那么使用没有聚合函数的userScored将基于随机行 - 可能是错误的行。

您可以使用聚合修复此问题:

tasteNoteRate.userVoting

或者,因为比较结果(除select ..., max(CASE WHEN tasteNoteRate.userVoting = 1162 THEN 1 ELSE 0 END) AS userScored from ... 之外的其他内容)是1或0,您还可以使用更短的版本:

null

要准备升级到MySQL 5.7(并启用select ..., coalesce(max(tasteNoteRate.userVoting = 1162),0) AS userScored from ... ),您还应ONLY_FULL_GROUP_BY列出group by列表中的所有非汇总列:{{1} }。

编写查询(以及其他方式)的另一种方法是在子查询中对select进行分组,因此您不必group by tastingNotes.userID, tastingNotes.beerID, tastingNotes.noteID, tastingNotes.note tastingNoteRates group bytastingNotes 1}}:

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;

这也可以让您通过将rates.userScored = 0 - 子句中的on更改为= 1(或删除它以获取两者)来获取用户投票的注释。

答案 1 :(得分:0)

更改为内部联接。

tasteNoteRate表保持连接到tastingNotes,这意味着返回完整的tastingNotes表(匹配where),然后通过tasteNoteRate表中的匹配字段进行扩展。如果不满足tasteNoteRate,则不会阻止tastingNotes返回匹配的字段。内连接将采用交叉点。

有关联接类型的更多说明,请参阅此处:

What's the difference between INNER JOIN, LEFT JOIN, RIGHT JOIN and FULL JOIN?

确保在两个表中的noteID上创建索引,否则此查询和用例将很快爆炸。

注意:根据您撰写的用例,我仍然不能100%确定您想加入noteID。实际上,它会尝试为所有用户提供连接所有评级的所有注释的联合表。我认为CASE ... END只会干扰查询优化器并将其转换为完整扫描+连接。为什么不在where添加另一个子句...&#34; and tasteNoteRate.userVoting = 1162&#34;?

如果这些表不是1-1,看起来像是(给定sum()和&#34; group by&#34;),那么你将面临当前查询的爆炸性问题。如果每个音符可以有10个不同的等级,并且有10个音符,则有100个候选结果行。如果它增长到1000和1000,你将快速耗尽内存。消除userID尚未投票的几行将删除最终1,000,000+中的10行,然后对它们进行求和并将它们分组?

另一种方法是反转左连接:

select ...,sum()... from tasteNoteRate ... left join tastingNotes using (noteID) where userID != xxx group by noteID,这样您只能获得其他用户的提示信息。笔记。

也许这有帮助,也许不是,但是,是的,SCHEMA和具体的用例/示例数据会有所帮助。

通过这种&#34;等级评级&#34;,有时最好保持投票总数的汇总表,并只跟踪用户已投票的内容。例如不要在选择查询中将它们全部加起来。相反,在insert...on duplicate key update (total = total + 1)中总结一下;至少这就是我如何处理一些用户排名表中的问题。他们的成长速度如此之快。