Claves primarias y foráneas en SQL: 6 casos resueltos
Practica claves primarias, foráneas y compuestas con seis casos ejecutados en SQLite: inserciones, NULL, RESTRICT, CASCADE y estados finales.
Las claves primarias y foráneas en SQL permiten comprobar si los datos respetan la identificación y las relaciones declaradas. Saber escribir un JOIN no basta para predecir si una inserción o un borrado será aceptado: esa decisión depende de las restricciones del esquema.
Esta práctica contiene seis casos originales ejecutados en SQLite 3.46.1, con operaciones aceptadas, errores esperados y comprobaciones del estado final. Todos los datos son ficticios. Utiliza una base de prácticas vacía y empieza cada caso con una preparación nueva; los resultados de un caso no se arrastran al siguiente.
El Pack Temario TAI 2027 desarrolla el modelo relacional y las claves. Este cuaderno complementa esa materia; no reproduce preguntas oficiales ni anuncia el programa de una convocatoria futura. Para practicar consultas sobre datos relacionados, utiliza también los ocho ejercicios SQL de JOIN, NULL y GROUP BY.
Qué distingue cada restricción
| Elemento | Qué comprobar en estos ejemplos |
|---|---|
| PRIMARY KEY | Que no se repita la combinación que identifica una fila |
| NOT NULL | Que la columna tenga un valor distinto de NULL |
| FOREIGN KEY | Que la referencia satisfaga la relación declarada |
| ON DELETE RESTRICT | Que no se borre una fila padre mientras tenga dependientes |
| ON DELETE CASCADE | Que el borrado del padre se propague a sus hijos relacionados |
La referencia técnica es la documentación oficial de SQLite sobre claves foráneas y CREATE TABLE y restricciones. Las claves referenciadas no se limitan en general a una primaria: SQLite admite claves padre con la unicidad compatible que exige su documentación. Aquí utilizaremos primarias explícitas.
La configuración y algunos detalles varían entre motores. Las salidas de esta página están comprobadas en la versión de SQLite indicada; no se ha probado su ejecución en todos los gestores.
Preparación inicial para cada caso
Activa las claves foráneas en la conexión que vas a utilizar y fuera de una transacción abierta. Comprueba que la consulta del ajuste devuelve 1; escribir la declaración no sustituye esa comprobación.
PRAGMA foreign_keys = ON;
PRAGMA foreign_keys;
CREATE TABLE departamento (
codigo TEXT NOT NULL PRIMARY KEY,
nombre TEXT NOT NULL
);
CREATE TABLE persona (
id TEXT NOT NULL PRIMARY KEY,
nombre TEXT NOT NULL,
dep_codigo TEXT,
FOREIGN KEY (dep_codigo)
REFERENCES departamento(codigo)
ON DELETE RESTRICT
);
INSERT INTO departamento (codigo, nombre) VALUES
('D01', 'Archivo'),
('D02', 'Atencion'),
('D03', 'Soporte');
INSERT INTO persona (id, nombre, dep_codigo) VALUES
('E1', 'Ana', 'D01'),
('E2', 'Bruno', 'D01'),
('E3', 'Carla', 'D02');
Hay tres departamentos y tres personas. Ana y Bruno comparten departamento; Soporte no tiene ninguna persona asignada. dep_codigo no es único ni lleva NOT NULL.
Hemos escrito NOT NULL junto a las claves primarias para expresar el requisito de forma explícita. SQLite conserva excepciones históricas de nulabilidad para determinadas declaraciones de primaria; no conviene deducir el comportamiento de cualquier esquema solo por el nombre de la restricción.
En los casos con un error previsto, ejecuta esa sentencia por separado. Algunos clientes interrumpen un script al encontrar un error: después tendrás que ejecutar la consulta de comprobación de forma independiente.
Caso 1. Repetir una clave primaria
Con los datos iniciales, intenta insertar una persona con un identificador que ya existe:
INSERT INTO persona (id, nombre, dep_codigo)
VALUES ('E1', 'Otra persona', 'D02');
¿Se acepta por tener un nombre y un departamento distintos?
Ver resultado y explicación del caso 1
Se rechaza. Ya existe E1 como clave primaria de persona. Cambiar los demás valores no hace que ese identificador deje de estar repetido. En la ejecución comprobada, SQLite informa de una restricción de unicidad sobre persona.id.
Después del error:
SELECT COUNT(*) AS personas FROM persona;
El resultado sigue siendo 3. La nueva fila no se ha incorporado.
Para razonar el caso, señala primero qué columnas forman la primaria. Comparar la fila completa con las anteriores podría hacerte aceptar una combinación distinta que, sin embargo, repite su identificador. El nombre de una persona tampoco es la clave elegida en este esquema.
Caso 2. Referenciar un departamento inexistente
Vuelve al estado inicial y ejecuta:
INSERT INTO persona (id, nombre, dep_codigo)
VALUES ('E4', 'Diego', 'D99');
E4 no existe todavía. ¿Eso garantiza que la fila sea válida?
Ver resultado y explicación del caso 2
No: la inserción se rechaza por la clave foránea. D99 no aparece en la clave referenciada de departamento. Tener un identificador de persona nuevo resuelve la comprobación de la primaria, pero no la relación con el departamento.
Comprueba la ausencia de la fila:
SELECT id FROM persona WHERE id = 'E4';
La consulta no devuelve filas. Este resultado presupone que PRAGMA foreign_keys devolvió 1, como se indicó al preparar la conexión.
No describas el error como «D99 está mal escrito» sin más datos. Podría ser un código futuro o una equivocación; lo que demuestra el esquema actual es que no existe una fila padre compatible. La restricción comprueba esa relación, no adivina la intención de quien inserta.
Caso 3. Una referencia que admite NULL
De nuevo con la preparación inicial, prueba esta variante:
INSERT INTO persona (id, nombre, dep_codigo)
VALUES ('E4', 'Diego', NULL);
¿Se rechaza por no existir un departamento cuyo código sea NULL?
Ver resultado y explicación del caso 3
Se acepta en este esquema. La columna hija dep_codigo permite NULL. Esta referencia nula no exige crear un departamento con ese código.
SELECT id, nombre, dep_codigo
FROM persona
WHERE id = 'E4';
| id | nombre | dep_codigo |
|---|---|---|
| E4 | Diego | NULL |
La tabla pasa a contener 4 personas. No conviertas «hay una clave foránea» en «la relación es obligatoria»: la nulabilidad también forma parte de la definición. Si el diseño exigiera asignar siempre un departamento, habría que declararlo con la restricción correspondiente.
Aquí NULL no es el texto 'NULL' ni un código inventado. El caso permite distinguir la ausencia admitida de una referencia no nula a D99, que se rechazaba en el anterior.
Caso 4. Borrar un padre con RESTRICT
Recupera el estado inicial. El departamento D01 tiene a Ana y Bruno. Intenta borrarlo:
DELETE FROM departamento WHERE codigo = 'D01';
Predice qué ocurre con el departamento y con sus dos personas.
Ver resultado y explicación del caso 4
El borrado se rechaza. La relación está declarada con ON DELETE RESTRICT y existen personas que dependen de D01.
Comprueba ambos lados después del error:
SELECT codigo FROM departamento WHERE codigo = 'D01';
SELECT COUNT(*) AS dependientes
FROM persona
WHERE dep_codigo = 'D01';
La primera consulta devuelve D01 y la segunda, 2. No se han eliminado ni ese departamento ni sus dependientes.
Leer únicamente la sentencia DELETE deja fuera el dato que decide el resultado: la acción declarada en la clave foránea. Tampoco hay que suponer que el motor borrará automáticamente a las personas. El siguiente caso cambia esa acción de forma expresa y utiliza otras tablas para que puedas comparar.
Caso 5. Borrado en cascada con otras tablas
Parte de una base recién preparada. Crea estas tablas adicionales, cuya relación utiliza CASCADE, y ejecuta el borrado indicado:
CREATE TABLE departamento_casc (
codigo TEXT NOT NULL PRIMARY KEY
);
CREATE TABLE persona_casc (
id TEXT NOT NULL PRIMARY KEY,
dep_codigo TEXT NOT NULL,
FOREIGN KEY (dep_codigo)
REFERENCES departamento_casc(codigo)
ON DELETE CASCADE
);
INSERT INTO departamento_casc VALUES ('D01'), ('D02');
INSERT INTO persona_casc VALUES
('C1', 'D01'), ('C2', 'D01'), ('C3', 'D02');
DELETE FROM departamento_casc WHERE codigo = 'D01';
¿Qué filas permanecen?
Ver resultado y explicación del caso 5
El borrado se acepta y elimina también C1 y C2. Son las filas hijas relacionadas con el padre borrado. C3, que referencia D02, permanece.
SELECT codigo FROM departamento_casc ORDER BY codigo;
SELECT id, dep_codigo FROM persona_casc ORDER BY id;
La primera salida contiene D02. La segunda es:
| id | dep_codigo |
|---|---|
| C3 | D02 |
Comprueba el alcance exacto: la cascada afecta a los dependientes del padre eliminado, no a todas las personas de la tabla. Estas tablas son una variante del ejercicio, no una modificación silenciosa del RESTRICT de la preparación inicial.
Elegir una acción de borrado es una decisión del diseño. Esta demostración permite observar su efecto con datos ficticios; no prescribe que deba utilizarse cascada para conservar o eliminar expedientes reales.
Caso 6. Una referencia compuesta debe coincidir como pareja
Empieza de nuevo y crea estas tablas adicionales:
CREATE TABLE sesion (
anio INT NOT NULL,
turno TEXT NOT NULL,
PRIMARY KEY (anio, turno)
);
CREATE TABLE inscripcion (
id TEXT NOT NULL PRIMARY KEY,
anio INT NOT NULL,
turno TEXT NOT NULL,
FOREIGN KEY (anio, turno) REFERENCES sesion(anio, turno)
);
INSERT INTO sesion VALUES (2026, 'A'), (2027, 'B');
Ahora intenta insertar:
INSERT INTO inscripcion VALUES ('X1', 2026, 'B');
El año 2026 y el turno B aparecen en la tabla padre. ¿Es suficiente?
Ver resultado y explicación del caso 6
No: la inserción se rechaza. La clave foránea referencia la combinación (anio, turno). Existe (2026, 'A') y existe (2027, 'B'), pero no existe (2026, 'B').
Esta otra inserción sí se acepta:
INSERT INTO inscripcion VALUES ('X2', 2026, 'A');
SELECT id, anio, turno FROM inscripcion ORDER BY id;
| id | anio | turno |
|---|---|---|
| X2 | 2026 | A |
No se están haciendo dos búsquedas independientes, una por año y otra por turno. El dato que debe existir es la pareja completa en una fila padre. En estas dos tablas ambas columnas llevan NOT NULL, por lo que el ejemplo tampoco deja su participación pendiente mediante nulos.
Resumen de estados comprobados
| Caso | Operación | Resultado |
|---|---|---|
| 1 | Repetir E1 | Rechazada; siguen 3 personas |
| 2 | Insertar E4 con D99 | Rechazada; E4 no aparece |
| 3 | Insertar E4 con referencia NULL | Aceptada; quedan 4 personas |
| 4 | Borrar D01 con RESTRICT | Rechazada; D01 y sus 2 personas permanecen |
| 5 | Borrar D01 con CASCADE | Aceptada; se eliminan C1 y C2, permanece C3 |
| 6 | Referenciar 2026 y B | Rechazada; la pareja 2026 y A sí se acepta |
Para corregirte, escribe restricción, dato que la incumple y estado posterior. Un mensaje de error sin comprobar las tablas deja incompleta la explicación. Una operación aceptada también exige revisar qué filas ha cambiado.
Si quieres estudiar cómo esas relaciones afectan a las consultas, continúa con los ejercicios de JOIN, recuentos y NULL. El Pack Temario TAI 2027 desarrolla la materia dentro de una preparación más amplia; consulta su índice y las bases que correspondan a tu proceso.
Preguntas frecuentes
¿Una clave primaria tiene que ser una sola columna?
No. Puede estar formada por varias columnas. En el sexto caso, la identificación y la referencia utilizan la combinación de año y turno.
¿Una clave foránea tiene que ser única en la tabla hija?
No. En la preparación de esta guía, Ana y Bruno comparten el código D01. La clave de persona es su identificador, no el código del departamento.
¿Una clave foránea permite NULL?
Depende del esquema y del motor. En el tercer caso de SQLite, la columna dep_codigo admite NULL y la inserción se acepta. Añadir una restricción NOT NULL impediría esa ausencia.
¿RESTRICT y CASCADE hacen lo mismo al borrar?
No. En estos casos, RESTRICT impide borrar el padre con dependientes. ON DELETE CASCADE permite el borrado y elimina también las filas hijas relacionadas con ese padre.
¿Estos casos están probados en todos los gestores SQL?
No. Se han ejecutado en SQLite 3.46.1 con claves foráneas activadas. Son seis ejercicios originales de práctica, no preguntas oficiales ni una validación de todos los motores.
Artículos relacionados
Condiciones booleanas TAI: 12 ejercicios de lógica
Practica tablas de verdad, AND, OR, XOR, De Morgan y condiciones de programación con doce ejercicios originales para TAI y soluciones comprobadas.
Permisos Linux para TAI: 10 ejercicios de chmod y umask
Resuelve diez ejercicios de permisos Linux para TAI: modos octales, chmod, umask, directorios y borrado. Soluciones comprobadas en un sistema Linux.
Subredes IPv4 para TAI: 8 ejercicios CIDR resueltos
Calcula red, broadcast, hosts y prefijos con ocho ejercicios IPv4 para TAI. Incluye división de subredes, agregación y un caso VLSM con resultados comprobados.