Mysql 同一查询中的计数和子计数

Mysql 同一查询中的计数和子计数,mysql,count,Mysql,Count,我有两张桌子:会员、订单 Members: MemberID, DateCreated Orders: OrderID, DateCreated, MemberID 我想找出一个月内新成员的数量,按顺序分组,例如5+、4、3、2、1、0 我有一个查询来计算一个成员的订单数量,但是如何在一个查询中得到这些值呢 SELECT COUNT(o.orderid) AS Purchases FROM members m LEFT JOIN orders o ON o.memberid =

我有两张桌子:会员、订单

Members: MemberID, DateCreated
Orders:  OrderID, DateCreated, MemberID
我想找出一个月内新成员的数量,按顺序分组,例如5+、4、3、2、1、0

我有一个查询来计算一个成员的订单数量,但是如何在一个查询中得到这些值呢

SELECT
  COUNT(o.orderid) AS Purchases
FROM
  members m
  LEFT JOIN orders o ON o.memberid = m.memberid
    AND MONTH(o.DateCreated) = 8
WHERE
  MONTH(m.DateCreated) = 8
GROUP BY
  m.memberid
ORDER BY
  COUNT(o.orderid) DESC

有几种方法可以做到这一点,其中一些可能相当复杂

我会这样做,关注新成员部分而不是计数部分:

  SELECT COUNT(M.MemberID),
         (SELECT COUNT(*) FROM Orders O WHERE O.MemberId = M.MemberId AND O.DateCreated BETWEEN '2010-08-01' AND DATE_ADD('2010-08-01', INTERVAL 1 MONTH)) AS num_orders
    FROM Members M
   WHERE M.DateCreated BETWEEN '2010-08-01' AND DATE_ADD('2010-08-01', INTERVAL 1 MONTH)
GROUP BY num_orders
我使用日期进行查找,因为这样会更快(它可以使用索引,而
MONTH(M.DateCreated)
总是进行完整的表扫描,但如果确实需要某个月的所有订单/成员,则可以将其更改回去)

编辑: 我忘了处理问题的5+部分,所以这里有一个选项:

  SELECT COUNT(M.MemberID),
         (SELECT IF(COUNT(*) >= 5, '5+', COUNT(*)) FROM Orders O WHERE O.MemberId = M.MemberId AND O.DateCreated BETWEEN '2010-08-01' AND DATE_ADD('2010-08-01', INTERVAL 1 MONTH)) AS num_orders
    FROM Members M
   WHERE M.DateCreated BETWEEN '2010-08-01' AND DATE_ADD('2010-08-01', INTERVAL 1 MONTH)
GROUP BY num_orders

您需要在FROM子句中使用子查询,或者在主SELECT语句之前使用一系列WITH语句(如果您的DBMS支持该符号)。您还需要修复您的查询,这样您就不会报告2009年8月加入的人员以及2010年8月加入的人员

更简单的答案 下面的“更难的答案”是经过大量修改的原始查询,我留下它是因为它显示了我是如何得出答案的。下面的答案更简单;它利用了这样一个事实,即如果要计数的列中没有非空值,COUNT(Column)返回0

它使用表BaseCounts来控制应显示哪些聚合:

CREATE TEMP TABLE BaseCounts
(
    NumOrders CHAR(2) NOT NULL PRIMARY KEY
);
INSERT INTO BaseCounts VALUES("0 ");
INSERT INTO BaseCounts VALUES("1 ");
INSERT INTO BaseCounts VALUES("2 ");
INSERT INTO BaseCounts VALUES("3 ");
INSERT INTO BaseCounts VALUES("4 ");
INSERT INTO BaseCounts VALUES("5+");

SELECT B.NumOrders, COUNT(N.MemberID) AS NumNewMembers
  FROM BaseCounts AS B LEFT OUTER JOIN
       (SELECT MemberID, CASE WHEN NumOrders < 5
                         THEN CAST(NumOrders AS CHAR(2))
                         ELSE "5+" END AS NumOrders
          FROM (SELECT M.MemberID, COUNT(O.OrderID) AS NumOrders
                  FROM Members AS M LEFT OUTER JOIN Orders AS O
                    ON M.MemberID = O.MemberID AND
                       YEAR(O.DateCreated) = 2010 AND MONTH(O.DateCreated) = 8
                 WHERE YEAR(M.DateCreated) = 2010 AND MONTH(M.DateCreated) = 8
                 GROUP BY M.MemberID
               ) AS NMO
       ) AS N
    ON B.NumOrders = N.NumOrders
 GROUP BY B.NumOrders
 ORDER BY B.NumOrders;
将该列表称为“NZO”(表示“非零订单”)。请注意,左外部联接会将人员分配到组“1”,即使他们没有下订单-这不是期望的结果

购买0的新成员 这是一个令人讨厌的查询,因为有相关的子查询,但它避免了引用NZO。另一种方法是找到在参考月份加入的成员名单,并从中减去有1个或多个订单的成员名单(NZO)

将该列表称为“WZO”(代表“零阶”)

很明显,NZO和WZO没有共同的成员——工会或工会——所有这些都给出了新成员的名单和他们下的订单数量

六类新成员 倒数第二个查询 将上面的各种比特和片段组合在一起,并在正确的位置获得正确的比特,生成以下查询:

SELECT NumOrders, COUNT(*) AS NumNewMembers
  FROM (SELECT MemberID, CAST(NumOrders AS CHAR(2)) AS NumOrders
          FROM (SELECT M.MemberID, 0 AS NumOrders
                  FROM Members AS M
                 WHERE YEAR(M.DateCreated) = 2010 AND MONTH(M.DateCreated) = 8
                   AND NOT EXISTS (SELECT * FROM Orders AS O
                                    WHERE YEAR(O.DateCreated) = 2010
                                      AND MONTH(O.DateCreated) = 8
                                      AND O.MemberID = M.MemberID
                                  )
               ) AS WZO
        UNION
        SELECT MemberID, CASE WHEN NumOrders < 5
                         THEN CAST(NumOrders AS CHAR(2))
                         ELSE "5+" END AS NumOrders
          FROM (SELECT M.MemberID, COUNT(*) AS NumOrders
                  FROM Members AS M JOIN Orders AS O ON M.MemberID = O.MemberID
                 WHERE YEAR(M.DateCreated) = 2010 AND MONTH(M.DateCreated) = 8
                   AND YEAR(O.DateCreated) = 2010 AND MONTH(O.DateCreated) = 8
                 GROUP BY M.MemberID
               ) AS NZO
       ) AS NMC
 GROUP BY NumOrders
 ORDER BY NumOrders;
如何逐段构建查询的一般方案应该可以帮助您在将来设计自己的查询。特别是,您可以在执行时验证查询的不同部分

您可能会发现,通过创建感兴趣月份的第一天和最后一天,然后运行对这些范围的查询,可以更轻松地计算日期。这也更灵活,因为它可以处理季度、半月或跨越两个月的时段

还要注意的是,如果没有新成员在他们加入的月份下2个订单,那么结果中就不会有行。解决这个问题是可能的——解决这个问题并不容易

处理“没有新会员购买N件商品” 可能有几种方法可以获取缺失项计数为零的行。我倾向于使用的技术是创建一个表,其中包含我想要显示的行,类似于这样-在这里我创建了临时表来保存答案主要部分中每个命名表达式的结果。这是简单答案中显示的基本计数表的变体;该版本不需要NumNewMembers列,而此版本需要

CREATE TEMP TABLE BaseCounts
(
    NumOrders CHAR(2) NOT NULL,
    NumNewMembers DECIMAL(15,0) NOT NULL
);

INSERT INTO BaseCounts VALUES("0 ", 0);
INSERT INTO BaseCounts VALUES("1 ", 0);
INSERT INTO BaseCounts VALUES("2 ", 0);
INSERT INTO BaseCounts VALUES("3 ", 0);
INSERT INTO BaseCounts VALUES("4 ", 0);
INSERT INTO BaseCounts VALUES("5+", 0);

SELECT NumOrders, MAX(NumNewMembers) AS NumNewMembers
  FROM (SELECT * FROM BaseCounts
        UNION
        SELECT NumOrders, COUNT(*) AS NumNewMembers
          FROM NMC
         GROUP BY NumOrders
        )
 GROUP BY NumOrders
 ORDER BY NumOrders;
FROM子句中UNION中的第二个查询是前一个“final”答案,使用临时表作为中间结果

终极查询 当为避免临时表而写入时,查询变为:

SELECT NumOrders, MAX(NumNewMembers)
  FROM (SELECT * FROM BaseCounts
        UNION
        SELECT NumOrders, COUNT(*) AS NumNewMembers
          FROM (SELECT MemberID, CAST(NumOrders AS CHAR(2)) AS NumOrders
                  FROM (SELECT M.MemberID, 0 AS NumOrders
                          FROM Members AS M
                         WHERE YEAR(M.DateCreated) = 2010 AND MONTH(M.DateCreated) = 8
                           AND NOT EXISTS (SELECT * FROM Orders AS O
                                            WHERE YEAR(O.DateCreated) = 2010
                                              AND MONTH(O.DateCreated) = 8
                                              AND O.MemberID = M.MemberID
                                          )
                       ) AS WZO
                UNION
                SELECT MemberID, CASE WHEN NumOrders < 5
                                 THEN CAST(NumOrders AS CHAR(2))
                                 ELSE "5+" END AS NumOrders
                  FROM (SELECT M.MemberID, COUNT(*) AS NumOrders
                          FROM Members AS M JOIN Orders AS O ON M.MemberID = O.MemberID
                         WHERE YEAR(M.DateCreated) = 2010 AND MONTH(M.DateCreated) = 8
                           AND YEAR(O.DateCreated) = 2010 AND MONTH(O.DateCreated) = 8
                         GROUP BY M.MemberID
                       ) AS NZO
               ) AS NMC
         GROUP BY NumOrders
       )
 GROUP BY NumOrders
 ORDER BY NumOrders;
一些DBMS提供了其他可能更方便的方法来创建表值,如BaseCounts表

可以考虑的另一种技术是使用“COUNT(column)”而不是“COUNT(*)”的某种外部联接。使用“COUNT(column)”时,查询仅对“column”的值为非null的行进行计数,因此在“column”中生成null的外部联接将为null生成零的“COUNT(column)”。但是,您仍然需要输出中应该出现的行中的某个位置的引用列表,以便确定数据集中何时缺少某些内容。这是由我的说明中的BaseCounts表提供的

WITH子句 此外,如顶部所述,SQL标准和一些DBMS提供了一个WITH子句,允许您创建命名的中间结果,然后在最终查询中使用(或者,实际上,稍后在WITH子句中使用):

成员 命令
谢谢你,乔纳森。这是很多细节,当我看完这些报告后,我可能会回到这一点。。
SELECT NumOrders, COUNT(*) AS NumNewMembers
  FROM NMC
 GROUP BY NumOrders
 ORDER BY NumOrders;
SELECT NumOrders, COUNT(*) AS NumNewMembers
  FROM (SELECT MemberID, CAST(NumOrders AS CHAR(2)) AS NumOrders
          FROM (SELECT M.MemberID, 0 AS NumOrders
                  FROM Members AS M
                 WHERE YEAR(M.DateCreated) = 2010 AND MONTH(M.DateCreated) = 8
                   AND NOT EXISTS (SELECT * FROM Orders AS O
                                    WHERE YEAR(O.DateCreated) = 2010
                                      AND MONTH(O.DateCreated) = 8
                                      AND O.MemberID = M.MemberID
                                  )
               ) AS WZO
        UNION
        SELECT MemberID, CASE WHEN NumOrders < 5
                         THEN CAST(NumOrders AS CHAR(2))
                         ELSE "5+" END AS NumOrders
          FROM (SELECT M.MemberID, COUNT(*) AS NumOrders
                  FROM Members AS M JOIN Orders AS O ON M.MemberID = O.MemberID
                 WHERE YEAR(M.DateCreated) = 2010 AND MONTH(M.DateCreated) = 8
                   AND YEAR(O.DateCreated) = 2010 AND MONTH(O.DateCreated) = 8
                 GROUP BY M.MemberID
               ) AS NZO
       ) AS NMC
 GROUP BY NumOrders
 ORDER BY NumOrders;
numorders   numnewmembers
CHAR(2)     DECIMAL(15,0)
0           1
1           1
2           1
3           1
4           1
5+          2
CREATE TEMP TABLE BaseCounts
(
    NumOrders CHAR(2) NOT NULL,
    NumNewMembers DECIMAL(15,0) NOT NULL
);

INSERT INTO BaseCounts VALUES("0 ", 0);
INSERT INTO BaseCounts VALUES("1 ", 0);
INSERT INTO BaseCounts VALUES("2 ", 0);
INSERT INTO BaseCounts VALUES("3 ", 0);
INSERT INTO BaseCounts VALUES("4 ", 0);
INSERT INTO BaseCounts VALUES("5+", 0);

SELECT NumOrders, MAX(NumNewMembers) AS NumNewMembers
  FROM (SELECT * FROM BaseCounts
        UNION
        SELECT NumOrders, COUNT(*) AS NumNewMembers
          FROM NMC
         GROUP BY NumOrders
        )
 GROUP BY NumOrders
 ORDER BY NumOrders;
SELECT NumOrders, MAX(NumNewMembers)
  FROM (SELECT * FROM BaseCounts
        UNION
        SELECT NumOrders, COUNT(*) AS NumNewMembers
          FROM (SELECT MemberID, CAST(NumOrders AS CHAR(2)) AS NumOrders
                  FROM (SELECT M.MemberID, 0 AS NumOrders
                          FROM Members AS M
                         WHERE YEAR(M.DateCreated) = 2010 AND MONTH(M.DateCreated) = 8
                           AND NOT EXISTS (SELECT * FROM Orders AS O
                                            WHERE YEAR(O.DateCreated) = 2010
                                              AND MONTH(O.DateCreated) = 8
                                              AND O.MemberID = M.MemberID
                                          )
                       ) AS WZO
                UNION
                SELECT MemberID, CASE WHEN NumOrders < 5
                                 THEN CAST(NumOrders AS CHAR(2))
                                 ELSE "5+" END AS NumOrders
                  FROM (SELECT M.MemberID, COUNT(*) AS NumOrders
                          FROM Members AS M JOIN Orders AS O ON M.MemberID = O.MemberID
                         WHERE YEAR(M.DateCreated) = 2010 AND MONTH(M.DateCreated) = 8
                           AND YEAR(O.DateCreated) = 2010 AND MONTH(O.DateCreated) = 8
                         GROUP BY M.MemberID
                       ) AS NZO
               ) AS NMC
         GROUP BY NumOrders
       )
 GROUP BY NumOrders
 ORDER BY NumOrders;
NumOrders    NumNewMembers
CHAR(2)      DECIMAL(15,0)
0            1
1            1
2            1
3            0
4            2
5+           2
WITH <name1> AS (<query1>),
     <name2>(<named-columns>) AS (<query2>),
     ...
