¿Por qué una restricción ÚNICA permite solo un NULL?


36

Técnicamente, NULL = NULL es falso, por esa lógica no NULL es igual a ningún NULL y todos los NULL son distintos. ¿No debería implicar que todos los NULL son únicos y que un índice único debería permitir cualquier número de NULL?


Los comentarios no son para discusión extendida; Esta conversación se ha movido al chat .
Paul White dice GoFundMonica

Respuestas:


52

¿Por qué funciona de esta manera? Porque hace mucho tiempo, alguien tomó una decisión de diseño sin saber o preocuparse por lo que dice el estándar (después de todo, tenemos todo tipo de comportamientos extraños con NULLs, y podemos forzar diferentes comportamientos a voluntad). Esa decisión dicta que, en este caso NULL = NULL,.

No fue una decisión muy inteligente. Lo que deberían haber hecho es que el comportamiento predeterminado se adhiera al estándar ANSI, y si realmente querían este comportamiento peculiar, permitirlo a través de una opción DDL como WITH CONSIDER_NULLS_EQUALo WITH ALLOW_ONLY_ONE_NULL.

Por supuesto, la retrospectiva es 20/20.

Y tenemos una solución, ahora, de todos modos, incluso si no es la más limpia o más intuitiva.

Puede obtener el comportamiento ANSI adecuado en SQL Server 2008 y superior creando un índice único y filtrado.

CREATE UNIQUE INDEX foo ON dbo.bar(key) WHERE key IS NOT NULL;

Esto permite más de un NULLvalor porque esas filas quedan completamente fuera de la verificación duplicada. Como una ventaja adicional, esto terminaría siendo un índice más pequeño que uno que consistía en la tabla completa si NULLse permitieran múltiples s (especialmente cuando no es la única columna en el índice, tiene INCLUDEcolumnas, etc.). Sin embargo, es posible que desee conocer algunas de las otras limitaciones de los índices filtrados:


8

Correcto. La implementación de una restricción o índice único en el servidor sql permite uno y solo un NULL. También corrija que esto técnicamente no encaja con la definición de NULL, pero es una de esas cosas que hicieron para hacerlo más útil a pesar de que no es "técnicamente" correcto. Tenga en cuenta que una CLAVE PRIMARIA (también un índice único) no permite NULL (por supuesto).


1
Este tecnicismo (del servidor SQL) tampoco se ajusta al estándar SQL. Hay un elemento de Connect de 7 años sobre este problema.
ypercubeᵀᴹ

@ypercube Cierto. Es por eso que dije que era solo la implementación y que realmente no encaja con la definición de NULL. No había pensado en el índice único filtrado (aunque lo he usado para otras cosas)
Kenneth Fisher

3

Primero, deja de usar la frase "Valor nulo", solo te llevará por mal camino. En su lugar, use la frase "marcador nulo": un marcador en una columna que indica que el valor real en esta columna falta o no es aplicable (pero tenga en cuenta que el marcador no dice cuál de esas opciones es realmente el caso¹).

Ahora, imagine lo siguiente (donde la base de datos no tiene un conocimiento completo de la situación modelada).

Situation          Database

ID   Code          ID   Code
--   -----         --   -----
1    A             1    A
2    B             2    (null)
3    C             3    C
4    B             4    (null)

La regla de integridad que estamos modelando es "el Código debe ser único". La situación del mundo real viola esto, por lo que la base de datos no debe permitir que los elementos 2 y 4 estén en la tabla al mismo tiempo.

El enfoque más seguro y menos flexible sería no permitir marcadores nulos en el campo Código, por lo que no hay posibilidad de datos inconsistentes. El enfoque más flexible sería permitir múltiples marcadores nulos y preocuparse por la unicidad cuando se ingresan los valores.

Los programadores de Sybase adoptaron el enfoque algo seguro y no muy flexible de permitir solo un marcador nulo en la tabla, algo de lo que los comentaristas se han quejado desde entonces. Microsoft ha continuado este comportamiento, supongo que para la compatibilidad con versiones anteriores.


¹ Estoy seguro de que leí en alguna parte que Codd consideró implementar dos marcadores nulos, uno para desconocido, otro para inaplicable, pero lo rechazó, pero no puedo encontrar la referencia. ¿Estoy recordando correctamente?

PD: Mi cita favorita sobre nulo: Louis Davidson, "Diseño profesional de la base de datos de SQL Server 2000", Wrox Press, 2001, página 52. "En resumen, una sola oración: NULL es malo".


1
Permitir un single nulltampoco logra este objetivo. Porque el valor faltante podría ser el mismo que el valor en una de las otras filas.
Martin Smith

1
Lo que dijo @MartinSmith. ¿Qué pasa si tienes una restricción de verificación CHECK (Value IN ('A','B','C','D'))? Luego, tanto la implementación de SQL-Server como el estándar SQL permiten que la tabla tenga 5 filas (una fila para cada valor más 1 con NULL). Luego, posiblemente, mientras la base de datos es consistente con sus restricciones, no es consistente con la intención del diseñador para la tabla tiene un máximo de 4 filas. No hay ningún valor en el que se pueda cambiar el valor NULL que no viole una restricción, a menos que se eliminen una o más filas.
ypercubeᵀᴹ

1
El hecho de que el estándar permita 6 o incluso 106 filas en lugar de 5 no cambia que ambas fallan de alguna manera en este escenario.
ypercubeᵀᴹ

