【问题标题】:Updating column with unique constraint/index in Firebird in multiple rows在多行中更新 Firebird 中具有唯一约束/索引的列
【发布时间】:2017-11-15 13:20:10
【问题描述】:

我有一张如下所示的表格:

CREATE TABLE SCHEDULE (
    RCDNO              INTEGER NOT NULL,
    MASTCONNO          INTEGER NOT NULL,
    ROWNO              INTEGER DEFAULT 0 NOT NULL,
    EDITTIMESTAMP      TIMESTAMP
)
ALTER TABLE SCHEDULE ADD CONSTRAINT PK_SCHEDULE PRIMARY KEY (RCDNO);
CREATE UNIQUE INDEX IDX_SCHEDULE_3 ON SCHEDULE (MASTCONNO, ROWNO);

我可以运行这样的查询:

select mastconno, rowno, rcdno, edittimestamp
from schedule
where mastconno = 12
order by rowno desc

我明白了:

由于应用程序代码中的错误,edittimestamp 的时间部分丢失了,但这在这里无关紧要。尽管如此,这些记录仍按 ROWNO 的条目顺序列出。这张桌子的设计就是为了方便这一点。

我试图做的是这个......

update SCHEDULE
set ROWNO = (ROWNO + 1)
where MASTCONNO = 12

...在准备插入新的 ROWNO=0 记录时,我收到此错误:

attempt to store duplicate value (visible to active transactions) in unique index "IDX_SCHEDULE_3".
Problematic key value is ("MASTCONNO" = 12, "ROWNO" = 3).

顺便说一句,我在 MS SQL Server 中有一个精确的表副本,我在那里没有这个问题。这似乎特定于 Firebird 的工作方式。

然后我尝试了这个,希望 Firebird 以非违规的顺序将来自 IN 谓词的值“提供”给 UPDATE

update SCHEDULE
set ROWNO = (ROWNO + 1)
where MASTCONNO = 12 and 
ROWNO in (select ROWNO from SCHEDULE 
where MASTCONNO = 12 order by ROWNO DESC)

遗憾的是反应和以前一样。更新语句正在复制 ROWNO 3。

【问题讨论】:

  • 你的 firebird 版本是什么?您是否有保证,当您更改表时,没有任何其他应用程序会插入新记录?通常 SQL 用于许多用户
  • 为了记录,我使用的是2.5

标签: firebird firebird2.5 unique-index


【解决方案1】:

使用唯一索引 (MASTCONNO, ROWNO),我已经测试了以下内容:

update SCHEDULE
set ROWNO = (ROWNO + 1)
where MASTCONNO = 12
order by ROWNO DESC

工作正常!

update SCHEDULE
set ROWNO = (ROWNO - 1)
where MASTCONNO = 12
order by ROWNO ASC

也可以正常工作!

非常感谢 Mark Rotteveel。

【讨论】:

    【解决方案2】:

    确实,约束检查和触发器 Firebird 是按每一行运行,而不是按事务或按表运行。

    因此,您必须使用与处理列表和数组的应用程序相同的基于循环的方法。

    您必须使用EXECUTE BLOCK 和正确定向的循环。

    http://www.firebirdsql.org/file/documentation/reference_manuals/fblangref25-en/html/fblangref25-dml-execblock.html

    您获取感兴趣的 MAXimum ID,并将其递增。 然后你取之前的值。 然后上一个...

    EXECUTE BLOCK 
    AS
      DECLARE ID INTEGER;
    BEGIN
      ID = ( SELECT MAX(ROWNO) FROM SCHEDULE WHERE MASTCONNO = 12 );
    
    /* 
      ID = NULL;
      SELECT MAX(ROWNO) FROM SCHEDULE WHERE MASTCONNO = 12 INTO :ID;
      IF (ID IS NULL) raise-some-error-somehow
    */
      While (ID >= 0)
      Begin
        update SCHEDULE set ROWNO = (ID + 1)
           where MASTCONNO = 12 and :ID = ROWNO;
        ID = ID - 1;
      End
    END
    

    危险!

    如果您开始执行此操作时,另一个事务(可能来自在另一台计算机上运行的另一个程序)插入一些带有MASTCONNO = 12 的新行并提交它 - 您有问题。

    Firebird 是多版本服务器,而不是表阻塞服务器,因此服务器中不会​​有任何内容,因为您的程序正在表上工作,所以禁止此插入。然后你有竞争条件,最快的事务会自己提交,而较慢的事务会因违反唯一索引而失败。

    您也可以使用 FOR-SELECT 循环而不是 WHILE 循环。

    https://www.firebirdsql.org/file/documentation/reference_manuals/fblangref25-en/html/fblangref25-psql-coding.html#fblangref25-psql-forselect

    像这样

    EXECUTE BLOCK 
    AS
    BEGIN
      FOR
        SELECT * FROM SCHEDULE 
          WHERE MASTCONNO = 12 
          ORDER BY ROWNO DESCENDING  -- proper loop direction: ordering is key!
          AS CURSOR PTR 
      DO
        UPDATE SCHEDULE SET ROWNO = ROWNO + 1
          WHERE CURRENT OF PTR;
    END
    

    但是,在 Firebird 3 中,基于光标的定位变得相当缓慢。

    【讨论】:

    • 谢谢。至于多个用户试图同时做同一件事的问题,这将是一个非常不寻常的情况,因为所讨论的表是一个“主”,每月只更新一次,并且由一个知道当其他人不使用桌子做任何事情时,请这样做。他将确保对表的独占访问权。
    • IIRC 使用order by rowno descending 进行更新也可以解决问题,我相信它会对行进行有序更新。不过我没试过。
    • 马克,哇!不知道!至少在 Firebird 2.5 中,它也应该可以解决问题。给个答案吧,会比我的好很多。 @nolaspeaker 调查 - firebirdsql.org/file/documentation/reference_manuals/…
    • 带有 order by 的更新看起来很有趣。我现在就测试一下。
    • 我看到@nolaspeaker 已经发布了答案。这只是我脑海中的一个猜测,我还没有时间验证它,幸运的是 nolaspeaker 已经有了。
    猜你喜欢
    • 2017-03-20
    • 2017-10-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-10-26
    • 1970-01-01
    • 2010-11-23
    相关资源
    最近更新 更多