在MySQL查询中合并多个SELECT语句

时间:2014-02-28 03:39:47

标签: mysql sql

除了使用SELECTUNION之外,还有其他方法可以在MySQL查询中合并超过2个UNION ALL语句吗?我已尝试使用UNIONUNION ALL但我的查询加载速度太慢。这是我的问题:

SELECT 
    'AVAILABLE' AS STATUS, 
    count(id_status) as BIL
FROM 
    book_records AS b, book_class AS c
WHERE 
    b.id_book = c.id_book AND 
    id_status IN ( 1 ) AND class_desc =  'NOVEL'
UNION
SELECT 
    'WAITING' AS STATUS, 
     count(id_status) as BIL
FROM 
    book_records AS b, book_class AS c
WHERE 
    b.id_book = c.id_book AND 
    id_status IN ( 2,3,5 ) AND 
    class_desc =  'NOVEL'
UNION
SELECT 
    'DAMAGED' AS STATUS, 
    count(id_status) as BIL
FROM 
    book_records AS b, book_class AS c
WHERE 
    b.id_book = c.id_book AND 
    id_status NOT IN ( 1,2,3,5 ) AND 
    class_desc =  'NOVEL'

结果:

STATUS      BIL
----------------
AVAILABLE   5
WAITING     25
DAMAGED     0

有谁能告诉我如何解决问题?

4 个答案:

答案 0 :(得分:1)

你可以通过一次通过表来获得结果,而不是三遍。

我个人的偏好是返回一个略有不同的结果集,一行有三个聚合:

SELECT SUM(IF(b.id_status IN ( 1 ))) AS `AVAILABLE`
     , SUM(IF(b.id_status IN ( 2,3,5 ))) AS `WAITING`
     , SUM(IF(b.id_status NOT IN ( 1,2,3,5 ))) AS `DAMAGED`
  FROM book_records b
  JOIN book_class c
    ON c.id_book = p.id_book
   AND c.class_desc = 'NOVEL'
 WHERE b.id_status IS NOT NULL

要将结果转换为当前查询返回的三行,我们可以生成您想要返回的三行(内联视图别名为s),然后匹配上面查询的计数(如内联视图别名为t) 。我们将做一个CROSS JOIN(从t只有一行),并使用CASE表达式来挑选哪一行返回哪一行......

SELECT s.status
     , CASE WHEN s.status = 'AVAILABLE' THEN t.count_available
            WHEN s.status = 'WAITING'   THEN t.count_waiting
            WHEN s.status = 'DAMAGED'   THEN t.count_damaged
       END AS BIL
  FROM ( SELECT 'AVAILABLE' AS status, 1 AS seq
          UNION ALL
         SELECT 'WAITING', 2
          UNION ALL
         SELECT 'DAMAGED', 3
       ) s
 CROSS
  JOIN ( SELECT IFNULL(SUM(IF(b.id_status IN ( 1 ))),0) AS count_available
              , IFNULL(SUM(IF(b.id_status IN ( 2,3,5 ))),0) AS count_waiting
              , IFNULL(SUM(IF(b.id_status NOT IN ( 1,2,3,5 ))),0) AS count_damaged
           FROM book_records b
           JOIN book_class c
             ON c.id_book = p.id_book
            AND c.class_desc = 'NOVEL'
          WHERE b.id_status IS NOT NULL
       ) t
 ORDER BY s.seq

(我看不到你的查询是如何运行的,因为它引用了k.id_book但是没有k行源。我会假设这应该是c.id_book;我没有看到b和c之间有任何连接条件,我不认为你的意思是做一个CROSS JOIN。我将假设b和c之间有一对多的关系,book_record可以有零,一个或多个book_class,但你只对特定类的book_record感兴趣。看起来你的每个查询都意味着执行GROUP BY来获取每个状态的计数......)

此外,最佳做法是限定引用多个行源的查询中的所有列引用。

要获得你要返回的结果集,我会做这样的事情,只需一次通过表格:

