根据外键计算条目
Counting entries depending on foreign key
我有2个table,我们称它们为T_FATHER
和T_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/)
我有2个table,我们称它们为T_FATHER
和T_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/)