Fundamentalmente, nada está errado com um NULL em uma chave primária de várias colunas. Mas ter uma delas tem implicações que o designer provavelmente não pretendia, e é por isso que muitos sistemas lançam um erro ao tentar fazer isso.
Considere o caso das versões de módulo / pacote armazenadas como uma série 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));
Os 5 primeiros elementos da chave primária são partes regularmente definidas de uma versão de lançamento, mas alguns pacotes têm uma extensão personalizada que geralmente não é um número inteiro (como "rc-foo" ou "vanilla" ou "beta" ou qualquer outra pessoa para quem quem quatro campos é insuficiente pode sonhar). Se um pacote não tiver uma extensão, será NULL no modelo acima, e nenhum dano seria causado ao deixar as coisas dessa maneira.
Mas o que é um NULL? Supõe-se que representa uma falta de informação, um desconhecido. Dito isto, talvez isso faça mais 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));
Nesta versão, a parte "ext" da tupla NÃO é NULL, mas o padrão é uma string vazia - que é semanticamente (e praticamente) diferente de um NULL. Um NULL é um desconhecido, enquanto uma sequência vazia é um registro deliberado de "algo que não está presente". Em outras palavras, "vazio" e "nulo" são coisas diferentes. É a diferença entre "não tenho valor aqui" e "não sei qual é o valor aqui".
Quando você registra um pacote que não possui uma extensão de versão, você sabe que ele não possui uma extensão; portanto, uma string vazia é realmente o valor correto. Um NULL só estaria correto se você não soubesse se tinha uma extensão ou não, ou você sabia que sim, mas não sabia o que era. É mais fácil lidar com essa situação em sistemas em que os valores de string são a norma, porque não há como representar um "número inteiro vazio" além da inserção de 0 ou 1, que acabará sendo acumulado em quaisquer comparações feitas posteriormente (que suas próprias implicações).
Aliás, as duas formas são válidas no Postgres (já que estamos discutindo RDMBSs "corporativos"), mas os resultados da comparação podem variar bastante quando você lança um NULL na mistura - porque NULL == "não sabe", então todos os resultados de uma comparação envolvendo um NULL acabam sendo NULL, pois você não pode saber algo que é desconhecido. PERIGO! Pense com cuidado: isso significa que os resultados da comparação NULL se propagam através de uma série de comparações. Isso pode ser uma fonte de erros sutis ao classificar, comparar etc.
O Postgres assume que você é adulto e pode tomar essa decisão por si mesmo. O Oracle e o DB2 assumem que você não percebeu que estava fazendo algo bobo e emitiu um erro. Esta é geralmente a coisa certa, mas nem sempre - você pode realmente não sei e ter um NULL em alguns casos e, portanto, deixando uma linha com um elemento desconhecido contra o qual comparações significativas são impossíveis é o comportamento correto.
Em qualquer caso, você deve se esforçar para eliminar o número de campos NULL permitidos em todo o esquema e duplamente quando se trata de campos que fazem parte de uma chave primária. Na grande maioria dos casos, a presença de colunas NULL é uma indicação de design de esquema não normalizado (em oposição a deliberadamente desnormalizado) e deve ser pensado muito antes de ser aceito.
[* NOTA: É possível criar um tipo personalizado que é a união de números inteiros e um tipo "inferior" que semanticamente significaria "vazio" em oposição a "desconhecido". Infelizmente, isso introduz um pouco de complexidade nas operações de comparação e, geralmente, ser verdadeiramente correto do tipo não vale o esforço na prática, pois você não deve permitir muitos NULL
valores em primeiro lugar. Dito isto, seria maravilhoso se os RDBMSs incluíssem um BOTTOM
tipo padrão , além NULL
de impedir o hábito de conflitar casualmente a semântica de "sem valor" com "valor desconhecido". ]