我有2个表,让我们称之为T_FATHER
和T_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 |
--------------------------
我最初的想法是创建一个before-insert-trigger,它计算给定父亲有多少个孩子,并为计数器加1。这应该可以正常工作,除非同时有2个插入,这将以相同的计数器结束。实际上从未发生过这种情况的可能性很高 - 但是比对不起更好。
我不知道是否可以使用发电机,但不要这么想,因为每个父亲必须有一台发电机。
我当前的方法是使用上述触发器并为FATHER_ID
+ COUNTER
添加唯一索引,以便只有一个同时插入通过。我将不得不处理客户端异常(并重新尝试失败的插入)。
有没有更好的方法直接在Firebird中处理这个?
PS:两张桌子中的任何一张都没有删除,所以这不是问题。
答案 0 :(得分: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 > ...)
这两个选项具有完全不同的性能特征。哪一个(如果有的话)适合您,取决于您的数据大小和访问模式。
答案 1 :(得分: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)
)
);
答案 2 :(得分:0)
在插入过程中,你应该做一个&#34;选择...计数+ 1&#34;直接在那个领域内。
但我可能会重新考虑首先添加该字段。感觉就像您需要时可以轻松推断的冗余信息。(例如,使用DENSE_RANK http://www.firebirdfaq.org/faq343/)