9

He tenido esto aparecido un par de veces en mi carrera, y ninguno de mis compañeros locales parece ser capaz de responderlo. Digamos que tengo una tabla que tiene un campo de "Descripción" que es una clave candidata, excepto que a veces un usuario se detiene a la mitad del proceso. Entonces, para quizás el 25% de los registros, este valor es nulo, pero para todos los que no son NULL, debe ser único.SQL ¿Puedo tener una restricción "condicionalmente única" en una tabla?

Otro ejemplo podría ser una tabla que debe mantener múltiples "versiones" de un registro, y un valor de bit indica cuál es la "activa". Por lo tanto, la "clave candidata" siempre está poblada, pero puede haber tres versiones que sean idénticas (con 0 en el bit activo) y solo una que esté activa (1 en el bit activo).

Tengo métodos alternativos para resolver estos problemas (en el primer caso, aplicar el código de la regla, ya sea en el procedimiento almacenado o la capa de negocios, y en el segundo, llenar una tabla de archivo con un desencadenador y UNION las tablas cuando necesito un historial). No quiero alternativas (a menos que haya soluciones mejores demostrables), me pregunto si algún sabor de SQL puede expresar "singularidad condicional" de esta manera. Estoy usando MS SQL, así que si hay una manera de hacerlo en eso, genial. En su mayoría solo estoy académicamente interesado en el problema.

+2

duplicados Posible: http://stackoverflow.com/questions/866061/conditional-unique-constraint –

+1

¿Qué versión de SQL Server? – gbn

+0

Gracias por el puntero In silico. Definitivamente se aplica a la situación 2. Intenté buscar antes de publicar, pero en realidad no busqué las palabras clave que usé en el título o ¡lo habría encontrado! El enlace que publica no resuelve la situación n. ° 1, que se resuelve con la sugerencia de Tom H. en 2005 y Arthur en 2008. gbn: estoy usando 2005, por lo que no sabía sobre el índice filtros en 2008. –

Respuesta

28

Si está utilizando SQL Server 2008 de un filtro de índice sería tal vez su solución:

http://msdn.microsoft.com/en-us/library/ms188783.aspx

Así es como cumplir un índice único con NULL múltiples valores

CREATE UNIQUE INDEX [IDX_Blah] ON [tblBlah] ([MyCol]) WHERE [MyCol] IS NOT NULL 
+1

Ah - índice basado en función ... Los DBA aborrecen esos –

+0

@ Ponies Pongo mientras que para los DBA es la única solución viable – msarchet

+0

@msarchet: El trabajo está obstaculizando la ayuda de ATM, pero la configuración de la tabla descrita en el OP es no es ideal para mi Pero disfruta peleando con los DBA =) –

0

Gracias por los comentarios, la versión inicial de esta respuesta fue incorrecta.

He aquí un truco usando una columna calculada que permite efectivamente una restricción única anulable en SQL Server:

create table NullAndUnique 
    (
    id int identity, 
    name varchar(50), 
    uniqueName as case 
     when name is null then cast(id as varchar(51)) 
     else name + '_' end, 
    unique(uniqueName) 
    ) 

insert into NullAndUnique default values 
insert into NullAndUnique default values -- Works 
insert into NullAndUnique default values -- not accidentally :) 
insert into NullAndUnique (name) values ('Joel') 
insert into NullAndUnique (name) values ('Joel') -- Boom! 

Se utiliza básicamente el id cuando el name es nulo. El + '_' es para evitar casos en los que el nombre sea numérico, como 1, que podría colisionar con id.

+1

¿De verdad? Desde MSDN: "Las columnas que se usan en un índice único se deben establecer en NOT NULL, porque los valores nulos múltiples se consideran duplicados cuando se crea un índice único". – Arthur

+6

No, SQL Server * NO * permite valores nulos múltiples en una columna cubierta por una restricción única. El estándar SQL especifica que esto debería permitirse (ya que NULL no es igual a sí mismo), pero el SQL Server de Microsoft no ha seguido el estándar en este punto. –

0

El otro método para aplicar las reglas condicionales que una restricción única de índice o verificación no puede manejar es usar un disparador.

1

Oracle does. Una clave completamente nula no está indexada por un árbol B en índice en Oracle, y Oracle usa B índices de árbol para imponer restricciones únicas.

Suponiendo que se quisiera versión columna_id basado en la ACTIVE_FLAG está establecido en 1:

CREATE UNIQUE INDEX idx_versioning_id ON mytable 
    (CASE active_flag WHEN 0 THEN NULL ELSE active_flag END, 
    CASE active_flag WHEN 0 THEN NULL ELSE id_column END); 
2

En el caso de las descripciones que aún no se han completado, no tendría las de la misma tabla que la ultimado descripciones La mesa final tendría un índice único o clave principal en la descripción.

En el caso de activo/inactivo, nuevamente podría tener tablas separadas como lo hizo con una tabla de "archivo" o "historial", pero otra forma posible de hacerlo en MS SQL Server es mediante el uso de una vista indizada:

CREATE TABLE Test_Conditionally_Unique 
(
    my_id INT NOT NULL, 
    active BIT NOT NULL DEFAULT 0 
) 
GO 
CREATE VIEW dbo.Test_Conditionally_Unique_View 
WITH SCHEMABINDING 
AS 
    SELECT 
     my_id 
    FROM 
     dbo.Test_Conditionally_Unique 
    WHERE 
     active = 1 
GO 
CREATE UNIQUE CLUSTERED INDEX IDX1 ON Test_Conditionally_Unique_View (my_id) 
GO 

INSERT INTO dbo.Test_Conditionally_Unique (my_id, active) 
VALUES (1, 0) 
INSERT INTO dbo.Test_Conditionally_Unique (my_id, active) 
VALUES (1, 0) 
INSERT INTO dbo.Test_Conditionally_Unique (my_id, active) 
VALUES (1, 0) 
INSERT INTO dbo.Test_Conditionally_Unique (my_id, active) 
VALUES (1, 1) 
INSERT INTO dbo.Test_Conditionally_Unique (my_id, active) 
VALUES (2, 0) 
INSERT INTO dbo.Test_Conditionally_Unique (my_id, active) 
VALUES (2, 1) 
INSERT INTO dbo.Test_Conditionally_Unique (my_id, active) 
VALUES (2, 1) -- This insert will fail 

podría utilizar este mismo método para las descripciones NULL/Valorado también.

+0

Gracias, esto funciona en 2005, y en realidad estaba jugando con las vistas indexadas hoy en día en un problema diferente. ¡Esta es una gran aplicación de eso! –

0

No soy del todo consciente de su uso previsto o sus tablas, pero podría intentar usar una relación de uno a uno. Divida esta columna única "a veces" en una nueva tabla, cree el índice UNIQUE en esa columna en la nueva tabla y FK vuelva a la tabla original utilizando las tablas originales PK. Solo tiene una fila en esta nueva tabla cuando se supone que existen datos "únicos".

tablas antigua:

TableA 
ID pk 
Col1 sometimes unique 
Col... 

nuevas tablas:

TableA 
ID 
Col... 

TableB 
ID PK, FK to TableA.ID 
Col1 unique index 
Cuestiones relacionadas