As colunas da tabela com uma chave estrangeira podem ser NULL?

235

Eu tenho uma tabela que possui várias colunas de identificação para outras tabelas.

Eu quero uma chave estrangeira para forçar a integridade somente se eu colocar dados lá. Se eu fizer uma atualização posteriormente para preencher essa coluna, também deverá verificar a restrição.

(Isso provavelmente depende do servidor de banco de dados, estou usando o tipo de tabela MySQL e InnoDB)

Acredito que seja uma expectativa razoável, mas corrija-me se estiver errado.

dobrador
fonte
6
Eu não sei sobre MySQL, mas o MS SQL Server permite que chaves estrangeiras sejam anuláveis ​​com a semântica que você deseja. Espero que seja um comportamento padrão.
Jeffrey L Whitledge
1
a chave estrangeira, não pode ser nula por padrão no mySQL, o motivo é simples: se você fizer referência a algo e deixar nulo, perderá a integridade dos dados. ao criar o conjunto de tabelas, permita nulo para NÃO e aplique a restrição de chave estrangeira. Você não pode definir nulo na atualização; ele deve enviar um erro, mas você pode (você deve) simplesmente não atualizar esta coluna e atualizar apenas os campos que precisa alterar.
JoelBonetR

Respostas:

245

Sim, você pode aplicar a restrição apenas quando o valor não for NULL. Isso pode ser facilmente testado com o seguinte exemplo:

CREATE DATABASE t;
USE t;

CREATE TABLE parent (id INT NOT NULL,
                     PRIMARY KEY (id)
) ENGINE=INNODB;

CREATE TABLE child (id INT NULL, 
                    parent_id INT NULL,
                    FOREIGN KEY (parent_id) REFERENCES parent(id)
) ENGINE=INNODB;


INSERT INTO child (id, parent_id) VALUES (1, NULL);
-- Query OK, 1 row affected (0.01 sec)


INSERT INTO child (id, parent_id) VALUES (2, 1);

-- ERROR 1452 (23000): Cannot add or update a child row: a foreign key 
-- constraint fails (`t/child`, CONSTRAINT `child_ibfk_1` FOREIGN KEY
-- (`parent_id`) REFERENCES `parent` (`id`))

A primeira inserção será aprovada porque inserimos um NULL no arquivo parent_id. A segunda inserção falha devido à restrição de chave estrangeira, pois tentamos inserir um valor que não existe na parenttabela.

Daniel Vassallo
fonte
16
A tabela pai também pode ser declarada com o ID INT NOT NULL.
Will
@CJDennis Se você fizer com que apenas uma linha possa ter um ID nulo, ela poderá ser usada como valores de fallback para outras linhas. (Embora possa funcionar melhor para o banco de dados se você apenas usar mais colunas.) A restrição padrão parece um problema se você quiser saber mais tarde se um valor foi originalmente definido como "padrão" (usando nulo) ou definido como um valor valor que é igual a "padrão". Por ter uma linha com um ID nulo, você pode indicar claramente que essa linha não deve ser usada como uma linha normal e pode usá-la como uma maneira de fornecer um tipo de valor padrão dinâmico para outras linhas.
Ouroborus
1
Eu acho que a parent_id INT NULLparte é (verbalmente) igual aparent_id int default null
Nota lateral para usuários java, se você usar o ibatis ou outro ORM e usar o primitivo em intvez de Integernos membros de sua classe, o padrão nunca será nulo, mas será 0 e você falhará na restrição.
Jim Ford
32

Descobri que, ao inserir, os valores da coluna nula precisavam ser declarados especificamente como NULL; caso contrário, eu receberia um erro de violação de restrição (em oposição a uma string vazia).

