我如何编写 SQL 查询来计算与其父组件一起销售的组件数量? (Postgres 11/递归 CTE?)
How can I write a SQL query to calculate the quantity of components sold with their parent assemblies? (Postgres 11/recursive CTE?)
我的目标
计算作为其父组件的一部分销售的组件的总和。
我确定这一定是一个常见的用例,但我还没有找到导致我正在寻找的结果的文档。
背景
我是 运行 CentOS 7 上的 Postgres 11。
我有一些表格如下:
CREATE TABLE the_schema.names_categories (
id INTEGER NOT NULL PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
created_at TIMESTAMPTZ DEFAULT now(),
thing_name TEXT NOT NULL,
thing_category TEXT NOT NULL
);
CREATE TABLE the_schema.relator (
id INTEGER NOT NULL PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
created_at TIMESTAMPTZ DEFAULT now(),
parent_name TEXT NOT NULL,
child_name TEXT NOT NULL,
child_quantity INTEGER NOT NULL
);
CREATE TABLE the_schema.sales (
id INTEGER NOT NULL PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
created_at TIMESTAMPTZ DEFAULT now(),
sold_name TEXT NOT NULL,
sold_quantity INTEGER NOT NULL
);
还有这样一个view,主要是将category key和relator.child_name关联起来进行过滤:
CREATE VIEW the_schema.relationships_with_child_catetgory AS (
SELECT
r.parent_name,
r.child_name,
r.child_quantity,
n.thing_category AS child_category
FROM
the_schema.relator r
INNER JOIN
the_schema.names_categories n
ON r.child_name = n.thing_name
);
这些表包含如下数据:
INSERT INTO the_schema.names_categories (thing_name, thing_category)
VALUES ('parent1', 'bundle'), ('child1', 'assembly'), ('subChild1', 'component'), ('subChild2', 'component');
INSERT INTO the_schema.relator (parent_name, child_name, child_quantity)
VALUES ('parent1', 'child1', 1),('child1', 'subChild1', 10), ('child1', 'subChild2', 2);
INSERT INTO the_schema.sales (sold_name, sold_quantity)
VALUES ('parent1', 1), ('parent1', 2);
我需要构建一个查询,根据这些数据,return 将如下所示:
child_name | sum_sold
------------+----------
subChild1 | 30
subChild2 | 6
(2 rows)
问题是我一开始并不知道如何解决这个问题,事实上,随着我的输入,它变得越来越可怕。我真的很难想象需要建立的联系,所以很难以合乎逻辑的方式开始。
通常,Molinaro 的 SQL Cookbook 有一些入门知识,它确实有一个关于分层查询的部分,但据我所知,其中有 none服务于这个特殊目的。
根据我对该站点的研究,我似乎可能需要使用递归 CTE /Common Table 表达式,如 this question/answer 中所示,但我很难理解这个方法以及如何在我的案例中使用它。
参考上面链接的 E. Brandstetter 的回答中的示例,我得出:
WITH RECURSIVE cte AS (
SELECT
s.sold_name,
r.child_name,
s.sold_quantity AS total
FROM
the_schema.sales s
INNER JOIN
the_schema.relationships_with_child_catetgory r
ON s.sold_name = r.parent_name
UNION ALL
SELECT
c.sold_name,
r.child_name,
(c.total * r.child_quantity)
FROM
cte c
INNER JOIN
the_schema.relationships_with_child_catetgory r
ON r.parent_name = c.child_name
) SELECT * FROM cte
其中一部分:
sold_name | child_name | total
-----------+------------+-------
parent1 | child1 | 1
parent1 | child1 | 2
parent1 | subChild1 | 10
parent1 | subChild1 | 20
parent1 | subChild2 | 2
parent1 | subChild2 | 4
(6 rows)
但是,这些结果包括不需要的行(前两个),当我尝试通过向两个部分添加 where r.child_category = 'component'
来过滤 CTE 时,查询 returns no rows:
sold_name | child_name | total
-----------+------------+-------
(0 rows)
并且当我尝试 group/aggregate 时,出现以下错误:
ERROR: aggregate functions are not allowed in a recursive query's recursive term
我一直在思考如何过滤掉不需要的行并进行聚合;显然我无法理解这个递归 CTE 是如何工作的。感谢所有指导!
好吧,我发现 CTE 可以用作子查询,它允许我需要的过滤和聚合:
SELECT
cte.child_name,
sum(cte.total)
FROM
(
WITH RECURSIVE cte AS (
SELECT
s.sold_name,
r.child_name,
s.sold_quantity AS total
FROM
the_schema.sales s
INNER JOIN
the_schema.relationships_with_child_catetgory r
ON s.sold_name = r.parent_name
UNION ALL
SELECT
c.sold_name,
r.child_name,
(c.total * r.child_quantity)
FROM
cte c
INNER JOIN
the_schema.relationships_with_child_catetgory r
ON r.parent_name = c.child_name
) SELECT * FROM cte ) AS cte
INNER JOIN
the_schema.relationships_with_child_catetgory r1
ON cte.child_name = r1.child_name
WHERE r1.child_category = 'component'
GROUP BY cte.child_name
;
它给出了所需的行:
child_name | sum
------------+-----
subChild2 | 6
subChild1 | 30
(2 rows)
对于手头的实际案例来说这很好而且可能足够了——但我怀疑有更清晰的方法来解决这个问题,所以我会渴望阅读所有其他提供的答案。
基本上你已经有了解决方案。如果您在 CTE 中也存储了数量和类别,您可以在之后简单地添加一个 WHERE
过滤器和一个 SUM
聚合:
SELECT
child_name,
SUM(sold_quantity * child_quantity)
FROM cte
WHERE category = 'component'
GROUP BY child_name
我的整个查询如下所示(仅在我上面提到的细节上与您的不同):
WITH RECURSIVE cte AS (
SELECT
s.sold_name,
s.sold_quantity,
r.child_name,
r.child_quantity,
nc.thing_category as category
FROM
sales s
JOIN relator r
ON s.sold_name = r.parent_name
JOIN names_categories nc
ON r.child_name = nc.thing_name
UNION ALL
SELECT
cte.sold_name,
cte.sold_quantity,
r.child_name,
r.child_quantity,
nc.thing_category
FROM cte
JOIN relator r ON cte.child_name = r.parent_name
JOIN names_categories nc
ON r.child_name = nc.thing_name
)
SELECT
child_name,
SUM(sold_quantity * child_quantity)
FROM cte
WHERE category = 'component'
GROUP BY child_name
注意:我没有使用您的视图,因为我发现直接从表中获取数据比连接已有的数据更方便。但这正是我个人喜欢的方式:)
我的目标
计算作为其父组件的一部分销售的组件的总和。
我确定这一定是一个常见的用例,但我还没有找到导致我正在寻找的结果的文档。
背景
我是 运行 CentOS 7 上的 Postgres 11。 我有一些表格如下:
CREATE TABLE the_schema.names_categories (
id INTEGER NOT NULL PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
created_at TIMESTAMPTZ DEFAULT now(),
thing_name TEXT NOT NULL,
thing_category TEXT NOT NULL
);
CREATE TABLE the_schema.relator (
id INTEGER NOT NULL PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
created_at TIMESTAMPTZ DEFAULT now(),
parent_name TEXT NOT NULL,
child_name TEXT NOT NULL,
child_quantity INTEGER NOT NULL
);
CREATE TABLE the_schema.sales (
id INTEGER NOT NULL PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
created_at TIMESTAMPTZ DEFAULT now(),
sold_name TEXT NOT NULL,
sold_quantity INTEGER NOT NULL
);
还有这样一个view,主要是将category key和relator.child_name关联起来进行过滤:
CREATE VIEW the_schema.relationships_with_child_catetgory AS (
SELECT
r.parent_name,
r.child_name,
r.child_quantity,
n.thing_category AS child_category
FROM
the_schema.relator r
INNER JOIN
the_schema.names_categories n
ON r.child_name = n.thing_name
);
这些表包含如下数据:
INSERT INTO the_schema.names_categories (thing_name, thing_category)
VALUES ('parent1', 'bundle'), ('child1', 'assembly'), ('subChild1', 'component'), ('subChild2', 'component');
INSERT INTO the_schema.relator (parent_name, child_name, child_quantity)
VALUES ('parent1', 'child1', 1),('child1', 'subChild1', 10), ('child1', 'subChild2', 2);
INSERT INTO the_schema.sales (sold_name, sold_quantity)
VALUES ('parent1', 1), ('parent1', 2);
我需要构建一个查询,根据这些数据,return 将如下所示:
child_name | sum_sold
------------+----------
subChild1 | 30
subChild2 | 6
(2 rows)
问题是我一开始并不知道如何解决这个问题,事实上,随着我的输入,它变得越来越可怕。我真的很难想象需要建立的联系,所以很难以合乎逻辑的方式开始。 通常,Molinaro 的 SQL Cookbook 有一些入门知识,它确实有一个关于分层查询的部分,但据我所知,其中有 none服务于这个特殊目的。
根据我对该站点的研究,我似乎可能需要使用递归 CTE /Common Table 表达式,如 this question/answer 中所示,但我很难理解这个方法以及如何在我的案例中使用它。
参考上面链接的 E. Brandstetter 的回答中的示例,我得出:
WITH RECURSIVE cte AS (
SELECT
s.sold_name,
r.child_name,
s.sold_quantity AS total
FROM
the_schema.sales s
INNER JOIN
the_schema.relationships_with_child_catetgory r
ON s.sold_name = r.parent_name
UNION ALL
SELECT
c.sold_name,
r.child_name,
(c.total * r.child_quantity)
FROM
cte c
INNER JOIN
the_schema.relationships_with_child_catetgory r
ON r.parent_name = c.child_name
) SELECT * FROM cte
其中一部分:
sold_name | child_name | total
-----------+------------+-------
parent1 | child1 | 1
parent1 | child1 | 2
parent1 | subChild1 | 10
parent1 | subChild1 | 20
parent1 | subChild2 | 2
parent1 | subChild2 | 4
(6 rows)
但是,这些结果包括不需要的行(前两个),当我尝试通过向两个部分添加 where r.child_category = 'component'
来过滤 CTE 时,查询 returns no rows:
sold_name | child_name | total
-----------+------------+-------
(0 rows)
并且当我尝试 group/aggregate 时,出现以下错误:
ERROR: aggregate functions are not allowed in a recursive query's recursive term
我一直在思考如何过滤掉不需要的行并进行聚合;显然我无法理解这个递归 CTE 是如何工作的。感谢所有指导!
好吧,我发现 CTE 可以用作子查询,它允许我需要的过滤和聚合:
SELECT
cte.child_name,
sum(cte.total)
FROM
(
WITH RECURSIVE cte AS (
SELECT
s.sold_name,
r.child_name,
s.sold_quantity AS total
FROM
the_schema.sales s
INNER JOIN
the_schema.relationships_with_child_catetgory r
ON s.sold_name = r.parent_name
UNION ALL
SELECT
c.sold_name,
r.child_name,
(c.total * r.child_quantity)
FROM
cte c
INNER JOIN
the_schema.relationships_with_child_catetgory r
ON r.parent_name = c.child_name
) SELECT * FROM cte ) AS cte
INNER JOIN
the_schema.relationships_with_child_catetgory r1
ON cte.child_name = r1.child_name
WHERE r1.child_category = 'component'
GROUP BY cte.child_name
;
它给出了所需的行:
child_name | sum
------------+-----
subChild2 | 6
subChild1 | 30
(2 rows)
对于手头的实际案例来说这很好而且可能足够了——但我怀疑有更清晰的方法来解决这个问题,所以我会渴望阅读所有其他提供的答案。
基本上你已经有了解决方案。如果您在 CTE 中也存储了数量和类别,您可以在之后简单地添加一个 WHERE
过滤器和一个 SUM
聚合:
SELECT
child_name,
SUM(sold_quantity * child_quantity)
FROM cte
WHERE category = 'component'
GROUP BY child_name
我的整个查询如下所示(仅在我上面提到的细节上与您的不同):
WITH RECURSIVE cte AS (
SELECT
s.sold_name,
s.sold_quantity,
r.child_name,
r.child_quantity,
nc.thing_category as category
FROM
sales s
JOIN relator r
ON s.sold_name = r.parent_name
JOIN names_categories nc
ON r.child_name = nc.thing_name
UNION ALL
SELECT
cte.sold_name,
cte.sold_quantity,
r.child_name,
r.child_quantity,
nc.thing_category
FROM cte
JOIN relator r ON cte.child_name = r.parent_name
JOIN names_categories nc
ON r.child_name = nc.thing_name
)
SELECT
child_name,
SUM(sold_quantity * child_quantity)
FROM cte
WHERE category = 'component'
GROUP BY child_name
注意:我没有使用您的视图,因为我发现直接从表中获取数据比连接已有的数据更方便。但这正是我个人喜欢的方式:)