Saltar al contenido
crear-tablas.sql · devschool

CREATE TABLE y tipos de datos en SQL

Lección 15 de 22 · 13 min de lectura · Actualizado el

En esta lección
  1. Crear una base de datos
  2. CREATE TABLE
  3. Tipos de datos en MySQL
  4. Ids automáticos
  5. IF NOT EXISTS
  6. CREATE TABLE … AS SELECT
  7. Ver qué has creado
  8. Juego de caracteres: utf8mb4
  9. Nombres de tablas y columnas
  10. Ejemplo completo: una biblioteca
  11. Errores frecuentes
  12. Resumen

Hasta ahora has trabajado con tablas que ya existían. En esta lección aprenderás a crearlas tú: a elegir el nombre de cada columna, su tipo de dato y su tamaño. Son decisiones importantes, porque cambiar una tabla llena de datos es mucho más costoso que diseñarla bien desde el principio. Al final crearás la base de datos completa de una biblioteca.

Los ejemplos usan la sintaxis de MySQL, y en notas verás qué cambia en PostgreSQL y SQLite.

Crear una base de datos

Un servidor MySQL puede guardar muchas bases de datos (una por aplicación, normalmente). Primero se crea la base de datos y luego se indica que vas a trabajar en ella:

CREATE DATABASE biblioteca
  CHARACTER SET utf8mb4
  COLLATE utf8mb4_unicode_ci;

USE biblioteca;
  • CREATE DATABASE crea un contenedor vacío.
  • USE la selecciona: a partir de ahí, todas las tablas se crean dentro de biblioteca.
  • CHARACTER SET utf8mb4 indica cómo se guardan los textos (lo vemos más abajo).

Nota: en PostgreSQL no existe USE: te conectas a la base de datos (en psql, con \c biblioteca). En SQLite, cada base de datos es un archivo y se crea al abrirlo.

CREATE TABLE

La forma general es:

CREATE TABLE nombre_tabla (
  columna1 TIPO [opciones],
  columna2 TIPO [opciones],
  ...
);

Un ejemplo sencillo, la tabla de socios de la biblioteca:

CREATE TABLE socios (
  id INT AUTO_INCREMENT PRIMARY KEY,
  dni CHAR(9) NOT NULL,
  nombre VARCHAR(100) NOT NULL,
  email VARCHAR(255),
  fecha_alta DATE NOT NULL
);
  • Cada línea define una columna: nombre, tipo y, opcionalmente, reglas.
  • Las columnas se separan con comas. Ojo: la última no lleva coma.
  • PRIMARY KEY marca la clave primaria y NOT NULL obliga a rellenar la columna. Estas reglas se llaman restricciones y tienen su propia lección: claves y restricciones.

Tipos de datos en MySQL

El tipo decide qué se puede guardar en la columna y cuánto ocupa. Estos son los que usarás casi siempre:

TipoQué guardaEjemplo de uso
TINYINTEntero de -128 a 127 (1 byte)Nota de 0 a 10
SMALLINTEntero de -32 768 a 32 767 (2 bytes)Año, número de páginas
INTEntero de unos ±2 100 millones (4 bytes)Ids, cantidades
BIGINTEntero enorme (8 bytes)Ids de tablas gigantes, visitas
DECIMAL(p, s)Número exacto con p dígitos, s decimalesPrecios, saldos
FLOAT / DOUBLENúmero aproximado (coma flotante)Medidas científicas, coordenadas
CHAR(n)Texto de longitud fija nDNI, código postal, ISBN
VARCHAR(n)Texto de hasta n caracteresNombres, emails, títulos
TEXTTexto largo (hasta 64 KB)Descripción, comentario
DATEFecha AAAA-MM-DDFecha de nacimiento
DATETIMEFecha y horaMomento de un préstamo
TIMESTAMPFecha y hora guardada en UTCFecha de creación de un registro
BOOLEANVerdadero o falso (en MySQL, TINYINT(1))¿Está disponible?
ENUM('a', 'b')Un valor de una lista cerradaTipo de socio

Números enteros

Elige el tipo más pequeño que seguro te vaya a bastar. Un INT llega a unos 2 100 millones: suficiente para el id de casi cualquier tabla. BIGINT es para tablas que pueden superar esa cifra (registros de visitas, mensajes de una red social). En MySQL puedes añadir UNSIGNED para admitir solo positivos y duplicar el máximo.

