Restricciones de columnas (constraints) en SQL

Las restricciones (constraints) son reglas que se aplican a los datos de una tabla para mantener su exactitud y fiabilidad. Desempeñan un papel importante a la hora de garantizar la integridad de los datos y el cumplimiento de las reglas de negocio. 🔒

Cuando creas una tabla o modificas su estructura, puedes definir distintas restricciones que impiden añadir, modificar o eliminar datos que incumplan las reglas establecidas. Esto ayuda a evitar situaciones no deseadas como:

  • Varios usuarios con el mismo identificador
  • Referencias a registros inexistentes en otras tablas
  • Falta de datos obligatorios
  • Valores incorrectos (por ejemplo, una edad negativa o una fecha de nacimiento en el futuro)

Principales tipos de restricciones en SQL ✨

En SQL existen los siguientes tipos principales de restricciones:

  1. PRIMARY KEY: identificador único de un registro en la tabla
  2. FOREIGN KEY: garantiza la integridad referencial entre tablas
  3. UNIQUE: garantiza que los valores de una columna o grupo de columnas sean únicos
  4. NOT NULL: prohíbe los valores NULL en una columna
  5. CHECK: comprueba que los datos cumplan una condición dada
  6. DEFAULT: establece un valor por defecto para una columna

Veamos cada tipo con más detalle.

PRIMARY KEY (clave primaria)

La clave primaria es una columna o combinación de columnas que identifica de forma única cada fila de la tabla.

No puede contener valores NULL y debe ser única. Una tabla solo puede tener una clave primaria.

MySQL 8.4
CREATE TABLE Users (
    id INT AUTO_INCREMENT PRIMARY KEY,
    username VARCHAR(50),
    email VARCHAR(100)
);

Sintaxis alternativa con una restricción independiente:

MySQL 8.4
CREATE TABLE Users (
    id INT AUTO_INCREMENT,
    username VARCHAR(50),
    email VARCHAR(100),
    CONSTRAINT pk_users PRIMARY KEY (id)
);

Si intentas añadir un registro con una clave primaria que ya existe o con NULL en lugar de la clave, el SGBD devolverá un error:

MySQL 8.4
Error(1062) 23000: "Duplicate entry '1' for key 'users.PRIMARY'"
MySQL 8.4
CREATE TABLE Users (
    id SERIAL PRIMARY KEY,
    username VARCHAR(50),
    email VARCHAR(100)
);

Sintaxis alternativa con una restricción independiente:

MySQL 8.4
CREATE TABLE Users (
    id SERIAL,
    username VARCHAR(50),
    email VARCHAR(100),
    CONSTRAINT pk_users PRIMARY KEY (id)
);

Si intentas añadir un registro con una clave primaria que ya existe o con NULL en lugar de la clave, el SGBD devolverá un error:

MySQL 8.4
ERROR: duplicate key value violates unique constraint "users_pkey"
DETAIL: Key (id)=(1) already exists.

FOREIGN KEY (clave foránea)

La clave foránea es una columna o grupo de columnas de una tabla que hace referencia a la clave primaria de otra tabla.

Garantiza la integridad referencial de los datos: los valores de la columna de clave foránea deben corresponder a valores de la columna de clave primaria de la tabla relacionada.

MySQL 8.4
CREATE TABLE Orders (
    order_id INT PRIMARY KEY,
    user_id INT,
    order_date DATE,
    FOREIGN KEY (user_id) REFERENCES Users(id)
);

Gracias a la restricción FOREIGN KEY:

  • No se puede añadir un pedido de un usuario que no existe
  • No se puede eliminar un usuario que tiene pedidos (salvo que se indiquen opciones especiales de CASCADE)

También puedes indicar qué debe ocurrir al eliminar o actualizar los datos relacionados:

MySQL 8.4
CREATE TABLE Orders (
    order_id INT PRIMARY KEY,
    user_id INT,
    order_date DATE,
    FOREIGN KEY (user_id) REFERENCES Users(id)
        ON DELETE CASCADE
        ON UPDATE CASCADE
);

Opciones posibles para ON DELETE y ON UPDATE:

  • CASCADE: elimina o actualiza automáticamente los registros relacionados
  • SET NULL: asigna NULL a la clave foránea
  • SET DEFAULT: asigna el valor por defecto
  • RESTRICT: prohíbe la eliminación o la actualización (es el comportamiento por defecto)
  • NO ACTION: equivale a RESTRICT en la mayoría de los SGBD

UNIQUE (unicidad)

