根据外键计算条目

Counting entries depending on foreign key

我有2个table,我们称它们为T_FATHERT_CHILD,每个父亲可以有多个child,像这样:

T_FATHER
--------------------------
ID - BIGINT, from Generator

T_CHILD
-------------------------------
ID - BIGINT, from Generator
FATHER_ID - BIGINT, Foreign Key

现在我想向 T_CHILD table 添加一个计数器,它从 1 开始并为每个新的 child 添加 1,但不是全局的,而是每个父亲,例如:

ID | FATHER_ID | COUNTER |
--------------------------
1  | 1          | 1      |
--------------------------
2  | 1          | 2      |
--------------------------
3  | 2          | 1      |
--------------------------

我最初的想法是创建一个 before-insert-trigger 来计算给定父亲有多少 child 并为计数器加 1。这应该可以正常工作,除非同时有 2 个插入,这将以相同的计数器结束。很有可能这从未真正发生过 - 但省却总比后悔好。

我不知道是否可以使用发电机,但不要这样认为,因为每个父亲都必须有一台发电机。

我目前的方法是使用上述触发器并向 FATHER_ID + COUNTER 添加一个唯一索引,以便只有一个同时插入通过。我将不得不处理异常 client-side(并重新尝试失败的插入)。

有没有更好的方法直接在 Firebird 中处理这个问题?

PS:两个 table 中的任何一个都不会被删除,所以这不是问题。

即使每个 FATHER_ID 使用生成器,您也不能为此使用它们,因为生成器不是事务安全的。如果您的事务因任何原因回滚,生成器无论如何都会增加,从而导致差距。

如果没有删除,我认为你的唯一约束方法是有效的。不过,我会考虑另一种选择。

您可以决定不这样存储计数器 – 将计数器存储在数据库中通常不是一个好主意。相反,只跟踪广告订单。为此,生成器 可用的,因为每条新记录都将具有更高的值并且 差距无关紧要 。事实上,除了您已经拥有的 ID 之外,您不需要任何东西。选择时确定编号;对于每个 child,你想知道有多少 children 是同一个父亲,但 ID 较低。作为奖励,删除将正常工作。

这是使用嵌套查询的概念证明:

SELECT ID, FATHER_ID,
       (SELECT 1 + COUNT(*)
        FROM T_CHILD AS OTHERS
        WHERE OTHERS.FATHER_ID = C.FATHER_ID
          AND OTHERS.ID < C.ID) AS COUNTER
FROM T_CHILD AS C

还有 window 函数的选项。它必须有一个派生的 table 来计算最终未被选择的任何行:

SELECT * FROM (
  SELECT ID, FATHER_ID, 
         ROW_NUMBER() OVER(PARTITION BY FATHER_ID ORDER BY ID) AS COUNTER
  FROM T_CHILD
  -- Filtering that wouldn't affect COUNTER (e.g. WHERE FATHER_ID ... AND ID < ...)
)
-- Filtering that would affect COUNTER (e.g. WHERE ID > ...)

这两个选项具有完全不同的性能特征。 suitable 哪一个(如果有的话)对您来说取决于您的数据大小和访问模式。

当您尝试使用计算域和 Thijs van Dien 的 Select 解决方案时?

CREATE TABLE T_CHILD(
  ID INTEGER,
  FATHER_ID INTEGER,
  COUNTER COMPUTED BY (
    (SELECT 1 + COUNT(*)
        FROM T_CHILD AS OTHERS
        WHERE OTHERS.FATHER_ID = T_CHILD.FATHER_ID
          AND OTHERS.ID < T_CHILD.ID)
  )
);

在插入期间,您应该直接在该字段内执行 "Select...count + 1"。

但我可能会重新考虑首先添加该字段。感觉像是在需要的时候可以很容易地推导出来的冗余信息。(例如,通过使用 DENSE_RANK http://www.firebirdfaq.org/faq343/