¿Cómo crear un índice único en una columna NULL?

101

Estoy usando SQL Server 2005. Quiero restringir los valores en una columna para que sean únicos, mientras se permiten NULLS.

Mi solución actual implica un índice único en una vista como esta:

CREATE VIEW vw_unq WITH SCHEMABINDING AS
    SELECT Column1
      FROM MyTable
     WHERE Column1 IS NOT NULL

CREATE UNIQUE CLUSTERED INDEX unq_idx ON vw_unq (Column1)

¿Alguna idea mejor?

Nuno G
fuente
16
no hay posibilidad de usar sql 2008? puede crear un índice filtrado usando 'where'
Simon_Weaver
3
No quiso decir único, permitiendo NULL , parece haber querido decir único, pero incluyendo múltiples NULL . De lo contrario, NULL se indexa como cualquier otro valor y la restricción de unicidad funciona como se esperaba, pero no de acuerdo con los estándares SQL, como @pst menciona en un comentario a continuación.
Suncat2000

Respuestas:

26

Estoy bastante seguro de que no puede hacer eso, ya que viola el propósito de los únicos.

Sin embargo, esta persona parece tener un trabajo decente alrededor: http://sqlservercodebook.blogspot.com/2008/04/multiple-null-values-in-unique-index-in.html

willasay what
fuente
2
Parece que el contenido del enlace que proporcionó se copió (parcialmente) sin atribución desde aquí: decipherinfosys.wordpress.com/2007/11/30/…
Tom Juergens
77
No estoy de acuerdo en que "viola el propósito de los únicos": NULL es un valor especial en SQL (similar en muchos aspectos a NaN) y debe tratarse en consecuencia. En realidad, es un error en SQL Server cumplir con varias especificaciones de SQL: aquí hay un enlace para una solicitud de la "implementación correcta" por lo que vale: connect.microsoft.com/SQLServer/feedback/details/299229/… .
5
para referencia en 2008 en adelante, puede hacer CREAR ÍNDICE ÚNICO foo ON dbo.bar (clave) DONDE la clave NO ES NULO;
niico
2
También estoy en desacuerdo con "viola el propósito de los únicos", NULL no es igual a NULL, por lo que debería poder crear un índice único en una columna anulable e insertar múltiples nulos.
Wodzu
105

Con SQL Server 2008, puede crear un índice filtrado: http://msdn.microsoft.com/en-us/library/cc280372.aspx . (Veo que Simon agregó esto como comentario, pero pensé que merecía su propia respuesta ya que el comentario se pasa por alto fácilmente).

Otra opción es un disparador para verificar la unicidad, pero esto podría afectar el rendimiento.

Phil Haselden
fuente
84
create unique index UIX on MyTable (Column1) where Column1 is not null
Jørn Schou-Rode
1
Nota: actualmente, SQL Server Management Studio no parece saber cómo crear dichos índices, por lo que si luego modifica la tabla, se confundirá e intentará
soltarla
3
Parece que Microsoft ha actualizado SSMS para admitir esto. Tengo SSMS 10.50.1617 y en el cuadro de diálogo Propiedades del índice puede seleccionar la página Filtro para editar el filtro. p. ej., "([Column1] IS NOT NULL)"
Phil Haselden
5
Permitir múltiples valores nulos en un índice y filtrar valores nulos de un índice son cosas independientes. El filtrado de un índice en realidad excluye registros del índice, mientras que las otras soluciones transforman el nulo en un valor único útil. Sea consciente de la diferencia.
Suncat2000
Si está utilizando procedimientos almacenados en una tabla con un índice filtrado como ese, asegúrese de que así ANSI_NULLSsea ON, de lo contrario obtendrá un error al intentar insertar datos.
Arne
71

El truco de la columna calculada es ampliamente conocido como "nullbuster"; mis notas le dan crédito a Steve Kass:

