【问题标题】:Two column primary keys, auto-increment column depending on value of the 2nd column两列主键,根据第二列的值自动增加列
【发布时间】:2014-05-08 16:18:43
【问题描述】:

我在为 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

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

【问题讨论】:

  • 从技术上讲,您只有一个由两列组成的主键。我知道,不是很有帮助。

标签: sql firebird composite-primary-key


【解决方案1】:

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

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

如果我们只有database_ids 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?

【讨论】:

  • 非常感谢您的回复!..确实没有简单的方法可以做到这一点....所以基本上,最好在应用程序中控制它
  • @t_mo_t 可能是这样,尽管您仍然必须处理并发更新(保证序列提供越来越多的数字,使用SELECT MAX(...) 不能保证由于事务可见性)。您可能需要考虑是否真的需要您的第一个键列依赖于第二个
【解决方案2】:

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

如果您有某种方式知道每个“database_id”的行插入顺序,则可以动态计算。应用于所有行的传统自动增量列为您提供排序。然后可以将计算隐藏在 VIEW 后面,而您的应用程序无需再聪明了。

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

     SELECT ROW_NUMBER() OVER (PARTITION BY "database_id" ORDER BYauto-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

【讨论】:

    猜你喜欢
    • 2018-06-05
    • 2011-07-21
    • 2011-12-01
    • 2015-06-30
    • 2018-08-26
    • 1970-01-01
    • 1970-01-01
    • 2013-03-01
    • 1970-01-01
    相关资源
    最近更新 更多