DECIMAL para el dinero

DECIMAL(6, 2) guarda números con 6 dígitos en total, 2 de ellos decimales: de -9999.99 a 9999.99. Los guarda exactos, cifra a cifra.

FLOAT y DOUBLE guardan los números en binario y no pueden representar exactamente muchos decimales, como 0,1. El error es minúsculo, pero se acumula. Si sumas diez veces 0,1 en una columna DOUBLE, MySQL y PostgreSQL dan:

SUM(columna DOUBLE)       → 0.9999999999999999
SUM(columna DECIMAL(10,2)) → 1.00

En una tienda, eso son céntimos que no cuadran en la contabilidad. Por eso el dinero siempre va en DECIMAL. FLOAT y DOUBLE son para medidas donde un error en la decimocuarta cifra no importa (temperaturas, coordenadas GPS).

CHAR frente a VARCHAR

  • CHAR(9) reserva siempre 9 caracteres. Perfecto cuando todos los valores miden lo mismo: DNI, ISBN-13, código de país (ES).
  • VARCHAR(100) guarda solo lo que escribes (más uno o dos bytes para la longitud). Para nombres, emails y títulos, que varían mucho.

El número entre paréntesis es un máximo de caracteres. Si intentas guardar más, MySQL (en modo estricto, el habitual) da error. Ponlo razonable: 100 para un nombre, 255 para un email. No ganas nada poniendo VARCHAR(5000) “por si acaso”; para textos largos existe TEXT.

Fechas y horas

  • DATE para fechas sin hora: '2026-09-30'. Siempre en formato año-mes-día.
  • DATETIME para fecha y hora: '2026-09-30 17:45:00'. Se guarda tal cual lo escribes.
  • TIMESTAMP también guarda fecha y hora, pero la convierte a UTC al guardarla y de vuelta a tu zona horaria al leerla. Es útil en aplicaciones con usuarios de varios países. Su rango acaba en 2038, así que no sirve para fechas lejanas.

Cuidado: no guardes fechas en un VARCHAR. Con texto no puedes ordenar bien ('10/02/2026' va antes que '9/01/2026'), ni sumar días, ni comprobar que la fecha exista.

BOOLEAN y ENUM

En MySQL, BOOLEAN es un alias de TINYINT(1): TRUE se guarda como 1 y FALSE como 0. PostgreSQL tiene un tipo booleano de verdad que muestra t y f.

ENUM limita una columna a una lista de valores: tipo ENUM('general', 'estudiante', 'jubilado'). Es cómodo, pero añadir un valor nuevo obliga a modificar la tabla. Si la lista puede crecer, suele ser mejor una tabla aparte. ENUM es propio de MySQL; en PostgreSQL se crea con CREATE TYPE.

Ids automáticos

Casi todas las tablas tienen un id numérico que genera la base de datos. Cada gestor lo escribe a su manera:

GestorSintaxis
MySQL / MariaDBid INT AUTO_INCREMENT PRIMARY KEY
PostgreSQL (moderno)id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY
PostgreSQL (clásico)id SERIAL PRIMARY KEY
SQLiteid INTEGER PRIMARY KEY

Al insertar, simplemente no das valor al id:

INSERT INTO socios (dni, nombre, email, fecha_alta)
VALUES ('12345678Z', 'Irene Sanz', 'irene@correo.es', '2026-09-01');
-- id = 1, el siguiente será 2...

En PostgreSQL, GENERATED ALWAYS es la opción recomendada: si intentas escribir tú el id, da error, lo que evita choques con el contador. En SQLite, la columna tiene que ser exactamente INTEGER PRIMARY KEY (con INT no funciona igual).

IF NOT EXISTS

Si ejecutas dos veces un CREATE TABLE, la segunda da error: la tabla ya existe. Con IF NOT EXISTS, la base de datos simplemente no hace nada si ya está creada:

CREATE TABLE IF NOT EXISTS socios ( ... );

Es muy útil en scripts de instalación que se pueden lanzar más de una vez. Pero ojo: no comprueba que la tabla existente sea igual a la que describes. Si ya había una socios con otras columnas, la deja como está. Existe también CREATE DATABASE IF NOT EXISTS.

CREATE TABLE … AS SELECT

Crea una tabla nueva a partir del resultado de una consulta, con sus datos:

