¿Pueden las columnas de la tabla con una clave externa ser NULL?

235

Tengo una tabla que tiene varias columnas de ID para otras tablas.

Quiero una clave foránea para forzar la integridad solo si pongo datos allí. Si hago una actualización más tarde para llenar esa columna, entonces también debería verificar la restricción.

(Esto es probable que dependa del servidor de la base de datos, estoy usando el tipo de tabla MySQL e InnoDB)

Creo que esta es una expectativa razonable, pero corrígeme si me equivoco.

juerga
fuente
66
No sé acerca de MySQL, pero MS SQL Server permite que las claves foráneas sean anulables con la semántica que desea. Espero que sea un comportamiento estándar.
Jeffrey L Whitledge
1
la clave externa, no puede ser nula por defecto en mySQL, la razón es simple, si hace referencia a algo y lo deja nulo, perderá la integridad de los datos. cuando cree el conjunto de tablas, permita nulo a NO y luego aplique la restricción de clave externa. No puede establecer nulo en la actualización, debería enviarle un error, pero puede (debe) simplemente no actualizar esta columna y actualizar solo los campos que necesita cambiar.
JoelBonetR

Respuestas:

245

Sí, puede aplicar la restricción solo cuando el valor no es NULL. Esto se puede probar fácilmente con el siguiente ejemplo:

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`))

La primera inserción pasará porque insertamos un NULL en el parent_id. La segunda inserción falla debido a la restricción de clave externa, ya que intentamos insertar un valor que no existe en la parenttabla.

Daniel Vassallo
fuente
16
La tabla principal también se puede declarar con id INT NOT NULL.
Será el
@CJDennis Si lo hace de modo que solo una fila pueda tener una ID nula, podría usarse como valores de reserva para otras filas. (Aunque podría funcionar mejor para la base de datos si solo usa más columnas). La restricción predeterminada parece un problema si desea saber más adelante si un valor se estableció originalmente como "predeterminado" (mediante el uso de nulo) o establecido en un valor que resulta ser el mismo que "predeterminado". Al tener una fila con una identificación nula, puede indicar claramente que esta fila no debe usarse como una fila normal, y puede usar la fila como una forma de proporcionar un tipo de valor dinámico predeterminado para otras filas.
Ouroborus
1
Creo que la parent_id INT NULLparte es (literalmente) igual aparent_id int default null
Nota al margen para los usuarios de Java, si usa ibatis u otro ORM y usa el primitivo en intlugar de Integeren los miembros de su clase, el valor predeterminado nunca será nulo, pero será 0 y fallará la restricción.
Jim Ford
32

Descubrí que al insertar, los valores de columna nulos tenían que declararse específicamente como NULL, de lo contrario obtendría un error de violación de restricción (en lugar de una cadena vacía).

Reincidente
fuente
8
¿No podría establecer un valor predeterminado de NULL en la columna para permitir esto?
Kevin Coulombe
Sí, en la mayoría de los idiomas, NULL es diferente a una cadena vacía. Quizás sutil al comenzar, pero crítico para recordar.
Gary
Hola Backslider, dices "(en oposición a una cadena vacía)", pero no creo que quisieras decir que INSERTARÍAS un valor de cadena vacía, sino que no especifiques un valor para el Valor en ¿todas? es decir, ni siquiera mencionas la columna en tu INSERT INTO {table} {list_of_columns}? Porque eso es cierto para mí; omitir la mención de la columna causa un error, pero incluir y establecer explícitamente en NULL corrige el error. Si estoy en lo cierto, creo que el comentario de @ Gary no se aplica (porque no querías decir una cadena vacía), pero @Kevin Coulombe podría ser útil ...
The Red Pea
Sí, la sugerencia de @ KevinCoulombe funciona, describí cómo lograr esto con los scripts de migración de Entity Framework Core, aquí
The Red Pea
Es importante señalar que la razón para ser explícito al actualizar un registro que contenga claves foráneas NULL solo se aplica a los tipos de cadena (varchar, etc.), porque de lo contrario se puede pasar una cadena vacía como predeterminada. Este es el caso con MySQL, y da como resultado un error de integridad en la actualización.
CodeMantle
4

Sí, eso funcionará como lo espera. Desafortunadamente, parece que tengo problemas para encontrar una declaración explícita de esto en el manual de MySQL .

Las claves foráneas significan que el valor debe existir en la otra tabla. NULL se refiere a la ausencia de valor, por lo que cuando establece una columna en NULL, no tendría sentido intentar imponer restricciones sobre eso.

davidtbernal
fuente
Por diseño, la clave externa debe referirse a alguna clave (primaria) que no es NULL, pero durante la fase de desarrollo cuando necesitamos tener múltiples datos insertados primero en la tabla secundaria, a los que no sabemos a quién se referirá (la tabla primaria) . Es por eso que tenemos un valor NULL permitido. En producción tener NULL será un flujo de diseño, eso se puede decir más o menos.
vimal krishna
2

Lo anterior funciona pero esto no. Tenga en cuenta la CASCADA ELIMINAR

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
fuente
44
¿Qué quieres decir con 'lo anterior'? Tenga en cuenta que si se refiere a otra respuesta, el orden puede cambiar.
d219
2

Sí, el valor puede ser NULL, pero debe ser explícito. He experimentado esta misma situación antes, y es fácil olvidar POR QUÉ sucede esto, por lo que lleva un poco de tiempo recordar lo que hay que hacer.

Si los datos enviados son emitidos o interpretados como una cadena vacía, fallará. Sin embargo, al establecer explícitamente el valor en NULL al INSERTAR o ACTUALIZAR, está listo para comenzar.

Pero esta es la diversión de la programación, ¿no? ¡Creando nuestros propios problemas y luego arreglándolos! ¡Salud!

Toby Crain
fuente
1

Otra forma de evitar esto sería insertar un elemento DEFAULT en la otra tabla. Por ejemplo, cualquier referencia a uuid = 00000000-0000-0000-0000-000000000000 en la otra tabla indicaría que no hay acción. También debe establecer todos los valores para que esa identificación sea "neutral", por ejemplo, 0, cadena vacía, nula para no afectar la lógica de su código.

Martillo Salvaje
fuente
2
Esto no es lo mismo. Un valor predeterminado o "neutral" no es lo mismo que NULL, la ausencia de un valor. Sin discutir el mérito de un valor predeterminado sobre un NULL, su fraseo es un poco mixto. "Otra forma de evitar esto sería insertar un elemento nulo en la otra tabla" debería decir algo más como "Otra forma de evitar esto sería insertar un elemento PREDETERMINADO en la otra tabla"
blindguy
0

También me atasqué en este tema. Pero lo resolví simplemente definiendo la clave externa como unsigned integer. Encuentra el siguiente ejemplo:

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
fuente