SELECT CASE WHEN b.id_status IN ( 1 ) THEN 'AVAILABLE'
            WHEN b.id_status IN ( 2,3,5 ) THEN 'WAITING'
            ELSE 'DAMAGED' // any non-NULL status that is not 1,2,3,5
       END AS status
     , COUNT(b.id_status) AS BIL
  FROM book_records b
  JOIN book_class c
    ON c.id_book = p.id_book
   AND c.class_desc = 'NOVEL'
 WHERE b.id_status IS NOT NULL
 GROUP BY status

如果保证id_status为NOT NULL,则可以省略该WHERE子句。

(不言而喻,book_class上的适当索引,包含id_book的前导列,包括book_class,或可能on (book_class, id_book),都会加快联接操作。我我假设id_book已经是book_record的唯一索引。理想情况下,也会覆盖索引ON book_records (id_book,id_status)。)

要获得相同的结果集,您返回的工作要多一些(保证三行,零计数),但它仍然比运行三个查询更有效:

SELECT s.status AS STATUS
     , IFNULL(t.BIL,0) AS BIL
  FROM ( SELECT 'AVAILABLE' AS status, 1 AS seq
          UNION ALL
         SELECT 'WAITING', 2
          UNION ALL
         SELECT 'DAMAGED', 3
       ) s
  LEFT
  JOIN ( SELECT CASE WHEN b.id_status IN ( 1 ) THEN 'AVAILABLE'
                     WHEN b.id_status IN ( 2,3,5 ) THEN 'WAITING'
                     ELSE 'DAMAGED' // any non-NULL status that is not 1,2,3,5
                END AS status
              , COUNT(b.id_status) AS BIL
          FROM book_records b
          JOIN book_class c
            ON c.id_book = p.id_book
           AND c.class_desc = 'NOVEL'
         WHERE b.id_status IS NOT NULL
         GROUP BY status
       ) t
    ON t.status = s.status
 ORDER BY s.seq

答案 1 :(得分:0)

使用union allunion删除重复项,这是不必要的开销:

SELECT 'AVAILABLE' AS STATUS, count(id_status) as BIL
FROM 
book_records AS b, book_class AS c
WHERE p.id_book = k.id_book AND id_status IN ( 1 ) AND class_desc =  'NOVEL'
UNION ALL
SELECT 'WAITING' AS STATUS, count(id_status) as BIL
FROM 
book_records AS b, book_class AS c
WHERE p.id_book = k.id_book AND id_status IN ( 2,3,5 ) AND class_desc =  'NOVEL'
UNION ALL
SELECT 'DAMAGED' AS STATUS, count(id_status) as BIL
FROM 
book_records AS b, book_class AS c
WHERE p.id_book = k.id_book AND id_status NOT IN ( 1,2,3,5 ) AND class_desc =  'NOVEL';

注意:我故意在查询中留下了查询错误(例如不匹配的别名)。这是发布的原始查询。我假设这是将查询标识符从另一种语言翻译成英语的工件。

编辑:

此查询根本不需要union。代替:

select (case when id_status in ( 1 ) then 'Available'
             when id_status in (2, 3, 5) then 'Waiting'
             else 'Damaged'
         end),
        count(*) as cnt
from book_records br join
     book_class bc
     on br.id_book = pc.id_book 
where bc.class_desc = 'NOVEL'
group by (case when id_status in ( 1 ) then 'Available'
               when id_status in (2, 3, 5) then 'Waiting'
               else 'Damaged'
         end);

此查询将使用book_class(class_desc, id_book)book_records(id_book)上的索引运行得更快。

答案 2 :(得分:0)

首先,你不需要UNION来做你正在做的事情。您可以使用SELECT在一个CASE中完成所有操作。您还需要正确分组聚合函数:

SELECT
    CASE
        WHEN id_status = 1 THEN 'AVAILABLE'
        WHEN id_status IN (2, 3, 5) THEN 'WAITING'
        ELSE 'DAMAGED'
    END AS status, count(id_status) as BIL
    FROM book_records b
    LEFT JOIN book_class c ON b.id_book = c.id_book 
    WHERE c.class_desc = 'NOVEL'
    GROUP BY status;

答案 3 :(得分:0)

CREATE INDEX br_idx ON book_records (id_book, id_status);

CREATE INDEX bc_idx ON book_class (id_book, class_desc);