我有 4 个表格和壁纸、下载、收藏夹、投票,这些都是这样定义的。在壁纸中,我只需要 id。
下载表
+----+---------+---------+
| id | wall_id | user_id |
+----+---------+---------+
| 1 | 1 | 1 |
| 2 | 35 | NULL |
| 3 | 35 | NULL |
+----+---------+---------+
最喜欢的表
+----+---------+---------+
| id | user_id | wall_id |
+----+---------+---------+
| 1 | 12 | 10 |
| 2 | 12 | 2 |
+----+---------+---------+
投票表
+----+---------+---------+---------+
| id | user_id | wall_id | type |
+----+---------+---------+---------+
| 1 | 12 | 1 | dislike |
| 2 | 12 | 39 | like |
| 3 | 1 | 2 | like |
| 4 | 2 | 2 | like |
| 5 | 3 | 2 | like |
| 6 | 5 | 2 | dislike |
| 7 | 12 | 10 | like |
| 8 | 12 | 2 | like |
+----+---------+---------+---------+
基本上我需要的是一个查询,它返回壁纸的下载、收藏、喜欢和不喜欢的数量。尝试使用多个 LEFT OUTER JOIN 嵌套,如下所示:
SELECT w.id,COUNT(d.id) AS Downloads,
COUNT(f.id) AS Favorites,
SUM(IF(v.type = 'like',1,0)) AS Likes,
SUM(IF(v.type = 'dislike',1,0)) AS Dislikes
FROM wallpapers AS w
LEFT OUTER JOIN downloads AS d ON w.id = d.wall_id
LEFT OUTER JOIN favorites AS f ON w.id = f.wall_id
LEFT OUTER JOIN votes AS v ON w.id = v.wall_id
WHERE w.id = 2
GROUP BY w.id;
这以我需要的方式返回结果,但计算错误,即值重复并且发生其他奇怪的事情。
结果表
+----+-----------+-----------+-------+----------+
| id | Downloads | Favorites | Likes | Dislikes |
+----+-----------+-----------+-------+----------+
| 2 | 10 | 10 | 8 | 2 |
+----+-----------+-----------+-------+----------+
我知道这是因为 LEFT OUTER JOIN 不能像这样嵌套,因为 3 个表必须具有与第一个表的 id 对应的记录。
有什么解决办法吗?
问题是某些值被多次计算。这是因为 id 在
COUNT
没有检查这些 id 之前没有被计算过的情况下完成。会出现相同的错误SUM
,因此可能会多次添加相同的投票。执行 a
LEFT OUTER JOIN
保留左侧表中的所有记录,并与右侧表中的记录合并(如果没有,则为 NULL)。问题在于,通过保留左侧的值,您将其中一些值加倍(或乘以),因为它们是为右侧的每条记录“加回”的。为了更好地了解这一点,我们将从您的 SELECT 中删除
COUNT
,SUM
和GROUP BY
,这给我们留下了以下语句:当使用问题中提供的数据执行时,将返回以下内容:
如您所见,首先选择
w.id
它是 2 所示,WHERE
没有下载所以 Downloads 为 NULL,找到了一个 id 为 2 的收藏夹......现在是问题开始的时候:找到 5 票,其中每一行将具有相同的左侧与每个选票相结合。如果不是因为现在我们发现 id 2 已被选中 5 次(每次投票一次),那将不是问题。一个快速的解决方法是添加一个
DISTINCT
以COUNT
避免该问题:这似乎一目了然,但它仍然给你留下了一个问题
SUM
(你一开始看不到问题中的数据)。想象一下,对于 id 为 2 的墙纸(就像现在一样),没有一个收藏夹,而是有两个。在这种情况下,所有选票将与产生 10 行的那两个组合在一起。他们SUM
将两次添加相同的投票。为了解决这个问题,我的建议是
SUM
使用COUNT
withDISTINCT
as 作为其他值(这就是你正在做的事情,因为你真的是在模拟 aCOUNT
和 aSUM
)。并且您将“喜欢”和“不喜欢”与不同的 JOIN 分开。通过我所说的更改,SQL 语句将是这样的:
这已经总是返回正确的值,因为即使有重复的 id,也只会计算不同的 id。
基于尝试尽可能多地根据您在问题中提供的代码进行调整,我给了您一个相当长的答案。现在我将为您提供一个
SELECT
更简单的有效且获得相同结果的答案,但即便如此,我仍会推荐其他答案。这是查询:
如您所见,查询本身更短更简单......但是它包含依赖子查询(如果您执行 a 可以看到
EXPLAIN
),最终会减慢速度,因为它们依赖于其中一个值(w.id
)来自主查询,并且必须与它的每一行一起执行。对于 Álvaro 提供的两个答案,还有第三个答案是使用派生表: