MySQL 有 3 个表、左联接和计数

MySQL with 3 tables, left joins, and counting

我正在尝试生成一个 table,它将用于创建一个类别菜单,该菜单将指示每个类别下的产品数量。如果某个类别未启用,则该类别将不会在 table 中列出,如果产品未启用,则该产品不会被计为实际类别的一部分。一种产品可以属于多个类别。启用的类别可能有并且应该报告 0 个产品。 “所有”类别是一个派生行(始终被视为启用),它只计算所有启用的产品,而不管与禁用类别的任何关系。产品也可以不属于任何类别 (NULL),因此只会显示在“全部”下。

DROP TABLE IF EXISTS _category;
CREATE TABLE _category (category_id INT(11) PRIMARY KEY AUTO_INCREMENT, name VARCHAR(20), enabled TINYINT(1));
INSERT INTO _category (name, enabled) VALUES ('Canvas', 1);
INSERT INTO _category (name, enabled) VALUES ('Glass', 1);
INSERT INTO _category (name, enabled) VALUES ('Rocks', 1);
INSERT INTO _category (name, enabled) VALUES ('Watercolor', 1);
INSERT INTO _category (name, enabled) VALUES ('Holidays', 1);
INSERT INTO _category (name, enabled) VALUES ('Pencil', 1);
INSERT INTO _category (name, enabled) VALUES ('Sheetmetal', 0);

DROP TABLE IF EXISTS _category_to_product;
CREATE TABLE _category_to_product (product_id INT(11), category_id INT(11) DEFAULT NULL);
INSERT INTO _category_to_product (product_id, category_id) VALUES (1, 1);
INSERT INTO _category_to_product (product_id, category_id) VALUES (1, 5);
INSERT INTO _category_to_product (product_id, category_id) VALUES (2, 1);
INSERT INTO _category_to_product (product_id, category_id) VALUES (3, 1);
INSERT INTO _category_to_product (product_id, category_id) VALUES (3, 2);
INSERT INTO _category_to_product (product_id, category_id) VALUES (3, 5);
INSERT INTO _category_to_product (product_id, category_id) VALUES (4, 4);
INSERT INTO _category_to_product (product_id, category_id) VALUES (5, 1);
INSERT INTO _category_to_product (product_id, category_id) VALUES (6, 2);
INSERT INTO _category_to_product (product_id, category_id) VALUES (6, 5);
INSERT INTO _category_to_product (product_id, category_id) VALUES (7, 4);
INSERT INTO _category_to_product (product_id, category_id) VALUES (8, 7);
INSERT INTO _category_to_product (product_id, category_id) VALUES (9, NULL);

DROP TABLE IF EXISTS _product;
CREATE TABLE _product (product_id INT(11) PRIMARY KEY AUTO_INCREMENT, name VARCHAR(20), enabled TINYINT(1));
INSERT INTO _product (name, enabled) VALUES ("Christmas", 0);
INSERT INTO _product (name, enabled) VALUES ("Spring", 1);
INSERT INTO _product (name, enabled) VALUES ("Halloween", 1);
INSERT INTO _product (name, enabled) VALUES ("Power Cross", 1);
INSERT INTO _product (name, enabled) VALUES ("Ombre", 1);
INSERT INTO _product (name, enabled) VALUES ("Snow", 1);
INSERT INTO _product (name, enabled) VALUES ("Anime", 1);
INSERT INTO _product (name, enabled) VALUES ("Horses", 1);
INSERT INTO _product (name, enabled) VALUES ("Puppies", 1);

我正在寻找这样的结果 table:

category_id | category_name | product_count
 0             All             8
 1             Canvas          3
 2             Glass           2
 5             Holidays        2
 6             Pencil          0
 3             Rocks           0
 4             Watercolor      2

在我寻找如何实现这一点的过程中,我认为最接近的是以下文章:https://mattmazur.com/2017/11/01/counting-in-mysql-when-joins-are-involved/。然而,它并不完全正确,虽然我确实从中学到了一些东西,但我无法完全从中得到我需要的东西……即使它是正确的方向。我也搜遍了SO,如果有什么解决方案可以帮到我,我就不认识了。

编辑 1: 所以我付出了一些额外的努力,在继续通过猜测和脆弱的线索(和运气)蒙混过关之后,我相信我接近解决方案了.我想出的是使用 INNER JOIN,而不是 LEFT JOIN:

SELECT   c.category_id,
         c.name AS category_name,
         c2p.product_id,
         p.name AS product_name
FROM _category c
INNER JOIN (_category_to_product c2p) ON c.category_id = c2p.category_id
INNER JOIN (
      SELECT *
      FROM _product
      WHERE enabled = 1
    ) p ON c2p.product_id = p.product_id
WHERE c.enabled = 1
ORDER BY  c.category_id ASC;

... 给我以下结果:

category_id | category_name | product_id | product_name
1             Canvas          2            Spring
1             Canvas          3            Halloween
1             Canvas          5            Ombre
2             Glass           3            Halloween
2             Glass           6            Snow
4             Watercolor      7            Anime
4             Watercolor      4            Power Cross
5             Holidays        3            Halloween
5             Holidays        6            Snow

