我有一个数据库表,其中一个字段(不是主键)上有一个唯一索引。现在我想将此列下的值交换为两行。怎么可能做到这一点?我知道的两个黑客是:
- 删除这两行并重新插入它们。
- 使用其他值更新行并交换,然后更新为实际值。
但我不想选择这些,因为它们似乎不是解决问题的合适方法。谁能帮帮我?
神奇的词在这里是DEFERRABLE:
DROP TABLE ztable CASCADE;
CREATE TABLE ztable
( id integer NOT NULL PRIMARY KEY
, payload varchar
);
INSERT INTO ztable(id,payload) VALUES (1,'one' ), (2,'two' ), (3,'three' );
SELECT * FROM ztable;
-- This works, because there is no constraint
UPDATE ztable t1
SET payload=t2.payload
FROM ztable t2
WHERE t1.id IN (2,3)
AND t2.id IN (2,3)
AND t1.id <> t2.id
;
SELECT * FROM ztable;
ALTER TABLE ztable ADD CONSTRAINT OMG_WTF UNIQUE (payload)
DEFERRABLE INITIALLY DEFERRED
;
-- This should also work, because the constraint
-- is deferred until "commit time"
UPDATE ztable t1
SET payload=t2.payload
FROM ztable t2
WHERE t1.id IN (2,3)
AND t2.id IN (2,3)
AND t1.id <> t2.id
;
SELECT * FROM ztable;
结果:
DROP TABLE
NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "ztable_pkey" for table "ztable"
CREATE TABLE
INSERT 0 3
id | payload
----+---------
1 | one
2 | two
3 | three
(3 rows)
UPDATE 2
id | payload
----+---------
1 | one
2 | three
3 | two
(3 rows)
NOTICE: ALTER TABLE / ADD UNIQUE will create implicit index "omg_wtf" for table "ztable"
ALTER TABLE
UPDATE 2
id | payload
----+---------
1 | one
2 | two
3 | three
(3 rows)
我认为您应该选择解决方案 2。我知道的任何 SQL 变体中都没有“交换”功能。
如果您需要定期执行此操作,我建议使用解决方案 1,具体取决于软件的其他部分如何使用这些数据。如果您不小心,您可能会遇到锁定问题。
但简而言之:除了您提供的解决方案之外,没有其他解决方案。
除了安迪欧文的回答
这对我有用(在 SQL Server 2005 上)在类似的情况下,我有一个复合键,我需要交换一个作为唯一约束一部分的字段。
键:pID,LNUM rec1:10、0 rec2:10、1 rec3:10、2
我需要交换 LNUM 以便结果是
键:pID,LNUM rec1:10、1 rec2:10、2 rec3:10、0
所需的 SQL:
UPDATE DOCDATA
SET LNUM = CASE LNUM
WHEN 0 THEN 1
WHEN 1 THEN 2
WHEN 2 THEN 0
END
WHERE (pID = 10)
AND (LNUM IN (0, 1, 2))
还有另一种适用于 SQL Server 的方法:在 UPDATE 语句中使用临时表连接。
该问题是由同时具有相同值的两行引起的,但是如果您一次更新两行(更新为它们的新的唯一值),则不会违反约束。
伪代码:
-- setup initial data values:
insert into data_table(id, name) values(1, 'A')
insert into data_table(id, name) values(2, 'B')
-- create temp table that matches live table
select top 0 * into #tmp_data_table from data_table
-- insert records to be swapped
insert into #tmp_data_table(id, name) values(1, 'B')
insert into #tmp_data_table(id, name) values(2, 'A')
-- update both rows at once! No index violations!
update data_table set name = #tmp_data_table.name
from data_table join #tmp_data_table on (data_table.id = #tmp_data_table.id)
感谢 Rich H 提供的这项技术。- 标记
假设您知道要更新的两行的 PK... 这适用于 SQL Server,不能代表其他产品。SQL 在语句级别是(应该是)原子的:
CREATE TABLE testing
(
cola int NOT NULL,
colb CHAR(1) NOT NULL
);
CREATE UNIQUE INDEX UIX_testing_a ON testing(colb);
INSERT INTO testing VALUES (1, 'b');
INSERT INTO testing VALUES (2, 'a');
SELECT * FROM testing;
UPDATE testing
SET colb = CASE cola WHEN 1 THEN 'a'
WHEN 2 THEN 'b'
END
WHERE cola IN (1,2);
SELECT * FROM testing;
所以你将从:
cola colb
------------
1 b
2 a
到:
cola colb
------------
1 a
2 b
我也认为#2 是最好的选择,尽管我会确保将它包装在一个事务中,以防在更新过程中出现问题。
用不同的值更新唯一索引值的替代方法(因为你问过)是将行中的所有其他值更新为另一行的值。这样做意味着您可以单独保留唯一索引值,最后,您会得到所需的数据。但请注意,如果其他表在外键关系中引用此表,则数据库中的所有关系都保持不变。
我也有同样的问题。这是我在 PostgreSQL 中提出的方法。在我的例子中,我的唯一索引是一个序列值,在我的行上定义了一个明确的用户顺序。用户将在网络应用程序中随机排列行,然后提交更改。
我打算添加一个“之前”触发器。在那个触发器中,每当我的唯一索引值更新时,我都会查看是否有任何其他行已经保存了我的新值。如果是这样,我会给他们我的旧价值,并有效地窃取他们的价值。
我希望 PostgreSQL 将允许我在之前的触发器中进行这种洗牌。
我会回帖,让你知道我的里程。
在 SQL Server 中,MERGE 语句可以更新通常会破坏 UNIQUE KEY/INDEX 的行。(只是测试了这个,因为我很好奇。)
但是,您必须使用临时表/变量来提供 MERGE w/必要的行。
对于 Oracle,有一个选项 DEFERRED,但您必须将其添加到您的约束中。
SET CONSTRAINT emp_no_fk_par DEFERRED;
要推迟在整个会话期间可推迟的所有约束,您可以使用 ALTER SESSION SET constraints=DEFERRED 语句。
我通常会想到一个我的表中绝对没有索引可以拥有的值。通常 - 对于唯一的列值 - 这真的很容易。例如,对于列“位置”的值(有关几个元素顺序的信息),它是 0。
然后您可以将值 A 复制到变量中,使用值 B 更新它,然后从变量中设置值 B。两个查询,我知道没有更好的解决方案。
Oracle 延迟了完整性检查,正好解决了这个问题,但它在 SQL Server 或 MySQL 中都不可用。
1)切换名称的ID
id student
1 Abbot
2 Doris
3 Emerson
4 Green
5 Jeames
对于样本输入,输出为:
学生证
1 Doris
2 Abbot
3 Green
4 Emerson
5 Jeames
“万一有n行怎么办……”