La restricción UNIQUE garantiza que todos los valores de una columna o grupo de columnas sean únicos. A diferencia de PRIMARY KEY, admite valores NULL (normalmente solo un valor NULL, ya que NULL != NULL).

MySQL 8.4
CREATE TABLE Users (
    id INT PRIMARY KEY,
    username VARCHAR(50) UNIQUE,
    email VARCHAR(100) UNIQUE
);

Así se evita que haya varios usuarios con el mismo nombre de usuario o la misma dirección de correo electrónico.

NOT NULL (prohibición de valores vacíos)

La restricción NOT NULL garantiza que una columna no pueda contener valores NULL. Es útil para las columnas obligatorias, sin las cuales el registro no tiene sentido.

MySQL 8.4
CREATE TABLE Users (
    id INT PRIMARY KEY,
    username VARCHAR(50) NOT NULL,
    email VARCHAR(100) NOT NULL,
    bio TEXT
);

En este ejemplo, las columnas username y email son obligatorias, mientras que bio puede quedar vacío.

CHECK (comprobación de una condición)

La restricción CHECK te permite definir una condición que deben cumplir los valores de una columna. Esto ayuda a aplicar las reglas de negocio y a evitar que se introduzcan datos incorrectos.

Nota: MySQL admite plenamente las restricciones CHECK a partir de la versión 8.0.16. En versiones anteriores se aceptaban sintácticamente, pero no se comprobaban.

MySQL 8.4
CREATE TABLE Products (
    product_id INT PRIMARY KEY,
    product_name VARCHAR(100) NOT NULL,
    price DECIMAL(10, 2) CHECK (price > 0),
    quantity INT CHECK (quantity >= 0)
);

Un ejemplo más complejo con una restricción con nombre:

MySQL 8.4
CREATE TABLE Employees (
    employee_id INT PRIMARY KEY,
    first_name VARCHAR(50) NOT NULL,
    last_name VARCHAR(50) NOT NULL,
    birth_date DATE NOT NULL,
    hire_date DATE NOT NULL,
    CONSTRAINT chk_dates CHECK (hire_date > birth_date)
);

La restricción CHECK te permite definir una condición que deben cumplir los valores de una columna. Esto ayuda a aplicar las reglas de negocio y a evitar que se introduzcan datos incorrectos.

PostgreSQL admite plenamente las restricciones CHECK y las comprueba de forma activa.

MySQL 8.4
CREATE TABLE Products (
    product_id INT PRIMARY KEY,
    product_name VARCHAR(100) NOT NULL,
    price DECIMAL(10, 2) CHECK (price > 0),
    quantity INT CHECK (quantity >= 0)
);

Un ejemplo más complejo con una restricción con nombre:

MySQL 8.4
CREATE TABLE Employees (
    employee_id INT PRIMARY KEY,
    first_name VARCHAR(50) NOT NULL,
    last_name VARCHAR(50) NOT NULL,
    birth_date DATE NOT NULL,
    hire_date DATE NOT NULL,
    CONSTRAINT chk_dates CHECK (hire_date > birth_date)
);

PostgreSQL también admite restricciones CHECK complejas con subconsultas y funciones:

MySQL 8.4
CREATE TABLE Users (
    id SERIAL PRIMARY KEY,
    email VARCHAR(100) CHECK (email ~* '^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$'),
    age INT CHECK (age >= 0 AND age <= 150)
);

DEFAULT (valor por defecto)

La restricción DEFAULT establece el valor que se usará si, al añadir un registro nuevo, no se indica ningún valor para esa columna.

MySQL 8.4
CREATE TABLE Orders (
    order_id INT AUTO_INCREMENT PRIMARY KEY,
    user_id INT,
    order_date DATE DEFAULT (CURRENT_DATE),
    status VARCHAR(20) DEFAULT 'Pending',
    FOREIGN KEY (user_id) REFERENCES Users(id)
);

En este ejemplo, si no se indica la fecha del pedido, se usará la fecha actual, y el estado por defecto será «Pending».

MySQL 8.4
CREATE TABLE Orders (
    order_id SERIAL PRIMARY KEY,
    user_id INT,
    order_date DATE DEFAULT CURRENT_DATE,
    status VARCHAR(20) DEFAULT 'Pending',
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (user_id) REFERENCES Users(id)
);

En este ejemplo, si no se indica la fecha del pedido, se usará la fecha actual, y el estado por defecto será «Pending».

PostgreSQL también admite valores DEFAULT más complejos, incluidas llamadas a funciones:

MySQL 8.4
CREATE TABLE Users (
    id SERIAL PRIMARY KEY,
    username VARCHAR(50),
    created_at TIMESTAMP DEFAULT NOW(),
    uuid_field UUID DEFAULT gen_random_uuid()
);

