2008-08-03 12 views
47

Tengo una tabla de base de datos y uno de los campos (no la clave principal) tiene un índice único en él. Ahora quiero intercambiar valores debajo de esta columna por dos filas. ¿Como se puede hacer esto? Dos cortes que conozco son:Intercambiar valores únicos de columnas indexadas en la base de datos

  1. Eliminar ambas filas y colocarlo de nuevo
  2. filas Actualizar con algún otro valor y de intercambio y luego actualizar a valor real.

Pero no quiero ir por estos ya que no parecen ser la solución adecuada al problema. ¿Alguien podría ayudarme?

+0

¿Cambiar el nombre de las columnas? – MatBailie

Respuesta

8

Creo que deberías ir a la solución 2. No hay función de 'intercambio' en ninguna variante SQL que conozca.

Si necesita hacer esto regularmente, sugiero la solución 1, dependiendo de cómo otras partes del software estén usando esta información. Puede tener problemas de bloqueo si no tiene cuidado.

Pero en resumen: no hay otra solución que las que usted proporcionó.

2

También creo que el # 2 es la mejor opción, aunque me aseguraré de envolverlo en una transacción en caso de que algo salga mal a mediados de la actualización.

Una alternativa (ya que pidió) para actualizar los valores del Índice único con diferentes valores sería actualizar todos los demás valores en las filas a la de la otra fila. Hacer esto significa que puede dejar los valores del Índice único solo, y al final, termina con los datos que desea. Sin embargo, tenga cuidado, en caso de que alguna otra tabla haga referencia a esta tabla en una relación de clave externa, que todas las relaciones en la base de datos permanezcan intactas.

2

Suponiendo que conoce el PK de las dos filas que desea actualizar ... Esto funciona en SQL Server, no se puede hablar de otros productos. SQL está (se supone que) atómica a nivel de estado:

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; 

por lo que irá desde:

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

a:

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

Esto no funcionó para mí en MySQL. –

5

con la respuesta de Andy Irving

esto funcionó para mí (en SQL Server 2005) en una situación similar donde tengo una clave compuesta e I n para intercambiar un campo que es parte de la restricción única.

clave: Pid, ​​LNUM REC1: 10, 0 REC2: 10, 1 REC3: 10, 2

y necesito para intercambiar LNUM de modo que el resultado es

clave: Pid, LNUM REC1: 10, 1 REC2: 10, 2 REC3: 10, 0

el SQL necesario:

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

Si esto funciona, es genial porque se puede hacer dentro de una sola transacción – codeulike

2

Tengo el mismo problema.Aquí está mi enfoque propuesto en PostgreSQL. En mi caso, mi índice único es un valor de secuencia, que define un orden de usuario explícito en mis filas. El usuario barajará filas en una aplicación web y luego enviará los cambios.

Estoy planeando agregar un disparador "antes". En ese disparador, cada vez que se actualice mi valor de índice único, veré si hay alguna otra fila que ya tenga mi nuevo valor. Si es así, les daré mi valor anterior y les quitaré el valor de manera efectiva.

Espero que PostgreSQL me permita hacer esta reorganización en el disparador anterior.

Voy a publicar de nuevo y hacerle saber mi kilometraje.

1

Oracle ha pospuesto la verificación de integridad que resuelve exactamente esto, pero no está disponible en SQL Server o MySQL.

3

Hay otro enfoque que funciona con SQL Server: use una tabla temporal para unirse a él en su instrucción UPDATE.

El problema se debe a que tiene dos filas con el mismo valor al mismo tiempo, pero si actualiza ambas filas a la vez (a sus nuevos valores únicos), no hay ninguna violación de restricción.

Pseudo-código:

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

Gracias a Rich H para esta técnica. - Marca

+1

Podría ser un poco viejo pero traté de hacer una página de "reordenamiento" para mi aplicación Silverlight ya que el cliente quería ordenar sus informes por un orden específico: agregué una columna de ordenación pero dado que es una clave única, tuve problemas para actualizarla. Terminé usando una variable de tabla, pero el principio es el mismo (no me gustan mucho las tablas temporales para ser sincero). Gracias por la idea :) – Charleh

1

En SQL Server, la instrucción MERGE puede actualizar las filas que normalmente romperían una clave/índice ÚNICO. (Acabo de probar esto porque tenía curiosidad.)

Sin embargo, tendría que usar una tabla/variable temporal para suministrar MERGE con las filas necesarias.

20

La palabra mágica es DEFERRABLE aquí:

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; 

RESULTADO:

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

¿Funciona esto en MySQL? –

+0

@MarcoDemaio No lo sé. Me temo que no: dado que mysql no permite actualizaciones de autouniones, supongo que hace lecturas sucias. Pero podrías intentar ... – wildplasser

+0

limpio, simple y funciona bien, gracias hombre :) – Aldos

1

Para Oracle no es una opción, diferido, pero hay que añadirlo a su restricción.

SET CONSTRAINT emp_no_fk_par DEFERRED; 

Aplazar todas las restricciones que son diferible durante toda la sesión, puede utilizar las limitaciones ALTER SESSION SET = instrucción diferida.

Source

0

generalmente pienso en un valor que absolutamente ningún índice en mi mesa podría tener. Por lo general, para valores de columna únicos, es realmente fácil. Por ejemplo, para valores de columna 'posición' (información sobre el orden de varios elementos) es 0.

Luego puede copiar el valor A en una variable, actualizarlo con el valor B y luego establecer el valor B en su variable. Dos consultas, sin embargo, no conozco una mejor solución.

Cuestiones relacionadas