SQL查询使用“错误”连接

时间:2013-08-15 14:43:46

标签: sql join sum

我有一个查询,它给了我错误的结果。

表:

A
+----+
| id |
+----+
|  1 |
|  2 |
+----+

B
+----+----+
| id |  x |  B.id = A.id
+----+----+
|  1 |  1 |
|  1 |  1 |
|  1 |  0 |
+----+----+

C
+----+----+
| id |  y |  C.id = A.id
+----+----+
|  1 |  1 |
|  1 |  2 |
+----+----+

我想做什么:从A中选择所有行。对于A中的每一行,所有x中的值为1,所有x的值为0,其中值为B.id = A.id.对于A中的每一行,从C获得最小y,其中C.id = A.id。

我期待的结果是:

+----+------+--------+---------+
| id |  min | count1 | count 2 |
+----+------+--------+---------+
|  1 |    1 |      2 |       1 |
|  2 | NULL |      0 |       0 |
+----+------+--------+---------+

首先尝试: 这不起作用。

SELECT a.id,
       MIN(c.y),
       SUM(IF(b.x = 1, 1, 0)),
       SUM(IF(b.x = 0, 1, 0))
FROM   a
       LEFT JOIN b
              ON ( a.id = b.id )
       LEFT JOIN c
              ON ( a.id = c.id )
GROUP BY a.id

+----+------+--------+---------+
| id |  min | count1 | count 2 |
+----+------+--------+---------+
|  1 |    1 |      4 |       2 |
|  2 | NULL |      0 |       0 |
+----+------+--------+---------+

第二次尝试: 这有效,但我确信它的表现不好。

SELECT a.id,
       MIN(c.y),
       b.x,
       b.y
FROM   a
       LEFT JOIN (SELECT b.id, SUM(IF(b.x = 1, 1, 0)) x, SUM(IF(b.x = 0, 1, 0)) y FROM b) b
              ON ( a.id = b.id )
       LEFT JOIN c
              ON ( a.id = c.id )
GROUP BY a.id

+----+------+--------+---------+
| id |  min | count1 | count 2 |
+----+------+--------+---------+
|  1 |    1 |      2 |       1 |
|  2 | NULL |      0 |       0 |
+----+------+--------+---------+

上次尝试: 这也有效。

SELECT x.*,
       SUM(IF(b.x = 1, 1, 0)),
       SUM(IF(b.x = 0, 1, 0))
FROM   (SELECT a.id,
               MIN(c.y)
        FROM   a
               LEFT JOIN c
                      ON ( a.id = c.id )
        GROUP  BY a.id) x
       LEFT JOIN b
              ON ( b.id = x.id )
GROUP  BY x.id

现在我的问题是:最后一个是最好的选择还是有办法用一个select语句编写这个查询(比如第一次尝试)?

1 个答案:

答案 0 :(得分:3)

您的联接正在为给定值执行笛卡尔积,因为每个表中有多行。

您可以使用count(distinct)而非sum()

来解决此问题
SELECT a.id, MIN(c.y),
       count(distinct (case when b.x = 1 then b.id end)),
       count(distinct (case when b.x = 0 then b.id end))
FROM   a
       LEFT JOIN b
              ON ( a.id = b.id )
       LEFT JOIN c
              ON ( a.id = c.id )
GROUP BY a.id;

您还可以通过预先汇总b(和/或c)来解决此问题。如果您的聚合函数类似于b中列的总和,则需要采用该方法。

编辑:

你是对的。上述查询计算B的不同值,但B包含完全重复的行。 (就个人而言,我认为有一个名称为id的列有重复的列表示设计不佳,但这是另一个问题。)

您可以通过id表中的真实b来解决此问题,因为count(distinct)会计算正确的值。您也可以在加入之前聚合两个表来解决它:

SELECT a.id, c.y, x1, x0
FROM   a
       LEFT JOIN (select b.id,
                         sum(b.x = 1) as x1,
                         sum(b.x = 0) as x0
                  from b
                  group by b.id
                 ) b
              ON ( a.id = b.id )
       LEFT JOIN (select c.id, min(c.y) as y
                  from c
                  group by c.id
                 ) c
              ON ( a.id = c.id );

Here是问题的SQL小提琴。

编辑II:

你可以在一个声明中得到它,但我不确定它是否适用于类似的数据。我们的想法是,您可以计算x = 1的所有情况,然后除以C表中的行数,以获得真正的不同计数:

SELECT a.id, MIN(c.y), 
       coalesce(sum(b.x = 1), 0) / count(distinct coalesce(c.y, -1)), 
       coalesce(sum(b.x = 0), 0) / count(distinct coalesce(c.y, -1))
FROM   a
       LEFT JOIN b
              ON ( a.id = b.id )
       LEFT JOIN c
              ON ( a.id = c.id )
GROUP BY a.id;

这有点棘手,因为你必须处理NULL来获得正确的值。请注意,这会计算y值以从C表中获取不同的计数。您的问题重新强制了为什么在每个表中都有一个唯一的整数主键是个好主意。