CREATE TABLE libros_antiguos AS
SELECT id, titulo, autor, anio_publicacion
FROM libros
WHERE anio_publicacion < 1970;

SELECT * FROM libros_antiguos;
idtituloautoranio_publicacion
1Cien años de soledadGabriel García Márquez1967
3NadaCarmen Laforet1945

Sirve para copias rápidas antes de un cambio arriesgado o para tablas de informes. Copia las columnas y los datos, pero no la clave primaria, los índices ni las claves foráneas. Si solo quieres copiar la estructura vacía, en MySQL tienes CREATE TABLE copia LIKE original;.

Ver qué has creado

En MySQL:

SHOW DATABASES;          -- lista las bases de datos
SHOW TABLES;             -- lista las tablas de la base de datos actual
DESCRIBE socios;         -- columnas, tipos y claves de una tabla
SHOW CREATE TABLE socios; -- el CREATE TABLE completo

DESCRIBE socios muestra algo así:

+------------+--------------+------+-----+---------+----------------+
| Field      | Type         | Null | Key | Default | Extra          |
+------------+--------------+------+-----+---------+----------------+
| id         | int          | NO   | PRI | NULL    | auto_increment |
| dni        | char(9)      | NO   |     | NULL    |                |
| nombre     | varchar(100) | NO   |     | NULL    |                |
| email      | varchar(255) | YES  |     | NULL    |                |
| fecha_alta | date         | NO   |     | NULL    |                |
+------------+--------------+------+-----+---------+----------------+

Nota: en PostgreSQL (psql) se usa \dt para listar tablas y \d socios para describir una. En SQLite, .tables y .schema socios, o la consulta PRAGMA table_info(socios);.

Juego de caracteres: utf8mb4

El juego de caracteres decide qué símbolos se pueden guardar. En MySQL, usa siempre utf8mb4: admite todos los caracteres de Unicode, desde la ñ y las tildes hasta los emojis.

El nombre confunde: el antiguo utf8 de MySQL (ahora llamado utf8mb3) no es UTF-8 completo. Solo admite caracteres de hasta 3 bytes, y falla con emojis y algunos símbolos. Desde MySQL 8, utf8mb4 es el valor por defecto, pero indícalo al crear la base de datos para no depender de la configuración del servidor.

La colación (COLLATE) decide cómo se comparan y ordenan los textos: si 'a' es igual a 'A' o a 'á'. Las terminadas en _ci (case insensitive) no distinguen mayúsculas.

Nombres de tablas y columnas

No hay una norma oficial, pero estas convenciones te ahorrarán problemas:

  • Minúsculas y snake_case: fecha_alta, no FechaAlta. En MySQL sobre Linux los nombres de tabla distinguen mayúsculas; en Windows no. Todo en minúsculas evita sorpresas.
  • Sin tildes, ñ ni espacios: anio_publicacion, no año publicación.
  • Tablas en plural (libros, socios) y columnas en singular. Lo importante es ser coherente.
  • Clave primaria id; claves foráneas <tabla en singular>_id: libro_id, socio_id.
  • Evita palabras reservadas como order, group, user o desc. Si una columna se llama order, tendrás que escribirla entre comillas invertidas (`order`) en MySQL o comillas dobles en PostgreSQL. Mejor orden o pedido.

Ejemplo completo: una biblioteca

Una biblioteca con libros, socios y los préstamos que relacionan a unos con otros:

CREATE DATABASE IF NOT EXISTS biblioteca
  CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
USE biblioteca;

CREATE TABLE libros (
  id INT AUTO_INCREMENT PRIMARY KEY,
  isbn CHAR(13) NOT NULL,
  titulo VARCHAR(200) NOT NULL,
  autor VARCHAR(100) NOT NULL,
  anio_publicacion SMALLINT,
  paginas SMALLINT,
  precio DECIMAL(6, 2),
  disponible BOOLEAN NOT NULL DEFAULT TRUE
);

CREATE TABLE socios (
  id INT AUTO_INCREMENT PRIMARY KEY,
  dni CHAR(9) NOT NULL,
  nombre VARCHAR(100) NOT NULL,
  email VARCHAR(255),
  fecha_alta DATE NOT NULL,
  tipo ENUM('general', 'estudiante', 'jubilado') NOT NULL DEFAULT 'general'
);

