为什么这个MySQL查询的结果相互成倍增加?

时间:2009-04-02 00:09:12

标签: mysql cartesian-product

SELECT user_id,
    SUM(COALESCE(point_points, 0)) AS total_points,
    SUM(
        CASE
            WHEN point_date > '$this_month'
            THEN point_points
            ELSE 0
        END)                AS month_points,
    COUNT(DISTINCT c_id)    AS num_comments,
    COUNT(DISTINCT rant_id) AS live_submissions
FROM users
    LEFT JOIN points
    ON  users.user_id = points.point_userid
    LEFT JOIN comments
    ON
        (
            c_userid = user_id
        )
    LEFT JOIN rants
    ON
        (
            rant_poster = user_id
        AND rant_status = 1
        )
WHERE user_id = $id
GROUP BY user_id

基本上live_submissionsnum_comments变量会显示正确的结果,而total_pointsmonth_points会显示month_points/total_pointslive_submissions和{num_comments的产品{1}}。知道为什么会这样吗?

3 个答案:

答案 0 :(得分:9)

这称为Cartesian Product。将表连接在一起时,默认结果为行的每个排列,其中连接条件为true。您使用JOIN条件来限制这些排列。

但是,由于您要将多个表连接到users,因此结果包括每个匹配表的每个排列。例如,points中每个匹配行重复comments中的每个匹配行,并且每个匹配行再次相乘,重复rants中每个匹配的行。

您可以使用COUNT(DISTINCT c_id)进行部分补偿,但DISTINCT只是因为每个c_id有多行才有必要。除非将其应用于唯一值,否则它不起作用。此补救措施不适用于SUM()表达式。

基本上,您尝试在一个查询中进行太多计算。您需要将其拆分为单独的查询,以使其可靠。然后你也可以摆脱DISTINCT修饰符。

SELECT u.user_id, SUM(COALESCE(p.point_points, 0)) AS total_points, 
  SUM( CASE WHEN p.point_date > '$this_month' THEN p.point_points ELSE 0 END ) AS month_points
FROM users u LEFT JOIN points p
  ON u.user_id = p.point_userid 
WHERE u.user_id = $id
GROUP BY u.user_id;

SELECT user_id, COUNT(c.c_id) as num_comments, 
FROM users u LEFT JOIN comments c
  ON (c.c_userid = u.user_id)
WHERE u.user_id = $id
GROUP BY u.user_id;

SELECT u.user_id, COUNT(r.rant_id) as live_submissions
FROM users u LEFT JOIN rants r
  ON (r.rant_poster = u.user_id AND r.rant_status = 1)
WHERE u.user_id = $id
GROUP BY u.user_id;

您不应该尝试在单个查询中执行所有这三个操作。

答案 1 :(得分:0)

你能提供一些样本输出吗?

我认为这与点数中添加咆哮和评论有关。你能尝试删除rants和comments表吗?

答案 2 :(得分:-1)

如果在分组之前查看查询输出,那么您将看到问题。如果用户在任何已连接的表中具有多个记录,则将为该用户返回多行。因此,如果用户有2条评论记录,那么也会返回2点记录。

作为一个简化的例子......

  

用户表

     

userId名称

     

1弗雷德

     

点位表

     

userId Points

     

1 10

     

评论表

     

userId评论

     

1这里

     

1那里

从这些表中选择*将导致

  

userId点评论

     

1 10这里

     

1 10那里

我不完全确定MYSQL语法,但你会想要像

这样的东西
SELECT UserId, C.num_comments, P.total_points
FROM users
LEFT JOIN 
   (SELECT c_userId, COUNT(DISTINCT c_id) as num_comments
    FROM Comments
    GROUP BY c_userId)
    AS C
    ON UserId = c_userid
LEFT JOIN 
   (SELECT point_userId, sum(COALESCE(point_points, 0)) as total_points
    FROM Points
    GROUP BY point_userId)
    AS P
    ON UserId = point_userid