¿Qué hay de malo con las columnas anulables en claves primarias compuestas?

149

ORACLE no permite valores NULL en ninguna de las columnas que comprenden una clave primaria. Parece que lo mismo es cierto para la mayoría de los otros sistemas de "nivel empresarial".

Al mismo tiempo, la mayoría de los sistemas también permiten restricciones únicas en columnas anulables.

¿Por qué las restricciones únicas pueden tener valores NULL pero las claves primarias no? ¿Existe una razón lógica fundamental para esto o es más una limitación técnica?

Roman Starkov
fuente

Respuestas:

216

Las claves primarias son para identificar filas de manera única. Esto se hace comparando todas las partes de una clave con la entrada.

Por definición, NULL no puede ser parte de una comparación exitosa. Incluso una comparación con sí misma ( NULL = NULL) fallará. Esto significa que una clave que contiene NULL no funcionaría.

Además, NULL está permitido en una clave externa, para marcar una relación opcional. (*) Permitirlo en el PK también rompería esto.


(*) Una advertencia: tener claves externas anulables no es un diseño de base de datos relacional limpio.

Si hay dos entidades Ay Bdónde Ase pueden relacionar opcionalmente B, la solución limpia es crear una tabla de resolución (digamos AB). Esa mesa enlazaría Acon B: Si no es una relación, entonces sería contener un registro, si existe , no es entonces no lo haría.

Tomalak
fuente
55
He cambiado la respuesta aceptada a esta. A juzgar por los votos, esta respuesta es la más clara para más personas. Todavía siento que la respuesta de Tony Andrews explica mejor la intención detrás de este diseño; ¡échale un vistazo también!
Roman Starkov
2
P: ¿Cuándo quieres un FULL NULL en lugar de una falta de fila? R: Solo en una versión de un esquema desnormalizado para la optimización. En esquemas no triviales, problemas no normalizados como este pueden causar problemas siempre que se requieran nuevas funciones. otoh, a la multitud de diseño web no le importa. Al menos agregaría una nota de precaución sobre esto en lugar de hacerlo parecer una buena idea de diseño.
zxq9
3
"Tener claves externas anulables no es un diseño limpio de base de datos relacional". - un diseño de base de datos libre de nulos (sexta forma normal) agrega invariablemente complejidad, los ahorros de espacio obtenidos a menudo se ven compensados ​​por el trabajo adicional del programador necesario para obtener esos logros.
Dai
1
¿Qué pasa si es una tabla de resolución ABC? con C opcional
Bart Calixto
1
Traté de evitar escribir "porque el estándar lo prohíbe", ya que esto realmente no explica nada.
Tomalak
62

Una clave primaria define un identificador único para cada fila de una tabla: cuando una tabla tiene una clave primaria, tiene una forma garantizada de seleccionar cualquier fila de la misma.

Una restricción única no necesariamente identifica cada fila; solo especifica que si una fila tiene valores en sus columnas, entonces deben ser únicos. Esto no es suficiente para identificar de manera única cada fila, que es lo que debe hacer una clave principal.

Tony Andrews
fuente
10
En SQL Server, una restricción única que tiene una columna anulable, permite el valor 'nulo' en esa columna solo una vez (dados valores idénticos para las otras columnas de la restricción). Entonces, esta restricción única se comporta esencialmente como un pk con una columna anulable.
Gerard
Confirmo lo mismo para Oracle (11.2)
Alexander Malakhov
2
En Oracle (no sé sobre SQL Server), la tabla puede contener muchas filas donde todas las columnas en una restricción única son nulas. Sin embargo, si algunas columnas en la restricción única no son nulas y algunas son nulas, entonces se aplica la unicidad.
Tony Andrews
¿Cómo se aplica esto al compuesto ÚNICO?
Dims
1
@Dims Al igual que con casi cualquier otra cosa en las bases de datos SQL "depende de la implementación". En la mayoría de los dbs, una "clave primaria" es en realidad una restricción ÚNICA debajo. La idea de "clave primaria" no es realmente más especial o poderosa que el concepto de ÚNICO. La verdadera diferencia es que si tiene dos aspectos independientes de una tabla que pueden garantizarse ÚNICO, entonces no tiene una base de datos normalizada por definición (está almacenando dos tipos de datos en la misma tabla).
zxq9
46

Básicamente, nada está mal con un NULL en una clave primaria de varias columnas. Pero tener una tiene implicaciones que el diseñador probablemente no tuvo la intención, por lo que muchos sistemas arrojan un error cuando lo intentas.

Considere el caso de las versiones de módulo / paquete almacenadas como una serie de campos:

CREATE TABLE module
  (name        varchar(20) PRIMARY KEY,
   description text DEFAULT '' NOT NULL);

CREATE TABLE version
  (module      varchar(20) REFERENCES module,
   major       integer NOT NULL,
   minor       integer DEFAULT 0 NOT NULL,
   patch       integer DEFAULT 0 NOT NULL,
   release     integer DEFAULT 1 NOT NULL,
   ext         varchar(20),
   notes       text DEFAULT '' NOT NULL,
   PRIMARY KEY (module, major, minor, patch, release, ext));

