3

我有 2 张桌子,我们称它们为T_FATHERT_CHILD,每个父亲可以有多个孩子,如下所示:

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

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

现在我想在T_CHILD表中添加一个计数器,它以 1 开头并为每个新孩子添加 1,但不是全局的,而是每个父亲,例如:

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

我最初的想法是创建一个插入前触发器,用于计算给定父亲有多少孩子,并为计数器加 1。这应该可以正常工作,除非同时有 2 个插入,这将以相同的计数器结束。这种情况实际上从未发生过的可能性很高——但保存总比后悔好。

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

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

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

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

4

3 回答 3

2

即使有一个生成器,FATHER_ID你也不能为此使用它们,因为生成器不是事务安全的。如果您的事务由于某种原因被回滚,那么生成器无论如何都会增加,从而导致间隙。

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

您可以决定不这样存储计数器——将计数器存储在数据库中通常是个坏主意。相反,只跟踪插入顺序。为此,可以使用生成器因为每条新记录都将具有更高的值,并且间隙无关紧要ID事实上,除了你已经拥有的,你不需要任何东西。选择时确定编号;对于每个孩子,你想知道有多少孩子有同一个父亲但比他低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

还有一个窗口功能的选项。它必须有一个派生表来计算最终未被选中的任何行:

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 > ...)

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

于 2016-12-13T18:46:10.237 回答
1

当您尝试使用计算域和 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)
  )
);
于 2016-12-15T13:18:35.553 回答
0

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

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

于 2016-12-21T07:41:27.300 回答