我在查询中遇到SQL连接问题,该查询旨在查询Post表已加入评论,点击和投票表并返回有关每个帖子活动的统计信息。我的查询是我一直在使用的。
SELECT
p.PostID,
p.Title,
CASE
WHEN COUNT(cm.CommentID) IS NULL THEN 0
ELSE COUNT(cm.CommentID)
END AS CommentCount,
CASE
WHEN COUNT(cl.ClickID) IS NULL THEN 0
ELSE COUNT(cl.ClickID)
END AS ClickCount,
CASE
WHEN SUM(vt.Value) IS NULL THEN 0
ELSE SUM(vt.Value)
END AS VoteScore
FROM
Post p
LEFT OUTER JOIN Comment cm ON p.PostID = cm.PostID
LEFT OUTER JOIN Click cl ON p.PostID = cl.PostID
LEFT OUTER JOIN Vote vt ON p.PostID = vt.PostID
GROUP BY
p.PostID,
p.Title
产生以下结果
| PostID | CommentCount | ClickCount | VoteScore |
|--------|--------------|------------|-----------|
| 41 | 60| 60| 60|
| 50 | 1683| 1683| 1683|
这个,我知道不对。当注释掉除了一个连接之外的所有连接时:
SELECT
p.PostID
,p.Title
,CASE
WHEN COUNT(cm.CommentID) IS NULL THEN 0
ELSE COUNT(cm.CommentID)
END AS CommentCount
/*
,CASE
WHEN COUNT(cl.ClickID) IS NULL THEN 0
ELSE COUNT(cl.ClickID)
END AS ClickCount
,CASE
WHEN SUM(vt.Value) IS NULL THEN 0
ELSE SUM(vt.Value)
END AS VoteScore
*/
FROM
Post p
LEFT OUTER JOIN Comment cm ON p.PostID = cm.PostID
/*
LEFT OUTER JOIN Click cl ON p.PostID = cl.PostID
LEFT OUTER JOIN Vote vt ON p.PostID = vt.PostID
*/
GROUP BY
p.PostID,
p.Title
我得到了
| PostID | CommentCount |
|--------|--------------|
| 41 | 3|
哪个是对的。我做错了什么想法?
感谢。
答案 0 :(得分:3)
正在返回的结果是预期的,因为查询正在生成笛卡尔(或半笛卡尔)积。该查询基本上告诉MySQL对从comment
,click
和vote
返回的行执行“交叉连接”操作。
从comment
(对于给定的postid)返回的每一行都匹配click
的每一行(对于相同的postid)。然后,该结果中的每一行都与vote
中的每一行匹配(对于相同的postid)。
因此,对于来自comment
的两行,来自click
的三行和来自vote
的四行,这将返回总共24(= 2x3x4)行。
解决此问题的常用模式是避免交叉连接操作。
有几种方法可以做到这一点。
选择列表中的相关子查询
如果您只需要来自三个表中的每个表的单个聚合(例如COUNT或SUM),则可以删除连接,并在SELECT列表中使用相关子查询。编写一个获取单个postid计数的查询,例如
SELECT COUNT(1)
FROM comment cmt
WHERE cmt.postid = ?
然后将该查询包装在parens中,并在另一个查询的SELECT列表中引用它,并将问号替换为外部查询中引用的表中对postid的引用。
SELECT p.postid
, ( SELECT COUNT(1)
FROM comment cmt
WHERE cmt.postid = p.postid
) AS comment_count
FROM post p
重复相同的模式,从click
和vote
获取“计数”。
这种方法的缺点是SELECT列表中的子查询将由外部查询返回的每个行执行。因此,如果外部查询返回大量行,这可能会变得昂贵。如果comment
是一个大表,那么为了获得合理的性能,comment
上有适当的索引是至关重要的。
在内联视图中进行预聚合
另一种方法是“预聚合”结果内联视图。编写一个返回postid注释计数的查询。例如
SELECT cmt.postid
, COUNT(1)
FROM comment cmt
GROUP BY cmt.postid
在parens中包装该查询并在另一个查询的FROM子句中引用它,指定别名。内联视图查询基本上取代了外部查询中的表。
SELECT p.postid
, cm.postid
, cm.comment_count
FROM post p
LEFT
JOIN ( SELECT cmt.postid
, COUNT(1) AS comment_count
FROM comment cmt
GROUP BY cmt.postid
) cm
ON cm.postid = p.postid
并为click
和vote
重复相同的模式。这里的技巧是内联视图查询中的GROUP BY子句,它保证它不会返回任何重复的postid值。而且笛卡尔积(交叉连接)不会产生重复。
这种方法的缺点是派生表不会被索引。因此对于大量postid,在外部查询中执行连接可能是昂贵的。 (更新版本的MySQL通过自动创建适当的索引来部分解决这个缺点。)
(我们可以通过创建具有适当索引的临时功能来解决此限制。但是这种方法需要额外的SQL语句,并不完全适用于特殊的单一语句。但对于大型集的批处理,额外的复杂性可以值得一些显着的性能提升。
按DISTINCT值折叠笛卡尔积
作为一种完全不同的方法,使用交叉连接操作保留您的查询,并允许MySQL生成笛卡尔积。然后SELECT列表中的聚合可以过滤掉重复项。这要求您为comment
创建一个列(或表达式),该列对于给定postid的注释中的每一行都是唯一的。
SELECT p.postid
, COUNT(DISTINCT c.id) AS comment_count
FROM post p
LEFT
JOIN comment c
ON c.postid = p.postid
GROUP BY p.postid
这种方法的一大缺点是它有可能产生巨大的中间结果,然后通过“使用文件排序”操作“折叠”(以满足GROUP BY)。对于大型套装而言,这可能相当昂贵。
这不是列出所有可能的查询模式的详尽列表,以实现您希望返回的结果。只是一个代表性的抽样。
答案 1 :(得分:2)
你可能想要这样的东西:
SELECT
p.PostID,
p.Title,
(SELECT COUNT(*) FROM Comment cm
WHERE cm.PostID = p.PostID) AS CommentCount,
(SELECT COUNT(*) FROM Click cl
WHERE p.PostID = cl.PostID) AS ClickCount ,
(SELECT SUM(vt.Value) FROM Vote vt
WHERE p.PostID = vt.PostID) AS VoteScore
FROM
Post p
您的查询存在问题,即第二个和第三个LEFT JOIN
操作重复记录:在应用第一个LEFT JOIN
后,您有PostID = 41
的帖子记录,例如3个。第二个LEFT JOIN
现在加入到这3条记录中,因此PostID = 41
在第二个LEFT JOIN
中使用 3次。
如果(Post
,Comment
),(Post
,Click
)和({}之间存在1:多关系直接 {1}},Post
),那么上面的查询很可能会给你你想要的东西。
答案 2 :(得分:1)
您的查询未执行您认为正在执行的操作。当您加入并计算这样的行时,您将创建一个包含x行的新数据集,然后只计算该数据集中的行三次。因此,你得到三次相同的计数。
您要做的只是计算评论行,并点击左连接在这两个表中找到数据的位置,例如:
SELECT
p.PostID
,p.Title
,COUNT(CASE
WHEN cm.PostID IS NULL THEN 0
ELSE 1
END) AS CommentCount
,COUNT(CASE
WHEN cl.PostID IS NULL THEN 0
ELSE 1
END) AS ClickCount
,SUM(CASE
WHEN vt.PostID IS NULL THEN 0
ELSE ISNULL(vt.Value,0)
END) AS VoteScore
FROM
Post p
LEFT OUTER JOIN Comment cm ON p.PostID = cm.PostID
LEFT OUTER JOIN Click cl ON p.PostID = cl.PostID
LEFT OUTER JOIN Vote vt ON p.PostID = vt.PostID
GROUP BY
p.PostID,
p.Title
答案 3 :(得分:1)
已经解释了你的查询有什么问题:对于postid 41,说3条评论,5次点击和4票(每次投票值为1),你得到3x5x4 = 60次计数第一次和第二次计数表达式和3x5x4x1 = 60表示总和。
当与聚合一起处理多个外部联接时,您不能先连接表并稍后聚合,而是首先聚合并加入聚合:
select
p.postid,
p.title,
coalesce(cm.cnt, 0) as commentcount,
coalesce(cl.cnt, 0) as clickcount,
coalesce(vt.total, 0) as votescore
from post p
left outer join (select postid, count(*) as cnt from comment group by postid) cm
on cm.postid = p.postid
left outer join (select postid, count(*) as cnt from click group by postid) cl
on cl.postid = p.postid
left outer join (select postid, sum(value) as total from vote group by postid) vt
on vt.postid = p.postid;
答案 4 :(得分:1)
COUNT计算非空值。但是将空值设置为0,它们就算了。将您的计数更改为SUM并将其移出案例,我认为它将解决问题。
EG
SELECT
p.PostID,
p.Title,
SUM(CASE
WHEN cm.CommentID IS NULL THEN 0
ELSE cm.CommentID
END) AS CommentCount,
SUM(CASE
WHEN cl.ClickID IS NULL THEN 0
ELSE cl.ClickID
END) AS ClickCount,
SUM(CASE
WHEN SUM(vt.Value IS NULL THEN 0
ELSE SUM(vt.Value
END) AS VoteScore
FROM
Post p
LEFT OUTER JOIN Comment cm ON p.PostID = cm.PostID
LEFT OUTER JOIN Click cl ON p.PostID = cl.PostID
LEFT OUTER JOIN Vote vt ON p.PostID = vt.PostID
GROUP BY
p.PostID,
p.Title