Backslider
fonte
8
Não foi possível definir um valor padrão NULL na coluna para permitir isso?
precisa
Sim, na maioria dos idiomas, NULL é diferente de uma sequência vazia. Talvez sutil ao começar, mas essencial para lembrar.
Gary
Hey Backslider, você diz "(ao contrário de uma string vazia)", mas não acho que você quis dizer que você INSERIRia um valor de string vazia, mas sim que não especifica um valor para o Valor em tudo? ou seja, você nem menciona a coluna na sua INSERT INTO {table} {list_of_columns}? Porque isso é verdade para mim; omitir a menção da coluna causa erro, mas incluir e definir explicitamente como NULL corrige o erro. Se eu estiver correto, eu acho @ comentário de Gary não se aplica (porque você não quis dizer uma empty-string), mas @ Kevin Coulombe de poderia ser útil ...
The Red Pea
Sim, @ obras sugestão de KevinCoulombe, eu descrevi como conseguir isso com os scripts de migração do Entity Framework Core, aqui
The Red Pea
É importante ressaltar que a lógica de ser explícita ao atualizar um registro contendo chaves estrangeiras NULL se aplica apenas a tipos de string (varchar, etc), porque, caso contrário, uma string vazia pode ser passada como padrão. Este é o caso do MySQL e resulta em um erro de integridade na atualização.
CodeMantle 21/03
4

Sim, isso funcionará como você espera. Infelizmente, parece que estou tendo problemas para encontrar uma declaração explícita disso no manual do MySQL .

Chaves estrangeiras significam que o valor deve existir na outra tabela. NULL se refere à ausência de valor, portanto, quando você define uma coluna como NULL, não faria sentido tentar impor restrições a isso.

davidtbernal
fonte
Por design, a Chave estrangeira deve se referir a alguma chave (Primária) que não é NULL, mas durante a fase de desenvolvimento, quando precisamos primeiro inserir vários dados na tabela filho, aos quais não sabemos a quem ele se referirá (a tabela pai) . É por isso que temos um valor NULL permitido. Na produção com NULL, haverá um fluxo de design, que pode ser dito aproximadamente.
vimal Krishna
2

O acima funciona, mas isso não. Observe a opção ON DELETE CASCADE

CREATE DATABASE t;
USE t;

CREATE TABLE parent (id INT NOT NULL,
                 PRIMARY KEY (id)
) ENGINE=INNODB;

CREATE TABLE child (id INT NULL, 
                parent_id INT NULL,
                FOREIGN KEY (parent_id) REFERENCES parent(id) ON DELETE CASCADE

) ENGINE=INNODB;


INSERT INTO child (id, parent_id) VALUES (1, NULL);
-- Query OK, 1 row affected (0.01 sec)
MrFabulous
fonte
4
O que você quer dizer com 'acima'? Observe que, se você estiver se referindo a outra resposta, o pedido poderá mudar.
d219 8/03/19
2

Sim, o valor pode ser NULL, mas você deve ser explícito. Eu já experimentei essa mesma situação antes, e é fácil esquecer POR QUE isso acontece e, portanto, é preciso um pouco para lembrar o que precisa ser feito.

Se os dados enviados forem convertidos ou interpretados como uma sequência vazia, eles falharão. No entanto, configurando explicitamente o valor como NULL ao INSERIR ou ATUALIZAR, você estará pronto.

Mas isso é divertido de programar, não é? Criando nossos próprios problemas e corrigindo-os! Felicidades!

Toby Crain
fonte
1

Outra maneira de contornar isso seria inserir um elemento DEFAULT na outra tabela. Por exemplo, qualquer referência a uuid = 00000000-0000-0000-0000-000000000000 na outra tabela indicaria nenhuma ação. Você também precisa definir todos os valores para esse ID como "neutro", por exemplo, 0, string vazia, nula para não afetar sua lógica de código.

Wildhammer
fonte
2
Isto não é a mesma coisa. Um valor padrão ou "neutro" não é o mesmo que NULL, a ausência de um valor. Sem discutir o mérito de um valor padrão sobre um NULL, seu fraseado é um pouco misto. "Outra maneira de contornar isso seria inserir um elemento nulo na outra tabela" deveria dizer algo mais como "Outra maneira de contornar isso seria inserir um elemento PADRÃO na outra tabela"
blindguy 30/03/19
0

Eu também fiquei preso nessa questão. Mas resolvi simplesmente definindo a chave estrangeira como unsigned integer. Encontre o exemplo abaixo

CREATE TABLE parent (
   id int(10) UNSIGNED NOT NULL,
    PRIMARY KEY (id)
) ENGINE=INNODB;

CREATE TABLE child (
    id int(10) UNSIGNED NOT NULL,
    parent_id int(10) UNSIGNED DEFAULT NULL,
    FOREIGN KEY (parent_id) REFERENCES parent(id) ON DELETE CASCADE
) ENGINE=INNODB;
Shams Reza
fonte