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_id
和 product_name
用于我自己的测试目的,所以它不会出现在最终产品中。而且,我已经排除了 product_count
因为我不是但确定如何整合它。)
看来我还有两个步骤要完成。 1) 能够“折叠”此结果(没有 product_id
和 product_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=0
、category_name=All
和 product_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 JOIN
s:
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
通过@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
我正在尝试生成一个 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_id
和 product_name
用于我自己的测试目的,所以它不会出现在最终产品中。而且,我已经排除了 product_count
因为我不是但确定如何整合它。)
看来我还有两个步骤要完成。 1) 能够“折叠”此结果(没有 product_id
和 product_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=0
、category_name=All
和 product_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 JOIN
s:
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
通过@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