Обменять уникальные индексированные значения столбцов в базе данных

У меня есть таблица базы данных, и одно из полей (а не первичный ключ) имеет уникальный индекс. Теперь я хочу обменять значения под этим столбцом на две строки. Как это можно сделать? Я знаю два хака:

  • Удалите обе строки и снова вставьте их
  • Обновить строки с другим значением и обменять, а затем обновить до фактического значения.

Но я не хочу идти за ними, поскольку они, похоже, не являются подходящим решением проблемы. Может ли кто-нибудь помочь мне?

Ответ 1

Я думаю, вам стоит пойти на решение 2. Нет функции "swap" в любом SQL-варианте, который я знаю.

Если вам нужно делать это регулярно, я предлагаю решение 1, в зависимости от того, как другие части программного обеспечения используют эти данные. Если вы не будете осторожны, у вас могут быть проблемы с блокировкой.

Но одним словом: нет другого решения, кроме тех, которые вы предоставили.

Ответ 2

Магическое слово 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)

Ответ 3

В ответ на ответ Энди Ирвинг

это сработало для меня (на 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))

Ответ 4

Существует еще один подход, который работает с SQL Server: используйте команду temp join в нем в инструкции 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 для этой техники. - Отметить

Ответ 5

Я также считаю, что # 2 - лучший выбор, хотя я бы обязательно обернул его в транзакцию, если что-то пойдет не так, как в середине обновления.

Альтернативой (с тех пор как вы просили) обновлять значения уникального индекса с разными значениями было бы обновление всех остальных значений в строках до других строк. Это означает, что вы можете оставить только уникальные значения индекса, и в итоге вы получите нужные данные. Однако будьте осторожны, если какая-либо другая таблица ссылается на эту таблицу в отношении внешнего ключа, что все отношения в БД остаются неповрежденными.

Ответ 6

Предполагая, что вы знаете 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

Ответ 7

У меня та же проблема. Вот мой предложенный подход в PostgreSQL. В моем случае мой уникальный индекс - это значение последовательности, определяющее явный порядок пользователей в моих строках. Пользователь будет перемешать строки в веб-приложении, а затем отправить изменения.

Я планирую добавить триггер "before". В этом триггере, когда мое уникальное значение индекса обновляется, я посмотрю, будет ли какая-либо другая строка уже содержать мое новое значение. Если это так, я дам им свое старое значение и эффективно украду ценность у них.

Я надеюсь, что PostgreSQL позволит мне сделать это в случайном порядке перед триггером.

Я отправлю сообщение и дам вам знать мой пробег.

Ответ 8

Oracle имеет отложенную проверку целостности, которая решает именно это, но она недоступна ни в SQL Server, ни в MySQL.

Ответ 9

В SQL Server оператор MERGE может обновлять строки, которые обычно ломают UNIQUE KEY/INDEX. (Просто испытал это, потому что мне было любопытно.)

Однако вам нужно будет использовать временную таблицу/переменную для подачи MERGE с необходимыми строками.

Ответ 10

Для Oracle есть опция DEFERRED, но вы должны добавить ее к своему ограничению.

SET CONSTRAINT emp_no_fk_par DEFERRED; 

Чтобы отложить все ограничения, которые откладываются на протяжении всего сеанса, вы можете использовать инструкцию ALTER SESSION SET = DEFERRED.

Source

Ответ 11

Я обычно думаю о значении, которое не имеет никакого индекса в моей таблице. Обычно - для уникальных значений столбцов - это очень просто. Например, для значений столбца "позиция" (информация о порядке нескольких элементов) это 0.

Затем вы можете скопировать значение A в переменную, обновить ее со значением B и затем установить значение B из вашей переменной. Два запроса, но я не знаю лучшего решения.