如何使用多重继承检索存储在 SQL 中的属性

How to retrieve the properties stored in SQL with multiple inheritance

我将记录存储在 SQL 中,这些记录表示类似于 C++ 中的多重继承关系。像那样:

CREATE TABLE Classes 
(
    id INTEGER PRIMARY KEY,
    name TEXT NOT NULL
);

CREATE TABLE Inheritance 
(
    class_id INTEGER NOT NULL,
    base_class_id INTEGER NOT NULL,
    FOREIGN KEY (class_id) REFERENCES Classes(id),
    FOREIGN KEY (base_class_id) REFERENCES Classes(id)
);

classes 具有两种类型的属性。 classes 继承了这些属性,但方式不同。为 class 定义的第一种类型 属性 会覆盖 在任何基础 class 中使用的相同 属性 的值es。另一种类型累加值:属性实际上是一组值,每个class继承它的基数class的所有值,加上可以向该集合添加一个额外的(单个)值:

CREATE TABLE OverridableValues
(
    class_id INTEGER PRIMARY KEY,
    value TEXT NOT NULL,
    FOREIGN KEY (class_id) REFERENCES Classes(id)
);

CREATE TABLE AccumulableValues
(
    class_id INTEGER PRIMARY KEY,
    value TEXT NOT NULL,
    FOREIGN KEY (class_id) REFERENCES Classes(id)
);

关于 OverridableValues 的警告:没有在多重继承的不同路径上覆盖相同 属性 的情况。

我正在尝试使用常见的 table 表达式来设计查询,对于给定的 属性 和 class。[=] return value/values 16=]

我尝试使用的方法是从根开始(为简单起见,假设只有一个根 class),然后构建从根到每个路径的树其他 class。问题是如何将有关属性的信息从 parents 传递到 children。例如下面是一个不正确的尝试:

WITH ParentProperty (id, value) AS 
(
    SELECT c.id, a.value
    FROM Classes c
    LEFT JOIN AccumulableValues a
      ON a.class_id = c.id
    WHERE c.id = 1 --This is the root

    UNION ALL

    SELECT i.class_id, IFNULL(a.value, ba.value)
    FROM ParentProperty p
    JOIN Inheritance i
      ON i.base_class_id = p.id
    LEFT JOIN AccumulableValues a
      ON a.class_id = i.class_id
    LEFT JOIN AccumulableValues ba
      ON ba.class_id = i.base_class_id
)
SELECT id, value
FROM ParentProperty;

我觉得我在 CTE 里面还需要一个 UNION ALL,这是不允许的。但如果没有它,我要么错过正确的价值观,要么错过继承的价值观。到目前为止,我未能为这两种类型的属性设计查询。

我正在使用 SQLite 作为我的数据库引擎。

我终于找到了解决办法。我在下面描述它,但仍然欢迎更有效的。

让我们从可累积 属性开始。我的问题是我试图将多个 UNION ALL 添加到单个 CTE 中。我通过添加额外的 CTE 解决了这个问题(参见 AcquiresFrom

WITH AcquiresFrom (class_id, from_class_id, value) AS
(
    SELECT a.class_id, a.class_id, a.value
    FROM AccumulatableValues a

    UNION ALL

    SELECT i.class_id, i.base_class_id, NULL
    FROM Inheritance i
),
ClassProperty (class_id, value) AS
(
    SELECT c.id, NULL
    FROM Classes c
    LEFT JOIN Inheritance i
      ON i.class_id = c.id
    WHERE i.base_class_id IS NULL

    UNION ALL

    SELECT a.class_id, IFNULL(a.value, p.value)
    FROM ClassProperty p
    JOIN AcquiresFrom a
      ON (a.from_class_id = p.class_id AND a.from_class_id != a.class_id) OR
         (a.class_id = p.class_id AND a.class_id = a.from_class_id AND p.value IS NULL)
)
SELECT DISTINCT class_id, value
FROM ClassProperty
WHERE value IS NOT NULL
ORDER BY class_id;

AcquiresFrom表示获取值的方式:class要么引入一个新值(第一个子句),要么继承它(第二个子句)。 ClassProperty 将值从基础 class 逐渐传播到派生。唯一剩下要做的就是消除重复项和 NULL 值(最后一个子句 SELECT DISTINCT / WHERE value IS NOT NULL)。

可重写的 属性 更复杂。

WITH Roots (id, value) AS
(
    SELECT c.id, o.value
    FROM Classes c
    LEFT JOIN Inheritance i
      ON i.class_id = c.id
    LEFT JOIN OverridableValues o
      ON o.class_id = c.id
    WHERE i.base_class_id IS NULL
),
PossibleValues (id, acquired_from_id, value) AS 
(
    SELECT r.id, r.id, r.value
    FROM Roots r

    UNION ALL

    SELECT i.class_id, CASE WHEN o.value IS NULL THEN p.acquired_from_id ELSE i.class_id END, IFNULL(o.value, p.value)
    FROM PossibleValues p
    JOIN Inheritance i
      ON i.base_class_id = p.id
    LEFT JOIN OverridableValues o
      ON o.class_id = i.class_id
),
Split (class_id, base_class_id, direct) AS (
    SELECT i.class_id, i.base_class_id, 1
    FROM Inheritance i
    UNION ALL
    SELECT i.class_id, i.base_class_id, 0
    FROM Inheritance i
),
Ancestors (id, ancestor_id) AS (
    SELECT r.id, NULL
    FROM Roots r

    UNION ALL

    SELECT s.class_id, CASE WHEN s.direct == 1 THEN a.id ELSE a.ancestor_id END
    FROM Ancestors a
    JOIN Split s
      ON s.base_class_id = a.id
)
SELECT DISTINCT p.id, p.value
FROM PossibleValues p
WHERE p.acquired_from_id NOT IN
(
    SELECT a.ancestor_id
    FROM PossibleValues p1
    JOIN PossibleValues p2
      ON p2.id = p1.id
    JOIN Ancestors a
      ON a.id = p1.acquired_from_id AND a.ancestor_id = p2.acquired_from_id
    WHERE p1.id = p.id
);

Roots显然是没有parents的class列表。 PossibleValues CTE propagates/overrides 从根到最终的值 classes,并打破了多个继承循环,使结构成为 tree-like。此查询的结果中存在所有有效的 id/value 对,但也存在一些无效值。这些无效值是那些在其中一个分支上被覆盖的值,但这一事实在另一个分支上是未知的。 acquired_from_id 允许我们重建第一个引入此值的 class 是谁(当两个不同的 class 引入相同的值时,这可能很有用)。

最后剩下的就是解决多重继承带来的歧义。知道 class 和两个可能的值,我们需要知道一个值是否覆盖另一个。这是用 Ancestors 表达式解决的。