SELECT ... FROM <name1> JOIN <name2> ON ... 
WITH NZO AS (
        SELECT M.MemberID, COUNT(*) AS NumOrders
          FROM Members AS M JOIN Orders AS O ON M.MemberID = O.MemberID
         WHERE YEAR(M.DateCreated) = 2010 AND MONTH(M.DateCreated) = 8
           AND YEAR(O.DateCreated) = 2010 AND MONTH(O.DateCreated) = 8
         GROUP BY M.MemberID),
     WZO AS (
        SELECT M.MemberID, 0 AS NumOrders
          FROM Members AS M
         WHERE YEAR(M.DateCreated) = 2010 AND MONTH(M.DateCreated) = 8
           AND NOT EXISTS (SELECT * FROM Orders AS O
                            WHERE YEAR(O.DateCreated) = 2010
                              AND MONTH(O.DateCreated) = 8
                              AND O.MemberID = M.MemberID
                          )),
     NMC AS (
        SELECT MemberID, CAST(NumOrders AS CHAR(2))
          FROM WZO
        UNION
        SELECT MemberID, CASE WHEN NumOrders < 5
                         THEN CAST(NumOrders AS CHAR(2))
                         ELSE "5+" END AS NumOrders
          FROM NZO),
     NZC AS (
        SELECT NumOrders, COUNT(*) AS NumNewMembers
          FROM NMC
         GROUP BY NumOrders)
SELECT NumOrders, MAX(NumNewMembers)
  FROM (SELECT * FROM NZC
        UNION
        SELECT * FROM BaseCounts
       )
 GROUP BY NumOrders
 ORDER BY NumOrders;
CREATE TABLE Members
(
    MemberID INTEGER NOT NULL PRIMARY KEY,
    DateCreated DATE NOT NULL
);

CREATE TABLE Orders
(
    OrderID INTEGER NOT NULL PRIMARY KEY,
    DateCreated DATE NOT NULL,
    MemberID INTEGER NOT NULL REFERENCES Members
);
INSERT INTO Members VALUES(1, '2009-08-03');
INSERT INTO Members VALUES(2, '2010-08-03');
INSERT INTO Members VALUES(3, '2010-08-05');
INSERT INTO Members VALUES(4, '2010-08-13');
INSERT INTO Members VALUES(5, '2010-08-15');
INSERT INTO Members VALUES(6, '2010-08-23');
INSERT INTO Members VALUES(7, '2010-08-23');
INSERT INTO Members VALUES(8, '2010-08-23');
INSERT INTO Members VALUES(9, '2010-09-03');
INSERT INTO Orders VALUES(11, '2010-08-03', 1);

INSERT INTO Orders VALUES(33, '2010-08-03', 3);

INSERT INTO Orders VALUES(44, '2010-08-05', 4);
INSERT INTO Orders VALUES(45, '2010-08-06', 4);

INSERT INTO Orders VALUES(56, '2010-08-11', 5);
INSERT INTO Orders VALUES(57, '2010-08-13', 5);
INSERT INTO Orders VALUES(58, '2010-08-23', 5);
--For testing 0 members with 3 orders (and 2 with 4 orders), add:
--INSERT INTO Orders VALUES(51, '2010-08-09', 5);

INSERT INTO Orders VALUES(61, '2010-08-05', 6);
INSERT INTO Orders VALUES(62, '2010-08-15', 6);
INSERT INTO Orders VALUES(63, '2010-08-15', 6);
INSERT INTO Orders VALUES(64, '2010-08-25', 6);

INSERT INTO Orders VALUES(71, '2010-08-03', 7);
INSERT INTO Orders VALUES(72, '2010-08-03', 7);
INSERT INTO Orders VALUES(73, '2010-08-03', 7);
INSERT INTO Orders VALUES(74, '2010-08-03', 7);
INSERT INTO Orders VALUES(75, '2010-08-03', 7);

INSERT INTO Orders VALUES(81, '2010-08-03', 8);
INSERT INTO Orders VALUES(82, '2010-08-03', 8);
INSERT INTO Orders VALUES(83, '2010-08-03', 8);
INSERT INTO Orders VALUES(84, '2010-08-03', 8);
INSERT INTO Orders VALUES(85, '2010-08-03', 8);
INSERT INTO Orders VALUES(86, '2010-08-03', 8);

INSERT INTO Orders VALUES(91, '2010-09-03', 9);