如何将这两个左连接合并为一个?

时间:2018-03-11 14:18:45

标签: mysql sql query-optimization

如何合并这两个左连接:http://sqlfiddle.com/#!9/1d2954/69/0

SELECT d.`id`, (adcount + bdcount)
FROM `docs` d

LEFT JOIN 
(
  SELECT da.`doc_id`, COUNT(da.`doc_id`) AS adcount FROM `docs_scod_a` da
  INNER JOIN `scod_a` a ON a.`id` = da.`scod_a_id`
  WHERE a.`ver_a` IN ('AA', 'AB')
  GROUP BY da.`doc_id`
) ad ON ad.`doc_id` = d.`id`

LEFT JOIN 
(
  SELECT db.`doc_id`, COUNT(db.`doc_id`) AS bdcount FROM `docs_scod_b` db
  INNER JOIN `scod_b` b ON b.`id` = db.`scod_b_id`
  WHERE b.`ver_b` IN ('BA', 'BB')
  GROUP BY db.`doc_id`
) bd ON bd.`doc_id` = d.`id`

是一个单一的左连接,只是为了简化它在我的代码中的使用,同时使它慢慢变慢?

2 个答案:

答案 0 :(得分:1)

首先我要强调,你的计算方法是更好的方法。您有两个单独的维度,单独聚合它们通常是进行计算的最有效方法。它也是最具扩展性的方法。

那就是说,你的查询应该等同于这个版本:

SELECT d.id,
       count(distinct a.id),
       count(distinct b.id) 
FROM docs d left join
     docs_scod_a da
     ON da.doc_id = d.id LEFT JOIN
     scod_a a
     ON a.id = da.scod_a_id AND a.ver_a IN ('AA', 'AB') LEFT JOIN
     docs_scod_b db
     ON db.doc_id = d.id LEFT JOIN
     scod_b b
     ON b.id = db.scod_b_id AND b.ver_b IN ('BA', 'BB')
GROUP BY d.id
ORDER BY d.id;

此查询比看起来更昂贵,因为与COUNT(DISTINCT)相比,COUNT()会产生额外的开销。

here是SQL小提琴。

并且,由于LEFT JOIN可以返回NULL值,因此您的查询更准确地写为:

SELECT d.`id`, COALESCE(adcount, 0) + COALESCE(bdcount, 0)

如果您遇到结果问题,这个小改动可能会解决这些问题。

答案 1 :(得分:1)

性能可能是一个大问题,具体取决于每个表的大小。它似乎是一种“膨胀 - 收缩”的情况,因为它首先通过JOIN“膨胀”行数,然后通过GROUP BY“缩小”。下面的表述避免了通货紧缩。

但首先,如果我正确地理解了这个子查询,那么

SELECT  da.`doc_id`, COUNT(da.`doc_id`) AS adcount
    FROM  `docs_scod_a` da
    INNER JOIN  `scod_a` a  ON a.`id` = da.`scod_a_id`
    WHERE  a.`ver_a` IN ('AA', 'AB')
    GROUP BY  da.`doc_id` 

可以改写为

SELECT  `doc_id`, 
        ( SELECT  COUNT(*)
            FROM  `scod_a` 
            WHERE `id` = da.`scod_a_id`
              AND `ver_a` IN ('AA', 'AB') 
        ) AS adcount
    FROM  `docs_scod_a` AS da

如果这是正确的,则整个查询变为

SELECT  d.id, 
        ( SELECT  COUNT(*)
            FROM  docs_scod_a ds
            JOIN  scod_a s  ON s.id = ds.scod_a_id
            WHERE  ds.doc_id = d.id
              AND  s.ver_a IN ('AA', 'AB') 
        ) + 
        ( SELECT  COUNT(*)
            FROM  docs_scod_b ds
            JOIN  scod_b s  ON s.id = ds.scod_b_id
            WHERE  ds.doc_id = d.id
              AND  s.ver_b IN ('BA', 'BB') 
        )
    FROM  docs AS d

哪些需要这些索引:

docs_scod_a:  (doc_id, scod_a_id), (scod_a_id, doc_id)
docs_scod_b:  (doc_id, scod_b_id), (scod_b_id, doc_id)
scod_a:  (ver_a, id)
scod_b:  (ver_b, id)
docs: -- presumably has  PRIMARY KEY(id)

请注意缺少GROUP BY

docs_scod_a闻起来像多对多映射表。我建议您按照提示here

(不需要COALESCE,因为COUNT只会返回零。)

(我不知道我的版本是否比Gordon更好(更快或更好),也不知道我的索引是否有助于他的表达。)