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 NULL
valores en primer lugar. Dicho esto, sería maravilloso si los RDBMS incluyeran un BOTTOM
tipo predeterminado además NULL
de evitar el hábito de combinar casualmente la semántica de "sin valor" con "valor desconocido". ]