SQL:与来自另一个 table 的匹配祖先连接(内部连接)

SQL: Join (Inner Join) with matching ancestor from another table

所以我完全无法理解这个 SQL 查询。

我有 table 个以树状结构存在的产品类别。为简化起见,假设有 3 个顶级类别:A、B、C。在它们之上还有一个类别 ('All'),即根类别。没有产品可以分配给这个类别。为了区分不能分配给产品的类别,它们有一个类型 'Abstract',而不是 'Concrete'

每个类别可以有任意数量和深度的子类别。我目前正在将这些与父 ID 一起存储到直接父(邻接列表)。

Categories

Category   Parent    Type
All        None      Abstract
A          All       Concrete
B          All       Concrete
C          All       Concrete
D          A         Concrete
E          D         Concrete
F          B         Concrete
G          F         Concrete
H          C         Concrete
I          C         Concrete

我还有另一个 table 产品带有类别字段。此 table 中出现的唯一类别是顶级类别。 IE。 A、B 或 C。

Products

Part Number       Category
XXXX-XXXX         A
XXXX-YYYY         A
XXXX-ZZZZ         B
YYYY-XXXX         C

我想创建一个连接两个 table 的查询,以创建将类别替换为子类别的行。 IE。从伪代码的角度来看,基本上加入类别 = 前提是类别等于或属于类别。

所以像这样:

select * from products
inner join categories
on products.category = descendent of category

会导致:

Part Number       Category
XXXX-XXXX         E (E's top level concrete parent is A)
XXXX-YYYY         E (E's top level concrete parent is A)
YYYY-XXXX         H (H's top level concrete parent is C)
YYYY-XXXX         I (I's top level concrete parent is C)

我有这个可以检索到顶层的所有具体类型:

with recursive
concrete_parents as (
  select category, parent, type
  from categories
  where category in ('E', 'H', 'I')
  UNION ALL
    select t2.category, t2.parent, t2.type
    from categories as t2
    inner join concrete_parents t1
    on t1.parent = t2.category
    where t2.type = 'Concrete'
)

select distinct * from concrete_parents
order by parent;

我不知道如何将它与主节点上的内部联接结合起来table?

我正在考虑的另一种选择是使用 Postgres ltree,但我对它不是很熟悉。

有什么想法吗?

... would be great to dynamically capture the top level concrete categories.

这似乎是可行的,因为你说:

The only categories that appear in this table (Products) are the top level ones, ie. A, B, or C.

所以那些顶级类别会在最后 JOIN 中自动过滤。由于根据您的示例数据,那些(并且只有那些)具有 parent = 'All',我们可以削减一级递归并使其更快一些,但是:

WITH RECURSIVE parent_cat AS (
   SELECT category AS original, category, parent -- no need for type
   FROM   categories      c
   WHERE  category in ('A', 'D', 'H', 'I')

   UNION ALL
   SELECT pc.original, c.category, c.parent
   FROM   parent_cat pc
   JOIN   categories c ON c.category = pc.parent
   WHERE  pc.parent <> 'All'  -- stop at top level, save 1 recursion
   )
SELECT p.part_number, pc.category, pc.original 
FROM   parent_cat pc
JOIN   products   p USING (category)
WHERE  pc.parent = 'All'      -- redundant, but a bit faster
ORDER  BY pc.original;

此外,无需使用 type = 'Concrete' 进行过滤,因为其他类型已通过连接以及 pc.parent = 'All' 进行了过滤。

db<>fiddle here

顺便说一句,如果性能很关键并且类别变化不大,请考虑 MATERIALIZED VIEW 替换查询中的 rCTE parent_cat - 并实施适当的制度以使其保持最新.

所以我相信这是有效的:

WITH RECURSIVE parent_categories AS (
SELECT category, parent, type, category AS original
FROM categories
WHERE category in ('E', 'H', 'I')

UNION ALL

SELECT cat.category, cat.parent, cat.type, pc.original
FROM categories cat, parent_categories pc
WHERE cat.category = pc.parent
)
SELECT b.part_number, a.category, a.original 
FROM parent_categories a
INNER JOIN products b
ON a.category = b.category
WHERE a.type = 'Concrete' AND a.category IN ('A', 'B', 'C')

Fiddle

我不喜欢它,因为动态捕获顶级具体类别会很棒。尽管在这个系统中它们非常稳定。如果我将 ('E', 'H', 'I') 替换为 ('D', 'H', 'I') 我得到:

part_number category    original
XXXX-XXXX   A           D
XXXX-YYYY   A           D
YYYY-XXXX   C           H
YYYY-XXXX   C           I

或者 ('A', 'D', 'H', 'I') 我得到:

part_number category    original
XXXX-XXXX   A           A
XXXX-YYYY   A           A
XXXX-XXXX   A           D
XXXX-YYYY   A           D
YYYY-XXXX   C           H
YYYY-XXXX   C           I 

我没有严格测试,但它似乎确实给出了我想要的结果。

是否有更优雅的解决方案不需要对查询中的顶级具体类别进行硬编码。