CREATE TABLE prestamos (
  id INT AUTO_INCREMENT PRIMARY KEY,
  libro_id INT NOT NULL,
  socio_id INT NOT NULL,
  fecha_prestamo DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  fecha_devolucion DATE,
  FOREIGN KEY (libro_id) REFERENCES libros(id),
  FOREIGN KEY (socio_id) REFERENCES socios(id)
);

Por qué cada tipo:

  • isbn CHAR(13): el ISBN-13 siempre tiene 13 cifras. Es texto y no número porque no se opera con él y podría empezar por cero.
  • anio_publicacion SMALLINT: un año cabe de sobra. (El tipo YEAR de MySQL solo llega hasta 1901, y hay libros más antiguos.)
  • precio DECIMAL(6, 2): dinero, exacto.
  • fecha_devolucion DATE sin NOT NULL: vale NULL mientras el libro no se ha devuelto.
  • prestamos se crea la última, porque sus claves foráneas apuntan a libros y socios, que deben existir antes.

Con unos datos de prueba:

INSERT INTO libros (isbn, titulo, autor, anio_publicacion, paginas, precio) VALUES
  ('9788420471839', 'Cien años de soledad', 'Gabriel García Márquez', 1967, 496, 22.90),
  ('9788408172178', 'La sombra del viento', 'Carlos Ruiz Zafón', 2001, 576, 19.95),
  ('9788433973511', 'Nada', 'Carmen Laforet', 1945, 304, 18.50);

INSERT INTO socios (dni, nombre, email, fecha_alta, tipo) VALUES
  ('12345678Z', 'Irene Sanz', 'irene@correo.es', '2026-09-01', 'estudiante'),
  ('87654321X', 'Mario Gil', NULL, '2026-09-05', 'general');

INSERT INTO prestamos (libro_id, socio_id, fecha_prestamo) VALUES
  (1, 1, '2026-09-10 17:30:00'),
  (3, 2, '2026-09-12 11:05:00');

SELECT p.id, s.nombre AS socio, l.titulo, p.fecha_prestamo, p.fecha_devolucion
FROM prestamos p
JOIN socios s ON s.id = p.socio_id
JOIN libros l ON l.id = p.libro_id;
idsociotitulofecha_prestamofecha_devolucion
1Irene SanzCien años de soledad2026-09-10 17:30:00NULL
2Mario GilNada2026-09-12 11:05:00NULL

Errores frecuentes

  • Coma después de la última columna: fecha_alta DATE NOT NULL, ); da error de sintaxis.
  • Guardar dinero en FLOAT: los céntimos acaban descuadrando. Usa DECIMAL.
  • Guardar fechas o números como texto: pierdes el orden correcto, los cálculos y la validación.
  • Teléfonos o códigos postales como INT: se pierden los ceros iniciales (09001 pasaría a ser 9001). Si no vas a hacer cuentas con un dato, probablemente es texto.

Resumen

Necesito guardar…Tipo en MySQL
Un idINT AUTO_INCREMENT (o BIGINT)
Una cantidad enteraINT, SMALLINT o TINYINT
DineroDECIMAL(10, 2)
Una medida aproximadaDOUBLE
Un texto de longitud fijaCHAR(n)
Un texto corto variableVARCHAR(n)
Un texto largoTEXT
Una fechaDATE
Fecha y horaDATETIME o TIMESTAMP
Sí / noBOOLEAN
  • CREATE DATABASE ... CHARACTER SET utf8mb4 y USE para empezar.
  • CREATE TABLE IF NOT EXISTS para scripts repetibles; CREATE TABLE ... AS SELECT para copias.
  • DESCRIBE y SHOW TABLES para ver lo que has creado.

En la siguiente lección, claves y restricciones, aprenderás a proteger los datos con PRIMARY KEY, UNIQUE, CHECK y claves foráneas.

Pon a prueba lo que has aprendido

[SQL] ¿Qué tipo es el más adecuado para el precio de un producto en MySQL?

[SQL] ¿Qué se guarda al insertar el código postal '09001' en una columna INT?
CREATE TABLE direcciones (cp INT);
INSERT INTO direcciones VALUES ('09001');
SELECT cp FROM direcciones;

[SQL] ¿Qué tipo encaja mejor para guardar un DNI español (8 cifras y una letra)?

[SQL] ¿Qué copia CREATE TABLE copia AS SELECT * FROM libros?

¿Te ha quedado claro? Márcala y verás tu progreso en el explorador.