Añadir y eliminar restricciones

Las restricciones se pueden añadir no solo al crear la tabla, sino también al modificarla:

MySQL 8.4
-- Añadir una restricción PRIMARY KEY
ALTER TABLE Users
ADD PRIMARY KEY (id);

-- Añadir una restricción FOREIGN KEY
ALTER TABLE Orders
ADD CONSTRAINT fk_user FOREIGN KEY (user_id) REFERENCES Users(id);

-- Añadir una restricción UNIQUE
ALTER TABLE Users
ADD CONSTRAINT uq_email UNIQUE (email);

-- Añadir una restricción CHECK
ALTER TABLE Products
ADD CONSTRAINT chk_price CHECK (price > 0);

-- Añadir una restricción NOT NULL
ALTER TABLE Users
MODIFY username VARCHAR(50) NOT NULL;

-- Añadir un valor por defecto
ALTER TABLE Orders
ALTER COLUMN status SET DEFAULT 'Pending';

Las restricciones también se eliminan con la sentencia ALTER TABLE:

MySQL 8.4
-- Eliminar una restricción PRIMARY KEY
ALTER TABLE Users
DROP PRIMARY KEY;

-- Eliminar una restricción FOREIGN KEY
ALTER TABLE Orders
DROP FOREIGN KEY fk_user;

-- Eliminar una restricción UNIQUE
ALTER TABLE Users
DROP INDEX uq_email;

-- Eliminar una restricción CHECK
ALTER TABLE Products
DROP CHECK chk_price;

-- Eliminar una restricción NOT NULL
ALTER TABLE Users
MODIFY username VARCHAR(50) NULL;

-- Eliminar un valor por defecto
ALTER TABLE Orders
ALTER COLUMN status DROP DEFAULT;

Las restricciones se pueden añadir no solo al crear la tabla, sino también al modificarla:

MySQL 8.4
-- Añadir una restricción PRIMARY KEY
ALTER TABLE Users
ADD PRIMARY KEY (id);

-- Añadir una restricción FOREIGN KEY
ALTER TABLE Orders
ADD CONSTRAINT fk_user FOREIGN KEY (user_id) REFERENCES Users(id);

-- Añadir una restricción UNIQUE
ALTER TABLE Users
ADD CONSTRAINT uq_email UNIQUE (email);

-- Añadir una restricción CHECK
ALTER TABLE Products
ADD CONSTRAINT chk_price CHECK (price > 0);

-- Añadir una restricción NOT NULL
ALTER TABLE Users
ALTER COLUMN username SET NOT NULL;

-- Añadir un valor por defecto
ALTER TABLE Orders
ALTER COLUMN status SET DEFAULT 'Pending';

Las restricciones también se eliminan con la sentencia ALTER TABLE:

MySQL 8.4
-- Eliminar una restricción PRIMARY KEY
ALTER TABLE Users
DROP CONSTRAINT users_pkey;

-- Eliminar una restricción FOREIGN KEY
ALTER TABLE Orders
DROP CONSTRAINT fk_user;

-- Eliminar una restricción UNIQUE
ALTER TABLE Users
DROP CONSTRAINT uq_email;

-- Eliminar una restricción CHECK
ALTER TABLE Products
DROP CONSTRAINT chk_price;

-- Eliminar una restricción NOT NULL
ALTER TABLE Users
ALTER COLUMN username DROP NOT NULL;

-- Eliminar un valor por defecto
ALTER TABLE Orders
ALTER COLUMN status DROP DEFAULT;

Buenas prácticas en el uso de restricciones 🚀

Al diseñar una base de datos, conviene seguir estas recomendaciones:

  1. Define siempre una clave primaria en cada tabla para identificar de forma única cada registro.

  2. Usa claves foráneas para garantizar la integridad referencial entre tablas relacionadas.

  3. Aplica la restricción NOT NULL a las columnas que deben tener siempre un valor.

  4. Usa la restricción UNIQUE en las columnas que deben contener valores únicos (por ejemplo, el email o el número de teléfono).

  5. Añade restricciones CHECK a las columnas cuyos valores deben cumplir determinadas reglas de negocio.

  6. Establece valores por defecto en las columnas que a menudo toman el mismo valor.

  7. Pon nombre a las restricciones (por ejemplo, CONSTRAINT pk_users PRIMARY KEY (id)) para que sea más fácil identificarlas y gestionarlas.

Comprueba lo que sabes sobre las restricciones en SQL:

¿Cuál de las siguientes restricciones de SQL NO puede contener valores NULL?