@Martin Smith, podría, pero de nuevo, podría no: el servidor de la base de datos no puede decirlo, por lo que no se arriesga y toma la ruta segura. Eso es lo que decidieron los programadores de Sybase (supongo), causando molestia desde entonces (al menos desde Inside SQL Server 6.5, el libro más antiguo en mi estantería, donde Ron Soukup hace el mismo comentario que Aaron Bertrand hace en su respuesta) . Supongo que podría ser peor: podrían no haber exigido marcadores nulos. :-)
Greenstone Walker

2
@GreenstoneWalker: no toma la ruta "segura". Se supone que el valor faltante no entrará en conflicto. CREATE TABLE #T(A INT NULL UNIQUE);INSERT INTO #T VALUES (1),(NULL);UPDATE #T SET A = 1 WHERE A IS NULL;provocará un error. Según su teoría de las motivaciones de diseño, debería haber evitado la inserción NULLen el primer caso, porque el conocimiento incompleto significa que no hay garantía de que el valor sea diferente.
Martin Smith

2

Esto puede no ser técnicamente exacto, pero filosóficamente me ayuda a dormir por la noche ...

Como varios otros han dicho o aludido, si piensa en NULL como desconocido, entonces no puede determinar si un valor NULL es de hecho igual a otro valor NULL. Pensando en esto de esta manera, la expresión NULL == NULL debería evaluar a NULL, lo que significa desconocido.

Una restricción Unique necesitaría un valor definitivo para la comparación de los valores de la columna. En otras palabras, cuando se compara un valor de una sola columna con cualquier otro valor de la columna utilizando el operador de igualdad, debe evaluar como falso para ser válido. Desconocido no es realmente falso a pesar de que a menudo se trata como falso. Dos valores NULL pueden ser iguales, o no ... simplemente no se puede determinar definitivamente.

Ayuda a pensar en una restricción única como valores restrictivos que se pueden determinar como distintos entre sí. Lo que quiero decir con esto es si ejecuta un SELECT que se parece a esto:

SELECT * from dbo.table1 WHERE ColumnWithUniqueContraint="some value"

La mayoría de las personas esperaría un resultado, dado que existe una restricción única. Si permitiste múltiples valores NULL en ColumnWithUniqueConstraint, entonces sería imposible seleccionar una sola fila distinta de la tabla usando NULL como valor comparado.

Dado eso, creo que independientemente de si se implementa con precisión o no con respecto a la definición de NULL, definitivamente es mucho más práctico en la mayoría de las situaciones que permitir múltiples valores NULL.


Su selección dará 1 resultado, cuando hay una restricción única (en cualquier implementación, no solo SQL-Server). ¿Cuál es tu punto?
ypercubeᵀᴹ

-3

Uno de los propósitos principales de una UNIQUErestricción es evitar registros duplicados. Si uno necesita tener una tabla en la que puede haber múltiples registros donde un valor es "desconocido", pero no se permite que dos registros tengan el mismo valor "conocido", entonces a los valores desconocidos se les deben asignar identificadores únicos artificiales antes de que sean añadido a la mesa.

Hay algunos casos raros en los que una columna que tiene una UNIQUErestricción y contiene un solo valor nulo; por ejemplo, si una tabla contiene un mapeo entre valores de columna y descripciones de texto localizadas, una fila para NULLpermitiría definir la descripción que debería aparecer cuando esa columna en alguna otra tabla lo es NULL. El comportamiento de NULLpermite ese caso de uso.

De lo contrario, no veo ninguna base para una base de datos con una UNIQUErestricción en ninguna columna para permitir la existencia de muchos registros idénticos, pero no veo ninguna forma de evitar eso mientras se permiten múltiples registros cuyos valores clave no son distinguibles. Declarar que NULLno es igual a sí mismo no hará que los NULLvalores sean distinguibles entre sí.


3
Los identificadores únicos artificiales son una broma, lo siento. ¿Cómo vas a hacer eso para un VIN? Si no sabes qué es, ¿por qué inventar algo? ¿Solo para ocupar espacio extra en el disco? Parece absurdo solucionar algún otro problema (como no querer escribir la aplicación de tal manera que maneje NULL con gracia). Si realmente necesita saber por qué algo es NULL (existe pero desconocido vs. saber que no existe vs. no sabe o no importa si existe, por ejemplo), agregue algún tipo de columna de estado. Los tokens solo conducen a un código incómodo para lidiar con ellos.
Aaron Bertrand

Mucho depende del propósito de la restricción de unicidad. Si se utilizará un campo como identificador, no debería ser nulo. En los casos (como con los VIN) donde las reglas de negocios sugerirían que cuando un elemento aparece dos veces, uno de ellos debe estar equivocado, pero algunos elementos pueden ser "no sé", una restricción de unicidad no parece el enfoque adecuado. Si uno tiene un vehículo con un VIN conocido y entra en conflicto con otro en la base de datos, se puede saber que al menos uno de los VIN está equivocado, pero sería mejor que la base de datos informara el valor creído para ambos registros de lo que se supone ese es correcto
supercat

@AaronBertrand: Hay algunos casos en los que un campo posiblemente nulo único si no nulo necesitaría ser una clave sustituta no se pudo establecer antes de llenar el campo (por ejemplo, "ID del cónyuge"), pero en situaciones como que una restricción "única" sería insuficiente; sería necesario que si X.Spouse no es nulo, X.Spouse.Spouse = X. Por cierto, algo así como "cónyuge" también podría manejarse diciendo que el registro de una persona soltera no debe tener "NULL" como cónyuge, sino más bien su propia identificación, en cuyo caso la regla X.spouse.spouse = X podría aplicar a todos.
supercat
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.