2008-08-03 14 views
47

Ho una tabella di database e uno dei campi (non la chiave primaria) ha un indice univoco su di esso. Ora voglio scambiare i valori sotto questa colonna per due righe. Come si può fare? Due hack che conosco sono:Scambia valori di colonne indicizzate univoche nel database

  1. Eliminare entrambe le file e reinserire loro
  2. Aggiornamento righe con qualche altro valore e swap e quindi aggiornare a valore reale.

Ma io non voglio andare per questi in quanto non sembrano essere la soluzione appropriata al problema. Qualcuno potrebbe darmi una mano?

+0

Rinominare le colonne? – MatBailie

risposta

8

Penso che dovresti optare per la soluzione 2. Non esiste alcuna funzione di 'scambio' in nessuna variante SQL che conosca.

Se è necessario farlo regolarmente, suggerisco la soluzione 1, a seconda di come altre parti del software utilizzano questi dati. Puoi avere problemi di blocco se non stai attento.

Ma in breve: non c'è altra soluzione oltre a quelle fornite.

2

Penso anche che # 2 sia la soluzione migliore, anche se sarei sicuro di avvolgerlo in una transazione nel caso qualcosa vada storto a metà aggiornamento.

Un'alternativa (dato che è stato chiesto) all'aggiornamento dei valori dell'indice univoco con valori diversi sarebbe quella di aggiornare tutti gli altri valori nelle righe a quello dell'altra riga. Ciò significa che puoi lasciare i valori dell'indice univoco da solo e alla fine ti ritroverai con i dati che desideri. Attenzione però, nel caso in cui qualche altra tabella faccia riferimento a questa tabella in una relazione Chiave esterna, che tutte le relazioni nel DB rimangano intatte.

2

Supponendo di conoscere il PK delle due righe che si desidera aggiornare ... Funziona in SQL Server, non può parlare per altri prodotti. SQL è (dovrebbe essere) atomica a livello di istruzione:

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; 

così si andrà da:

cola colb 
------------ 
1  b 
2  a 

a:

cola colb 
------------ 
1  a 
2  b 
+0

Questo non ha funzionato per me in MySQL. –

5

seguito alla risposta di Andy Irving

ha funzionato per me (su SQL Server 2005) in una situazione simile dove ho una chiave composta e I n eed per scambiare un campo che fa parte del vincolo univoco.

chiave: PID, lNum rec1: 10, 0 rec2: 10, 1 REC3: 10, 2

e ho bisogno di scambiare lNum in modo che il risultato sia

chiave: PID, lNum rec1: 10, 1 rec2: 10, 2 REC3: 10, 0

SQL necessaria:

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)) 
+0

Se funziona, è fantastico perché può essere fatto all'interno di una singola transazione – codeulike

2

Ho lo stesso problema.Ecco il mio approccio proposto in PostgreSQL. Nel mio caso, il mio indice univoco è un valore di sequenza, che definisce un ordine utente esplicito sulle mie righe. L'utente mescolerà le righe in una web-app, quindi invierà le modifiche.

Sto pianificando di aggiungere un trigger "prima". In questo trigger, ogni volta che viene aggiornato il mio valore univoco dell'indice, cercherò di verificare se un'altra riga contiene già il mio nuovo valore. Se è così, darò loro il mio vecchio valore, e in effetti ne ruberò il valore.

Spero che PostgreSQL mi consenta di eseguire questo shuffle nel trigger precedente.

Ti posterò e ti farò sapere il mio chilometraggio.

1

Oracle ha un controllo di integrità differito che risolve esattamente questo, ma non è disponibile in SQL Server o MySQL.

3

Esiste un altro approccio che funziona con SQL Server: utilizzare un join tabella temporaneo nell'istruzione UPDATE.

Il problema è causato da avere due file con lo stesso valore allo stesso tempo, ma se si aggiorna entrambe le file in una sola volta (per i loro nuovi, valori unici), non v'è alcuna violazione del vincolo.

pseudo-codice:

-- 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) 

Grazie a Rich H su questa tecnica. - Mark

+1

Potrebbe essere un po 'vecchio questo ma stavo cercando di fare una pagina di' riordino 'per la mia app Silverlight come il cliente voleva ordinare i loro rapporti da un ordine specifico: ho aggiunto una colonna di ordinamento, ma poiché si trattava di una chiave univoca, avevo difficoltà ad aggiornarlo. Ho finito per usare una variabile di tabella, ma il principio è lo stesso (non mi piacciono molto i tavoli temporali per essere onesti!). Grazie per l'idea :) – Charleh

1

In SQL Server, l'istruzione MERGE può aggiornare le righe che normalmente interrompono un KEY/INDICE UNIQUE. (Ho appena provato questo perché ero curioso.)

Tuttavia, dovresti usare una tabella/variabile temporanea per fornire MERGE con le righe necessarie.

20

La parola magica è DEFERRABLE qui:

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; 

RISULTATO:

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) 
+0

Funziona in MySQL? –

+0

@MarcoDemaio Non lo so. Temo di no: dal momento che mysql non consente aggiornamenti da auto-join, suppongo che faccia delle letture sporche. Ma potresti provare ... – wildplasser

+0

pulito, semplice e funziona bene, grazie amico :) – Aldos

1

Per Oracle v'è un'opzione, in differita, ma bisogna aggiungerlo al vincolo.

SET CONSTRAINT emp_no_fk_par DEFERRED; 

di rinviare tutti i vincoli che sono indifferibile durante l'intera sessione, è possibile utilizzare il vincoli ALTER SESSION SET = istruzione differita.

Source

0

Io di solito pensare a un valore che assolutamente nessun indice nella mia tabella potrebbe avere. Solitamente, per valori di colonna univoci, è davvero facile. Ad esempio, per i valori della colonna 'posizione' (informazioni sull'ordine di più elementi) è 0.

Quindi è possibile copiare il valore A in una variabile, aggiornarlo con il valore B e quindi impostare il valore B dalla variabile. Due domande, non conosco nessuna soluzione migliore però.