如您所见,类别 1 (Canvas) 有 3 个产品,类别 2(玻璃)有 2 个产品,依此类推,正如我上面的预期结果。 (我只包括了 product_idproduct_name 用于我自己的测试目的,所以它不会出现在最终产品中。而且,我已经排除了 product_count 因为我不是但确定如何整合它。)

看来我还有两个步骤要完成。 1) 能够“折叠”此结果(没有 product_idproduct_name),使其看起来像上面我想要的 table(有 product_count),以及 2) 包括“全部”行作为第一行,category_id=0 和 product_count 总计(使用此数据集)8.

编辑 2: 好的,所以我取得了更多进展,因为我有 product_count where count>=1:

SELECT c.category_id,
       c.name AS category_name,
       COUNT(*) AS product_count
FROM _category c
INNER JOIN (_category_to_product c2p) ON c.category_id = c2p.category_id
INNER JOIN (
      SELECT *
      FROM _product
      WHERE enabled = 1
    ) p ON c2p.product_id = p.product_id
WHERE c.enabled = 1
GROUP BY 1
ORDER BY c.name ASC

... 给我以下结果:

category_id | category_name | product_count
1             Canvas          3
2             Glass           2
5             Holidays        2
4             Watercolor      2

我会继续蒙混过关,但是,我想我可能会停滞不前。我不知道如何使用 category_id=0category_name=Allproduct_count=8 获得第一行,我也不知道如何使用 product_count = 0 包含已启用的类别。

编辑 3: 今天一大早,我能够靠近一些,但没有时间更新这个 post。我得到了除“全部”类别之外的所有内容:

SELECT c.category_id AS category_id,
       c.name AS category_name,
       COUNT(p.product_id) AS product_count
FROM _category c
LEFT JOIN (_category_to_product c2p) ON c.category_id = c2p.category_id
LEFT JOIN (_product p) ON c2p.product_id = p.product_id AND p.enabled = 1
WHERE c.enabled = 1
GROUP BY c.category_id
ORDER BY c.name ASC;

...或者我可以爆炸 LEFT JOINs:

SELECT c.category_id AS category_id,
       c.name AS category_name,
       COUNT(p.product_id) AS product_count
FROM _category c
LEFT JOIN (SELECT category_id, product_id
           FROM _category_to_product
          ) c2p ON c.category_id = c2p.category_id
LEFT JOIN (SELECT product_id, enabled
           FROM _product
           WHERE enabled=1
          ) p ON c2p.product_id = p.product_id AND p.enabled = 1
WHERE c.enabled = 1
GROUP BY c.category_id
ORDER BY c.name ASC;

...都给我以下结果:

category_id | category_name | product_count
1             Canvas          3
2             Glass           2
5             Holidays        2
6             Pencil          0
3             Rocks           0
4             Watercolor      2

这是完美的,除了缺少“全部”类别。为此,我认为我需要某种 UNION,但我能够弄清楚如何实现它。

@etsuhisa,您的查询几乎完美无缺,非常感谢您的宝贵时间!然而,“全部”类别显示总共 9 个,但它应该是 8 个 - 正如它所写的,它没有考虑到其中一个产品是 enabled=0。并且...虽然您的两个选项有效(除了全部计数),但它们让我眼球内爆。有什么方法可以调整我上面的“Edit 3”版本以包含“All”类别(不包括禁用产品)?再一次,我问只是因为看起来我通往所需 table 的路径是有效的(我错了吗?如果是这样,你能给我看一个案例吗?),而且它不会让我的眼睛像内爆一样很多!

只需使用UNION ALL输出All的产品数量。

SELECT c.category_id AS category_id,
       MAX(c.name) AS category_name,
       COUNT(p.product_id) AS product_count
FROM _category c
LEFT JOIN (_category_to_product c2p) ON c.category_id = c2p.category_id
LEFT JOIN (_product p) ON c2p.product_id = p.product_id AND p.enabled = 1
WHERE c.enabled = 1
GROUP BY c.category_id
UNION ALL
SELECT 0, 'All', COUNT(*) FROM _product WHERE enabled=1
ORDER BY category_name

DB Fiddle

通过@etsuhisa 的回答和评论的帮助和见解(如果没有这些,我可能会花更长的时间来想出任何合理的解决方案,所以谢谢!),我能够推导出基于 CTE 的解决方案。我添加了第二个 CTE 以准确获取“所有”类别的产品计数。

WITH cte1 AS (
  SELECT
    c.category_id,
    c.name category_name,
    COUNT(c2p.product_id) product_count
  FROM _category c
    LEFT JOIN _category_to_product c2p
    ON c.category_id=c2p.category_id AND
       c2p.product_id IN (SELECT product_id FROM _product WHERE enabled=1)
  WHERE c.enabled=1
  GROUP BY c.category_id
), cte2 as (
  SELECT COUNT(enabled) all_product_count FROM _product WHERE enabled = 1
)
SELECT * FROM (
  SELECT * FROM cte1
  UNION ALL SELECT 0, 'All', SUM(all_product_count) FROM cte2
) w
ORDER BY category_name