在Firebird中以多行更新具有唯一约束/索引的列

时间:2017-11-15 13:20:11

标签: firebird firebird2.5 unique-index

我有一个看起来像这样的表:

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

我得到了这个:

enter image description here

由于应用代码中存在错误,因此缺少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正在被更新声明复制。

2 个答案:

答案 0 :(得分:3)

使用唯一索引(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。

答案 1 :(得分: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中,基于游标的定位变得相当缓慢。