CREATE TABLE dupNulls (
pk int identity(1,1) primary key,
X  int NULL,
nullbuster as (case when X is null then pk else 0 end),
CONSTRAINT dupNulls_uqX UNIQUE (X,nullbuster)
)
un día cuando
fuente
Esto parece un truco genial. Curiosamente, la búsqueda de nullbuster no trae demasiadas cosas. Me pregunto si esto también será útil para acelerar las búsquedas, en lugar de una columna calculada de solo 1 y 0 para nulo o no, si el uso de PK le da al índice algo más con lo que trabajar. Ir a probar este fin de semana en una mesa grande y ver.
David Storfer
@DavidStorfer, no puede hacer eso porque podría tener una colisión entre los ID de las dos tablas diferentes.
user393274
Mejora: ISNULL (X, CONVERT (VARCHAR (10), pk))
Faiz
5
@Faiz: La mejora está en el ojo del espectador. Prefiero el aspecto del original.
cuando
@NunoG, esta debería ser la respuesta aceptada, ya que proporciona una buena solución que cumple con sus requisitos, en lugar de simplemente vincular un sitio externo que puede desaparecer.
Frédéric
-3

Estrictamente hablando, una columna única que acepta valores NULL (o un conjunto de columnas) puede ser NULL (o un registro de NULL) solo una vez, ya que tener el mismo valor (y esto incluye NULL) más de una vez obviamente viola la restricción única.

Sin embargo, eso no significa que el concepto de "columnas únicas que aceptan valores NULL" sea válido; para implementarlo realmente en cualquier base de datos relacional solo tenemos que tener en cuenta que este tipo de bases de datos están destinadas a ser normalizadas para que funcionen correctamente, y la normalización generalmente implica la adición de varias tablas adicionales (no entidades) para establecer relaciones entre las entidades .

Trabajemos con un ejemplo básico considerando solo una "columna única que acepta valores NULL", es fácil expandirla a más columnas de este tipo.

Supongamos que tenemos la información representada por una tabla como esta:

create table the_entity_incorrect
(
  id integer,
  uniqnull integer null, /* we want this to be "unique and nullable" */
  primary key (id)
);

Podemos hacerlo separando uniqnull y agregando una segunda tabla para establecer una relación entre los valores uniqnull y the_entity (en lugar de tener uniqnull "dentro" de the_entity):

create table the_entity
(
  id integer,
  primary key(id)
);

create table the_relation
(
  the_entity_id integer not null,
  uniqnull integer not null,

  unique(the_entity_id),
  unique(uniqnull),
  /* primary key can be both or either of the_entity_id or uniqnull */
  primary key (the_entity_id, uniqnull), 
  foreign key (the_entity_id) references the_entity(id)
);

Para asociar un valor de uniqnull a una fila en the_entity, también debemos agregar una fila en the_relation.

Para las filas en the_entity donde no hay valores uniqnull asociados (es decir, para los que pondríamos NULL en the_entity_incorrect) simplemente no agregamos una fila en the_relation.

Tenga en cuenta que los valores de uniqnull serán únicos para toda la relación, y también observe que para cada valor en la_entidad puede haber como máximo un valor en la relación, ya que las claves primaria y externa imponen esto.

Entonces, si un valor de 5 para uniqnull se va a asociar con un id de the_entity de 3, necesitamos:

start transaction;
insert into the_entity (id) values (3); 
insert into the_relation (the_entity_id, uniqnull) values (3, 5);
commit;

Y, si un valor de id de 10 para the_entity no tiene una contraparte uniqnull, solo hacemos:

start transaction;
insert into the_entity (id) values (10); 
commit;

Para desnormalizar esta información y obtener los datos que tendría una tabla como the_entity_incorrect, necesitamos:

select
  id, uniqnull
from
  the_entity left outer join the_relation
on
  the_entity.id = the_relation.the_entity_id
;

El operador "unión externa izquierda" asegura que todas las filas de the_entity aparecerán en el resultado, poniendo NULL en la columna uniqnull cuando no haya columnas coincidentes en the_relation.

Recuerde, cualquier esfuerzo invertido durante algunos días (o semanas o meses) en diseñar una base de datos bien normalizada (y las correspondientes vistas y procedimientos desnormalizantes) le ahorrará años (o décadas) de dolor y recursos desperdiciados.

Roy
fuente
6
Como ya se indicó en el comentario de la respuesta aceptada con cincuenta votos a favor, MS Sql Server debe admitir que tenga múltiples nulos en una columna indexada como única. Es una falla para implementar los estándares SQL no permitirlo. Nulo no es un valor, nulo no es igual a nulo, esa es una regla básica de SQL desde hace años. Entonces, su primera oración es incorrecta y la mayoría de los lectores no se molestarán en seguir leyendo.
Frédéric