Los primeros 5 elementos de la clave primaria son partes definidas regularmente de una versión de lanzamiento, pero algunos paquetes tienen una extensión personalizada que generalmente no es un número entero (como "rc-foo" o "vanilla" o "beta" o cualquier otra cosa para alguien con quien cuatro campos son insuficientes podría soñar). Si un paquete no tiene una extensión, entonces es NULL en el modelo anterior, y no se haría ningún daño al dejar las cosas de esa manera.

Pero, ¿qué es un NULL? Se supone que representa una falta de información, una incógnita. Dicho esto, quizás esto tenga más sentido:

CREATE TABLE version
  (module      varchar(20) REFERENCES module,
   major       integer NOT NULL,
   minor       integer DEFAULT 0 NOT NULL,
   patch       integer DEFAULT 0 NOT NULL,
   release     integer DEFAULT 1 NOT NULL,
   ext         varchar(20) DEFAULT '' NOT NULL,
   notes       text DEFAULT '' NOT NULL,
   PRIMARY KEY (module, major, minor, patch, release, ext));

En esta versión, la parte "ext" de la tupla NO ES NULA, pero por defecto es una cadena vacía, que es semánticamente (y prácticamente) diferente de una NULL. Un NULL es un desconocido, mientras que una cadena vacía es un registro deliberado de "algo que no está presente". En otras palabras, "vacío" y "nulo" son cosas diferentes. Es la diferencia entre "No tengo un valor aquí" y "No sé cuál es el valor aquí".

Cuando registra un paquete que carece de una extensión de versión, sabe que carece de una extensión, por lo que una cadena vacía es realmente el valor correcto. Un NULL solo sería correcto si no supiera si tiene una extensión o no, o si supiera que sí, pero no sabía qué era. Esta situación es más fácil de tratar en sistemas donde los valores de cadena son la norma, porque no hay forma de representar un "entero vacío" que no sea insertar 0 o 1, lo que terminará enrollando en las comparaciones realizadas más tarde (que tiene sus propias implicaciones) *.

Por cierto, ambas formas son válidas en Postgres (ya que estamos discutiendo los RDMBS "empresariales"), pero los resultados de la comparación pueden variar bastante cuando se agrega un NULL a la mezcla, porque NULL == "no sabe" así que todos Los resultados de una comparación que involucra un NULL terminan siendo NULL ya que no se puede saber algo que se desconoce. ¡PELIGRO! Piénselo bien: esto significa que los resultados de comparación NULL se propagan a través de una serie de comparaciones. Esto puede ser una fuente de errores sutiles al ordenar, comparar, etc.

Postgres asume que eres un adulto y puede tomar esta decisión por ti mismo. Oracle y DB2 asumen que no se dio cuenta de que estaba haciendo algo tonto y arrojan un error. Por lo general, esto es lo correcto, pero no siempre; en realidad , es posible que no sepa y tenga un NULL en algunos casos y, por lo tanto, dejar una fila con un elemento desconocido contra el cual las comparaciones significativas son imposibles es un comportamiento correcto.

En cualquier caso, debe esforzarse por eliminar el número de campos NULL que permite en todo el esquema y de manera doble cuando se trata de campos que son parte de una clave primaria. En la gran mayoría de los casos, la presencia de columnas NULL es una indicación de un diseño de esquema no normalizado (en oposición a un desalineamiento deliberado) y debe pensarse mucho antes de ser aceptado.

[* NOTA: es posible crear un tipo personalizado que sea la unión de enteros y un tipo "inferior" que significaría semánticamente "vacío" en lugar de "desconocido". Desafortunadamente, esto introduce un poco de complejidad en las operaciones de comparación y, por lo general, ser verdaderamente correcto en escribir no vale la pena el esfuerzo en la práctica, ya que no se le deberían permitir muchos NULLvalores en primer lugar. Dicho esto, sería maravilloso si los RDBMS incluyeran un BOTTOMtipo predeterminado además NULLde evitar el hábito de combinar casualmente la semántica de "sin valor" con "valor desconocido". ]

zxq9
fuente
55
Esta es una respuesta MUY BONITA y explica mucho sobre los valores NULL y sus implicaciones en muchas situaciones. Usted, señor, ahora tiene mi respeto! Ni siquiera en la universidad obtuve una buena explicación sobre los valores NULL dentro de las bases de datos. ¡Gracias!
Apoyo la idea principal de esta respuesta. Pero escribir como 'se supone que representa una falta de información, un desconocido', 'semánticamente (y prácticamente) diferente de un NULL', 'Un NULL es un desconocido', 'una cadena vacía es un registro deliberado de "algo que no está presente "',' NULL ==" no sabe "', etc. son vagos y engañosos y en realidad solo son mnemotécnicos para declaraciones ausentes sobre cómo NULL o cualquier valor es o puede o fue destinado a ser utilizado, por el resto de la publicación . (Incluyendo inspirar el diseño (malo) de las características SQL NULL). No justifican ni explican nada; deberían ser explicados y desacreditados.
Filipinas
21

