两列主键,根据第二列的值自动递增列

时间:2014-05-08 16:18:43

标签: sql firebird composite-primary-key

我在为Firebird编写sql时遇到问题。我想要实现的目标:

table_id   database_id     other_columns
1          1
2          1
3          1
1          2
2          2

table_id是自动增量部分,database_id是第二部分。

基本上就像这个MySQL解决方案,但使用Firebird: mysql two column primary key with auto-increment

如何创建表格以及如何插入表格?

2 个答案:

答案 0 :(得分:1)

这在Firebird中并不像在MySQL中那么简单。如果预先知道database_id的数量,您可以为每个id分配一个序列并在触发器中使用它,但是对于大量的ID,这很快变得难以处理。

我的其余部分假设使用了Firebird 2.5(我已经使用Firebird 2.5.2 Update 1进行了测试)。

如果我们只有database_id s 1和2,我们可以创建两个序列:

CREATE SEQUENCE multisequence_1;
CREATE SEQUENCE multisequence_1;

当使用没有序列的id时,我们需要一个例外:

CREATE OR ALTER EXCEPTION no_sequence 'No corresponding sequence found';

然后我们可以使用以下触发器:

CREATE OR ALTER TRIGGER multisequence_BI FOR multisequence
   ACTIVE BEFORE INSERT POSITION 0
AS
BEGIN
   IF (NEW.database_id = 1) THEN
       NEW.table_id = NEXT VALUE FOR multisequence_1;
   ELSE IF (NEW.database_id = 2) THEN
       NEW.table_id = NEXT VALUE FOR multisequence_2;
   ELSE 
       EXCEPTION no_sequence;
END

正如您所看到的,这将很快导致许多IF / ELSE语句。这可以通过使用EXECUTE STATEMENT和动态生成的查询来简化下一个序列值。如果您无法提前控制database_id值(及其序列)的数量,则无法工作。

您可以尝试使用动态查询解决此问题,如下所示。这可能有其自身的问题(特别是如果存在大量插入),因为EXECUTE STATEMENT有一些开销,并且由于使用动态DDL(例如锁定/更新冲突),它也可能导致问题元数据表)。

CREATE OR ALTER TRIGGER multisequence_BI FOR multisequence
   ACTIVE BEFORE INSERT POSITION 0
AS
   DECLARE new_id INTEGER;
   DECLARE get_sequence VARCHAR(255);
BEGIN
    get_sequence = 'SELECT NEXT VALUE FOR multisequence_' || NEW.database_id || 
         ' FROM RDB$DATABASE';
    BEGIN
        EXECUTE STATEMENT get_sequence INTO :new_id;
        WHEN SQLCODE -104 DO
        BEGIN
            EXECUTE STATEMENT 
                'CREATE SEQUENCE multisequence_' || NEW.database_id 
                WITH AUTONOMOUS TRANSACTION;
            EXECUTE STATEMENT get_sequence INTO :new_id;
        END
    END
    NEW.table_id = new_id;
END

此代码仍然容易受到尝试创建相同序列的多个事务的影响。在(尝试)创建序列的语句之后添加WHEN ANY DO可能允许您仍然使用序列,但它也可能导致诸如锁定冲突之类的虚假错误。另请注意,不建议在EXECUTE STATEMENT中使用DDL(请参阅warning in the documentation)。

在生产环境中使用此解决方案之前,我强烈建议在负载下对此进行全面测试!

请注意,WITH AUTONOMOUS TRANSACTION子句在技术上不是创建序列所必需的,但是需要确保序列对其他事务也是可见的(如果原始事务被滚动则不会被删除)回)。

还要注意单个Firebird数据库中序列(或:生成器)的最大数量:+/- 32758,请参阅Firebird Generator Guide: How many generators are available in one database?

答案 1 :(得分:0)

“table_id”字段实际上是基于记录插入顺序的简单行编号,并由“database_id”分区。第一个“database_id” foo 的“table_id”为1,第二个 foo 为2,第一个为1,第二个< em> bar a 2等等

如果您知道每个“database_id”的行插入顺序,可以动态计算。应用于所有行的传统自动增量列为您提供排序。然后计算可以隐藏在VIEW后面,你的应用程序不需要更聪明。

使用SQL窗口函数很容易表达分区行号,如果我没弄错的话,Firebird 3支持这些函数:

SELECT ROW_NUMBER() OVER (PARTITION BY "database_id" ORDER BY auto-increment-column ) AS "table_id"

对于Firebird 2,您可以通过询问每个不同的“database_id”,在其前面有多少行来自行计算:

   SELECT COUNT(b.auto_inc_id) + 1 AS table_id,
          a.database_id
     FROM tbl a
LEFT JOIN tbl b
          ON a.database_id = b.database_id AND b.auto_inc_id < a.auto_inc_id
 GROUP BY 2