ALTER TABLE y DROP en SQL
En esta lección
Ninguna base de datos se queda como se diseñó el primer día. La tienda empieza a vender en otro país y necesita una columna pais, un campo se queda corto, una regla nueva obliga a añadir una restricción. ALTER TABLE cambia la estructura de una tabla que ya existe, sin perder sus datos. DROP y TRUNCATE borran: tablas enteras, bases de datos o todo el contenido de una tabla. Son instrucciones potentes y sin vuelta atrás, así que al final verás cómo trabajar con ellas de forma segura.
La tabla de ejemplo
Partimos de una tabla de libros sencilla, con dos filas:
CREATE TABLE libros (
id INT AUTO_INCREMENT PRIMARY KEY,
titulo VARCHAR(100) NOT NULL,
autor VARCHAR(100),
precio DECIMAL(6, 2)
);
INSERT INTO libros (titulo, autor, precio) VALUES
('Nada', 'Carmen Laforet', 18.50),
('La sombra del viento', 'Carlos Ruiz Zafón', 19.95);
(En SQLite, cambia la primera columna por id INTEGER PRIMARY KEY; lo viste en CREATE TABLE.)
Añadir columnas: ADD COLUMN
ALTER TABLE libros ADD COLUMN editorial VARCHAR(80);
SELECT * FROM libros;
| id | titulo | autor | precio | editorial |
|---|---|---|---|---|
| 1 | Nada | Carmen Laforet | 18.50 | NULL |
| 2 | La sombra del viento | Carlos Ruiz Zafón | 19.95 | NULL |
La columna nueva aparece al final y, en las filas que ya existían, vale NULL. Si le das un valor por defecto, las filas existentes lo reciben:
ALTER TABLE libros ADD COLUMN stock INT NOT NULL DEFAULT 1;
SELECT * FROM libros;
| id | titulo | autor | precio | editorial | stock |
|---|---|---|---|---|---|
| 1 | Nada | Carmen Laforet | 18.50 | NULL | 1 |
| 2 | La sombra del viento | Carlos Ruiz Zafón | 19.95 | NULL | 1 |
Una columna NOT NULL sin valor por defecto
¿Qué pasa si añades una columna obligatoria sin DEFAULT a una tabla que ya tiene filas?
ALTER TABLE libros ADD COLUMN isbn CHAR(13) NOT NULL;
Cada gestor reacciona distinto:
- PostgreSQL da error: column “isbn” contains null values.
- SQLite da error: Cannot add a NOT NULL column with default value NULL.
- MySQL la añade y rellena las filas existentes con un valor “vacío” (
''en textos,0en números). No falla, pero te deja datos sin sentido.
La forma correcta es en tres pasos: añadir la columna permitiendo NULL, rellenarla con un UPDATE y, después, hacerla obligatoria.
Nota: en MySQL puedes elegir la posición de la columna nueva con
AFTER otra_columnaoFIRST:ADD COLUMN editorial VARCHAR(80) AFTER autor. En PostgreSQL y SQLite siempre va al final.
Borrar columnas: DROP COLUMN
ALTER TABLE libros DROP COLUMN editorial;
La columna desaparece con todos sus datos. No hay papelera ni confirmación.
Nota: SQLite admite
DROP COLUMNdesde la versión 3.35 (2021), y no deja borrar columnas que formen parte de la clave primaria, de un índiceUNIQUEo de una clave foránea.
Renombrar columnas: RENAME COLUMN
ALTER TABLE libros RENAME COLUMN precio TO precio_venta;
SELECT * FROM libros;
| id | titulo | autor | precio_venta | stock |
|---|---|---|---|---|
| 1 | Nada | Carmen Laforet | 18.50 | 1 |
| 2 | La sombra del viento | Carlos Ruiz Zafón | 19.95 | 1 |
Esta sintaxis funciona en MySQL 8, PostgreSQL y SQLite (desde 3.25). En MySQL 5.7 y en MariaDB antiguo se usa CHANGE, que obliga a repetir el tipo: CHANGE precio precio_venta DECIMAL(6, 2).
Cuidado: renombrar una columna rompe todas las consultas de tu aplicación que usen el nombre antiguo. Busca en el código antes de hacerlo.
Cambiar el tipo de una columna
Aquí es donde más cambia la sintaxis entre gestores. Queremos que titulo admita hasta 200 caracteres:
-- MySQL / MariaDB: MODIFY con la definición COMPLETA
ALTER TABLE libros MODIFY COLUMN titulo VARCHAR(200) NOT NULL;
-- PostgreSQL: ALTER COLUMN ... TYPE
ALTER TABLE libros ALTER COLUMN titulo TYPE VARCHAR(200);
En MySQL, MODIFY sustituye la definición entera de la columna. Si escribes solo MODIFY COLUMN titulo VARCHAR(200), pierdes el NOT NULL (y el DEFAULT, si tenía). Es un error muy habitual: repite siempre todas las opciones.
PostgreSQL separa cada cambio en una orden distinta:
ALTER TABLE libros ALTER COLUMN autor SET NOT NULL;
ALTER TABLE libros ALTER COLUMN autor DROP NOT NULL;
ALTER TABLE libros ALTER COLUMN stock SET DEFAULT 0;
ALTER TABLE libros ALTER COLUMN stock DROP DEFAULT;
SET DEFAULT y DROP DEFAULT también funcionan así en MySQL.
Si reduces un tipo y algún dato no cabe, la base de datos se niega. Con los datos de ejemplo, autor a VARCHAR(5) falla en PostgreSQL con value too long for type character varying(5) (MySQL en modo estricto también da error). Ampliar siempre es seguro; reducir, no.
SQLite: ALTER TABLE limitado
SQLite solo permite renombrar la tabla, renombrar columnas, añadir columnas y borrar columnas. No puede cambiar el tipo de una columna ni añadir o quitar restricciones. Para eso hay que reconstruir la tabla:
-- 1. Crear la tabla nueva con la estructura deseada
CREATE TABLE libros_nueva (
id INTEGER PRIMARY KEY,
titulo VARCHAR(200) NOT NULL,
autor VARCHAR(100) NOT NULL,
precio DECIMAL(6, 2),
stock INT NOT NULL DEFAULT 0 CHECK (stock >= 0)
);
-- 2. Copiar los datos
INSERT INTO libros_nueva (id, titulo, autor, precio, stock)
SELECT id, titulo, autor, precio, stock FROM libros;
-- 3. Borrar la antigua y renombrar la nueva
DROP TABLE libros;
ALTER TABLE libros_nueva RENAME TO libros;
Hazlo dentro de una transacción para que, si algo falla a mitad, no se quede a medias.
Añadir y quitar restricciones
Las restricciones también se pueden añadir después. Por eso es tan útil ponerles nombre con CONSTRAINT. (La tabla prestamos es la de la biblioteca de CREATE TABLE.)
ALTER TABLE libros ADD CONSTRAINT chk_libros_stock CHECK (stock >= 0);
ALTER TABLE libros ADD CONSTRAINT uq_libros_titulo UNIQUE (titulo);
ALTER TABLE prestamos ADD CONSTRAINT fk_prestamos_libro
FOREIGN KEY (libro_id) REFERENCES libros(id);
Al añadir una restricción, la base de datos comprueba las filas que ya existen. Si alguna la incumple (dos títulos repetidos, un stock negativo), da error y no la crea. Primero limpia los datos, luego añade la regla.
Para quitarlas:
-- PostgreSQL y MySQL 8.0.19 o posterior
ALTER TABLE libros DROP CONSTRAINT chk_libros_stock;
-- MySQL, forma clásica (según el tipo de restricción)
ALTER TABLE libros DROP CHECK chk_libros_stock;
ALTER TABLE libros DROP INDEX uq_libros_titulo; -- UNIQUE
ALTER TABLE prestamos DROP FOREIGN KEY fk_prestamos_libro;
ALTER TABLE libros DROP PRIMARY KEY;
En MySQL, una restricción UNIQUE es en realidad un índice, y por eso se borra con DROP INDEX.
Renombrar tablas
-- MySQL
RENAME TABLE libros TO catalogo;
-- MySQL, PostgreSQL y SQLite
ALTER TABLE libros RENAME TO catalogo;
Las claves foráneas que apuntaban a libros se actualizan solas y siguen funcionando. Las consultas de tu aplicación, en cambio, no.
DROP TABLE
DROP TABLE borra la tabla entera: estructura, datos, índices y restricciones.
DROP TABLE catalogo;
DROP TABLE IF EXISTS catalogo; -- sin error si no existe
Sin IF EXISTS, borrar una tabla que no existe da error (no such table, Unknown table). Con IF EXISTS es ideal para scripts que se ejecutan varias veces.
Si otra tabla tiene una clave foránea que apunta a la que quieres borrar, la base de datos lo impide. Con las tablas del curso:
DROP TABLE clientes;
-- Error: pedidos.cliente_id apunta a clientes
-- PostgreSQL: cannot drop table clientes because other objects depend on it
Tienes que borrar antes la tabla hija (pedidos) o su clave foránea. PostgreSQL ofrece DROP TABLE clientes CASCADE, que elimina la restricción de pedidos (no la tabla ni sus datos).
DROP DATABASE
DROP DATABASE IF EXISTS biblioteca;
Borra la base de datos completa con todas sus tablas. En un servidor de producción, esta línea puede acabar con años de datos en un segundo. En SQLite no existe: se borra el archivo.
TRUNCATE: vaciar una tabla
TRUNCATE borra todas las filas pero deja la tabla (estructura, restricciones e índices):
TRUNCATE TABLE catalogo;
¿En qué se diferencia de DELETE FROM catalogo; sin WHERE?
DELETE FROM tabla | TRUNCATE TABLE tabla | |
|---|---|---|
| Qué borra | Las filas que cumplan el WHERE (o todas) | Siempre todas |
| Velocidad | Fila a fila: lento con millones | Casi instantáneo |
| Contador de ids | Sigue donde estaba | Vuelve a empezar (MySQL; en PostgreSQL con RESTART IDENTITY) |
Deshacer con ROLLBACK | Sí | No en MySQL; sí en PostgreSQL |
| Con claves foráneas que apuntan a ella | Comprueba fila a fila | Da error |
SQLite no tiene TRUNCATE: usa DELETE FROM tabla;, que internamente ya está optimizado para vaciarla rápido.
Precauciones
ALTER, DROP y TRUNCATE pertenecen al DDL (Data Definition Language), las instrucciones que cambian la estructura. En MySQL tienen una trampa: cada una hace un COMMIT automático, así que no se pueden deshacer aunque estés dentro de una transacción. PostgreSQL sí permite deshacer la mayoría con ROLLBACK.
Por eso, antes de tocar la estructura de datos que importan:
- Haz una copia de seguridad. Como mínimo, de la tabla:
CREATE TABLE libros_copia AS SELECT * FROM libros;. - Prueba primero en una copia de la base de datos, nunca directamente en producción.
- Comprueba dónde estás conectado. Muchos desastres vienen de ejecutar un
DROPen el servidor equivocado. - Cuidado con las tablas grandes. Un
ALTER TABLEsobre millones de filas puede tardar minutos y bloquear la tabla mientras tanto.
Consejo: en MySQL, la herramienta
mysqldumpguarda una base de datos entera en un archivo.sql(con todos susCREATE TABLEeINSERT) desde la terminal:mysqldump -u root -p biblioteca > copia_biblioteca.sql. Para restaurarla:mysql -u root -p biblioteca < copia_biblioteca.sql. En PostgreSQL, el equivalente espg_dump.
Migraciones en proyectos reales
En un proyecto de verdad nadie escribe ALTER TABLE a mano en el servidor. Los cambios de estructura se guardan como migraciones: archivos numerados que se ejecutan en orden y se guardan en Git junto al código.
migraciones/
001_crear_libros.sql
002_crear_socios.sql
003_anadir_stock_a_libros.sql
004_crear_prestamos.sql
Cada migración contiene un cambio pequeño (por ejemplo, ALTER TABLE libros ADD COLUMN stock INT NOT NULL DEFAULT 0;). La herramienta anota en una tabla cuáles se han aplicado ya, de modo que cualquier compañero, el servidor de pruebas y el de producción llegan exactamente a la misma estructura. Muchas permiten también una migración “hacia atrás” para deshacer el cambio.
Casi todos los frameworks traen su sistema: Laravel (PHP), Django (Python), Spring con Flyway o Liquibase (Java), Prisma o Knex (Node.js). Lo aprenderás cuando uses uno, pero la idea es siempre la misma: cada cambio de estructura, en un archivo versionado.
Errores frecuentes
MODIFYen MySQL sin repetirNOT NULLoDEFAULT: se pierden sin avisar.- Añadir una columna
NOT NULLsinDEFAULTa una tabla con filas: error en PostgreSQL y SQLite; valores vacíos en MySQL. - Confiar en
ROLLBACKdespués de unDROPoTRUNCATEen MySQL: el cambio ya es definitivo. - Usar
TRUNCATEpara borrar “algunas” filas: no admiteWHERE. Para eso,DELETE. - Renombrar tablas o columnas sin actualizar el código que las usa.
- Borrar sin copia de seguridad. Siempre, antes, una copia.
Resumen
| Quiero… | Instrucción |
|---|---|
| Añadir una columna | ALTER TABLE t ADD COLUMN c TIPO |
| Borrar una columna | ALTER TABLE t DROP COLUMN c |
| Renombrar una columna | ALTER TABLE t RENAME COLUMN a TO b |
| Cambiar el tipo (MySQL) | ALTER TABLE t MODIFY COLUMN c TIPO_COMPLETO |
| Cambiar el tipo (PostgreSQL) | ALTER TABLE t ALTER COLUMN c TYPE TIPO |
| Añadir una restricción | ALTER TABLE t ADD CONSTRAINT nombre ... |
| Quitar una restricción | ALTER TABLE t DROP CONSTRAINT nombre |
| Renombrar una tabla | ALTER TABLE t RENAME TO nuevo o RENAME TABLE (MySQL) |
| Borrar una tabla | DROP TABLE IF EXISTS t |
| Vaciar una tabla | TRUNCATE TABLE t |
| Borrar una base de datos | DROP DATABASE IF EXISTS bd |
En la siguiente lección, vistas, aprenderás a guardar consultas con nombre para usarlas como si fueran tablas.
Pon a prueba lo que has aprendido
¿Te ha quedado claro? Márcala y verás tu progreso en el explorador.