NULL == NULL -> false (al menos en DBMS)

Por lo tanto, no podrá recuperar ninguna relación utilizando un valor NULL incluso con columnas adicionales con valores reales.

Cogsy
fuente
1
Esto suena como la mejor respuesta, pero aún no entiendo por qué esto está prohibido en la creación de la clave primaria. Si se trata solo de un problema de recuperación, puede usar where pk_1 = 'a' and pk_2 = 'b'valores normales y cambiar a where pk_1 is null and pk_2 = 'b'cuando hay valores nulos.
EoghanM
O aún más confiable, where (a.pk1 = b.pk1 or (a.pk1 is null and b.pk1 is null)) and (a.pk2 = b.pk2 or (a.pk2 is null and b.pk2 is null))/
Jordan Rieger
8
Respuesta incorrecta. NULL == NULL -> DESCONOCIDO. No falso. El problema es que una restricción no se considera violada si el resultado de la prueba es DESCONOCIDO. Esto a menudo lo hace PARECER como si la comparación arrojara resultados falsos, pero en realidad no es así.
Erwin Smout
4

La respuesta de Tony Andrews es decente. Pero la respuesta real es que esta ha sido una convención utilizada por la comunidad de bases de datos relacionales y NO es una necesidad. Tal vez sea una buena convención, tal vez no.

Comparar cualquier cosa con NULL resulta en DESCONOCIDO (3er valor de verdad). Entonces, como se ha sugerido con nulos, toda la sabiduría tradicional sobre la igualdad se va por la ventana. Bueno, así es como parece a primera vista.

Pero no creo que esto sea necesariamente así, e incluso las bases de datos SQL no creen que NULL destruya toda posibilidad de comparación.

Ejecute en su base de datos la consulta SELECT * FROM VALUES (NULL) UNION SELECT * FROM VALUES (NULL)

Lo que ves es solo una tupla con un atributo que tiene el valor NULL. Entonces la unión reconoció aquí los dos valores NULL como iguales.

Al comparar una clave compuesta que tiene 3 componentes con una tupla con 3 atributos (1, 3, NULL) = (1, 3, NULL) <=> 1 = 1 AND 3 = 3 AND NULL = NULL El resultado de esto es DESCONOCIDO .

Pero podríamos definir un nuevo tipo de operador de comparación, por ejemplo. ==. X == Y <=> X = Y OR (X ES NULO E Y ES NULO)

Tener este tipo de operador de igualdad haría que las claves compuestas con componentes nulos o las claves no compuestas con valor nulo no sean problemáticas.

Rami Ojares
fuente
1
No, la UNIÓN ha reconocido los dos NULL como no distintos. Lo cual no es lo mismo que "igual". Pruebe UNION ALL en su lugar y obtendrá dos filas. Y en cuanto al "nuevo tipo de operador de comparación", SQL ya lo tiene. NO ES DISTINTO DE Pero eso por sí solo no es suficiente. Usar esto en construcciones SQL como NATURAL JOIN, o la cláusula REFERENCES de una clave foránea, requerirá opciones adicionales en esas construcciones.
Erwin Smout
Ajá, Erwin Smout. Realmente un placer conocerte también en este foro! No estaba al tanto de SQL "NO ES DISTINTO DE". ¡Muy interesante! Pero parece que es exactamente lo que quise decir con mi operador == inventado. ¿Podría explicarme por qué dice eso: "eso por sí solo no es suficiente"?
Rami Ojares
La cláusula REFERENCES se basa en la igualdad, por definición. Un tipo de REFERENCIAS que coincida con una tupla / fila secundaria con una tupla / fila principal, basándose en que los valores de los atributos correspondientes NO SON DISTINCTOS en lugar de (el más estricto) EQUAL, requerirían la capacidad de especificar esta opción, pero la sintaxis no Permitelo. Lo mismo para NATURAL JOIN.
Erwin Smout
Para que una clave externa funcione, la referencia debe ser única (es decir, todos los valores deben ser distintos). Lo que significa que podría tener un solo valor nulo. Todos los valores nulos podrían referirse a ese nulo único si las REFERENCIAS se definieran con el operador NOT DISTINCT. Creo que sería mejor (en el sentido de más útil). Con JOIN (tanto externo como interno) creo que la igualdad estricta es mejor porque los "NULL MATCHES" se multiplicarían cuando los nulos en el lado izquierdo coincidirían con todos los nulos en el lado derecho.
Rami Ojares
1

Todavía creo que este es un defecto fundamental / funcional provocado por un tecnicismo. Si tiene un campo opcional por el cual puede identificar a un cliente, ahora tiene que hackear un valor ficticio, solo porque NULL! = NULL, no es particularmente elegante pero es un "estándar de la industria"

Adriaan Davel
fuente