简体   繁体   English

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

[英]Two column primary keys, auto-increment column depending on value of the 2nd column

I am having a problem with writing sql for Firebird.我在为 Firebird 编写 sql 时遇到问题。 What i want to achieve:我想要达到的目标:

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

Where the table_id is the auto-increment part and the database_id is the second part.其中 table_id 是自动增量部分, database_id是第二部分。

Basically like this MySQL solution but using Firebird: mysql two column primary key with auto-increment基本上喜欢这个 MySQL 解决方案,但使用 Firebird: mysql two column primary key with auto-increment

How do I create a table and how do I insert into it?如何创建表格以及如何插入表格?

This is not so simple to do in Firebird as it is in MySQL.这在 Firebird 中并不像在 MySQL 中那样简单。 If the number of database_id is known in advance, you can allocate a sequence for each id and use that in a trigger, but this quickly becomes unwieldy for a large number of ids.如果预先知道database_id的数量,您可以为每个 id 分配一个序列并在触发器中使用它,但是这对于大量 id 很快变得笨拙。

The rest of my answer assumes the use of Firebird 2.5 (I have tested it with Firebird 2.5.2 Update 1).我的其余答案假设使用 Firebird 2.5(我已经使用 Firebird 2.5.2 Update 1 对其进行了测试)。

If we only have database_id s 1 and 2, we can create two sequences:如果我们只有database_id s 1 和 2,我们可以创建两个序列:

CREATE SEQUENCE multisequence_1;
CREATE SEQUENCE multisequence_1;

We need an exception when an id is used that has no sequence:当使用没有序列的 id 时,我们需要一个异常:

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

We can then use the following trigger:然后我们可以使用以下触发器:

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

As you can see this will quickly lead to a lot of IF/ELSE statements.如您所见,这将很快导致大量 IF/ELSE 语句。 This can probably be simplified by using EXECUTE STATEMENT and a dynamically generated query for the next sequence value.这可能可以通过使用EXECUTE STATEMENT和对下一个序列值动态生成的查询来简化。 This won't work if you cannot control the number of database_id values (and their sequences) in advance.如果您无法提前控制database_id值(及其序列)的数量,这将不起作用。

You can try to solve this using dynamic queries as shown below.您可以尝试使用动态查询来解决这个问题,如下所示。 This might have its own problems (especially if there is a high volume of inserts) because EXECUTE STATEMENT has some overhead, and it might also lead to problems because of the use of dynamic DDL (eg lock/update-conflicts on the metadata tables).这可能有其自身的问题(尤其是在插入量很大的情况下),因为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

This code is still susceptible to multiple transaction trying to create the same sequence.此代码仍然容易受到试图创建相同序列的多个事务的影响。 Adding a WHEN ANY DO after the statement that (attempted to) create the sequence, might allow you to use the sequence anyway, but it might also lead to spurious errors like lock conflicts.在(试图)创建序列的语句之后添加WHEN ANY DO可能允许您无论如何都使用该序列,但它也可能导致虚假错误,如锁冲突。 Also note that using DDL in EXECUTE STATEMENT is discouraged (see the warning in the documentation ).另请注意,不鼓励在EXECUTE STATEMENT中使用 DDL(请参阅文档中警告)。

Before using this solution in a production situation, I'd strongly suggest to thoroughly test this under load!在生产环境中使用此解决方案之前,我强烈建议在负载下进行彻底测试!

Note that the WITH AUTONOMOUS TRANSACTION clause is technically not necessary for creating the sequence, but it is required to ensure the sequence is also visible to other transactions (and doesn't get deleted if the original transaction is rolled back).请注意, WITH AUTONOMOUS TRANSACTION子句在技术上不是创建序列所必需的,但需要确保序列对其他事务也是可见的(并且如果原始事务回滚则不会被删除)。

Also be aware of the maximum number of sequences (or: generators) in a single Firebird database: +/- 32758, see Firebird Generator Guide: How many generators are available in one database?还要注意单个 Firebird 数据库中的最大序列数(或:生成器):+/- 32758,请参阅Firebird 生成器指南:一个数据库中有多少个生成器可用? . .

The "table_id" field is really a simple row numbering based on record insertion order and partitioned by "database_id". “table_id”字段实际上是一个基于记录插入顺序并由“database_id”分区的简单行编号。 The first "database_id" foo gets a "table_id" of 1, the second foo a 2, the first bar a 1, the second bar a 2, etc.第一个“database_id” foo的“table_id”为 1,第二个foo为 2,第一个bar为 1,第二个bar为 2,依此类推。

This can be dynamically computed, if you have some way of knowing the order of row insertions for each "database_id".如果您有某种方式知道每个“database_id”的行插入顺序,这可以动态计算。 A conventional auto-increment column, applied across all rows, gives you that ordering.应用于所有行的传统自动增量列为您提供这种排序。 Then the computation can be hidden behind a VIEW and your application need be none the wiser.然后计算可以隐藏在 VIEW 后面,您的应用程序不需要更聪明。

Partitioned row numbers are easy to express with SQL window functions, which are supported in Firebird 3 if I'm not mistaken:分区行号很容易用 SQL 窗口函数表达,如果我没记错的话,Firebird 3 支持这些函数:

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

For Firebird 2, you can compute it yourself by asking, for each distinct "database_id", how many rows precede it:对于 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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM