如何计算包括子类别的相关行?

How to count related rows including subcategories?

我在 Postgres 12.3 数据库中有一些 table。

第一个名字是category:

id|template_id|name           |entry_count|is_base_template|can_rename|can_delete|section|userId|parentCategoryId|
--|-----------|---------------|-----------|----------------|----------|----------|-------|------|----------------|
 1|           |Notes          |          0|true            |true      |true      |A      |      |                |
 2|           |ToDo           |          0|true            |true      |true      |A      |      |                |
 3|          1|Notes          |          0|false           |true      |true      |A      |     1|                |
 4|          2|ToDo           |          0|false           |true      |true      |A      |     1|                |
 5|           |Must Do        |          0|false           |          |          |A      |      |               4|
 6|           |Important notes|          0|false           |          |          |A      |      |               3|

第二个 table 称为 entry - 这与手头的问题无关。

还有 link table category_entries_entry:

categoryId|entryId|
----------|-------|
         4|      1|
         5|      5|
         5|      6|
         4|      7|
         3|      8|
         6|      9|

一个类别可以拥有children,如果parentCategoryId不为NULL那么我们正在处理一个child。例如,id = 5 的类别是 id = 4 的子类别。 Children不能有自己的children,所以只能嵌套一层。

我需要计算每个类别的条目数包括 个子类别。

这个请求基本上满足了我的需要。但是没有考虑到用户:

SELECT COALESCE(c."parentCategoryId" , c.id) as cat_id , COUNT(*) as entries_in_cat
FROM category c JOIN
     category_entries_entry r
     ON c.id = r."categoryId" 
WHERE c.is_base_template = false
GROUP BY cat_id;

Returns:

cat_id|entries_in_cat|
------|--------------|
     4|             4|
     3|             2|

category table 也有 userId 并且应该只对给定用户执行计数。值得注意的是,只有根类别具有 userId.

的条目

而且我想列出子类别及其数量另外。因此,给定样本的 期望输出 userId = 1 是:

cat_id|entries_in_cat|
------|--------------|
     5|             2|
     4|             4|
     6|             1|
     3|             2|

这是一个细分:

1) 类别编号 6 是第 3 个类别的子类别,它有 1 个条目,所以结果是正确的。

2) 类别编号3是一个类别(也就是说,它没有parent),它包含1个条目,另一个应该来自第6个子类别,总共2个。您的脚本 returns 1 是错误的。

3) 类别编号 5 是第四类别的子类别,它包含 2 个条目。你的脚本 returns 也是 2 是正确的。

4) 类别编号 4 是一个类别,它有 2 个自己的条目,另外两个来自第 5 个子类别,总共 4 个。您的脚本 returns 2 是错误的。应该 return 4.

我怎样才能做到这一点?

由于过滤仅适用于父类别,您需要先在父类别中应用它:然后您可以获得子类别。拥有父类别和子类别后,您可以将它们加入条目并计算它们。

总之,查询应如下所示:

with
c as (
  SELECT id
  FROM category
  WHERE userId = 1 AND is_base_template = false
),
s as (
  SELECT d.id
  FROM c
  JOIN category d on d.parentCategoryId = c.id
)
SELECT u.id, count(*) as entries_in_cat
FROM (select id from c union select id from s) u
JOIN category_entries_entry r ON u.id = r."categoryId" 
GROUP BY u.id

这完成了 单层 嵌套的工作:

要仅列出根类别,计数包括子类别:

WITH root AS (
   SELECT id AS cat_id, id AS sub_id
   FROM   category
   WHERE  is_base_template = false
   AND    "userId" = 1
   )
SELECT c.cat_id, count(*)::int AS entries_in_cat
FROM  (
   TABLE root
   UNION ALL
   SELECT r.cat_id, c.id
   FROM   root     r
   JOIN   category c ON c."parentCategoryId" = r.cat_id
   ) c
JOIN   category_entries_entry e ON e."categoryId" = c.sub_id
GROUP  BY c.cat_id;

重点是加入 sub_id,但按 cat_id 分组。

要像上面那样列出根类别和子类别另外:

WITH root AS (
   SELECT id AS cat_id, id AS sub_id
   FROM   category
   WHERE  is_base_template = false
   AND    "userId" = 1
   )
, ct AS (
   SELECT c.cat_id, c.sub_id, count(*)::int AS ct
   FROM  (
      TABLE root
      UNION ALL
      SELECT r.cat_id, c.id AS sub_id
      FROM   root     r
      JOIN   category c ON c."parentCategoryId" = r.cat_id
      ) c
   JOIN   category_entries_entry e ON e."categoryId" = c.sub_id
   GROUP  BY c.cat_id, c.sub_id
   )
SELECT cat_id, sum(ct)::int AS entries_in_cat
FROM   ct
GROUP  BY 1

UNION ALL
SELECT sub_id, ct
FROM   ct
WHERE  cat_id <> sub_id;

db<>fiddle here

对于任意数量的嵌套级别,使用递归 CTE。示例:

关于可选的短语法 TABLE parent: