Proyectos · SQL · Intermedio

Base de datos de una biblioteca

Diseña las tablas de una biblioteca (autores, libros, socios y préstamos), relaciónalas con claves y responde con consultas qué libros están prestados, quién debe devolverlos y qué se lee más.

  • 5 pasos
  • 1 hora aproximadamente
  • 59 líneas de código

Lo que vas a construir

Una biblioteca es el ejemplo perfecto de base de datos relacional: los libros tienen autores, los socios piden préstamos y todo está conectado. Vas a diseñar las tablas, relacionarlas con claves y escribir las consultas que una biblioteca necesita cada día. Funciona en SQLite, que no necesita instalar ningún servidor.

Lo que vas a practicar

  • Diseñar tablas relacionadas
  • Claves primarias y foráneas
  • Consultas con JOIN y LEFT JOIN
  • Encontrar valores NULL
  • Agrupar con GROUP BY y guardar vistas

Cómo ejecutarlo: Instala SQLite (o usa la extensión SQLite de tu editor), crea la base de datos con sqlite3 biblioteca.db y pega el código; con .read biblioteca.sql puedes ejecutar el archivo completo. Para ver los títulos de las columnas, escribe antes .headers on.

Así funciona al final
titulo | veces
Cien años de soledad | 3
La casa de los espíritus | 1
Nada | 1
Romancero gitano | 1
Don Quijote de la Mancha | 0
El amor en los tiempos del cólera | 0

Paso 1 de 5

Autores y libros

Cada autor se guarda una sola vez en su tabla y cada libro apunta a su autor con autor_id. Así, si un autor tiene varios libros, su nombre no se repite (y no puede escribirse distinto en cada uno).

PRIMARY KEY identifica cada fila de forma única y REFERENCES autores(id) declara que autor_id es una clave foránea: tiene que corresponder a un autor que exista.

biblioteca.sql
CREATE TABLE autores (
  id     INTEGER PRIMARY KEY,
  nombre TEXT NOT NULL,
  pais   TEXT
);

CREATE TABLE libros (
  id        INTEGER PRIMARY KEY,
  titulo    TEXT NOT NULL,
  anio      INTEGER,
  autor_id  INTEGER NOT NULL REFERENCES autores(id)
);

INSERT INTO autores (id, nombre, pais) VALUES
  (1, 'Miguel de Cervantes', 'España'),
  (2, 'Gabriel García Márquez', 'Colombia'),
  (3, 'Carmen Laforet', 'España'),
  (4, 'Isabel Allende', 'Chile'),
  (5, 'Federico García Lorca', 'España');

INSERT INTO libros (id, titulo, anio, autor_id) VALUES
  (1, 'Don Quijote de la Mancha', 1605, 1),
  (2, 'Cien años de soledad', 1967, 2),
  (3, 'El amor en los tiempos del cólera', 1985, 2),
  (4, 'Nada', 1945, 3),
  (5, 'La casa de los espíritus', 1982, 4),
  (6, 'Romancero gitano', 1928, 5);

SELECT titulo, anio FROM libros;
Al ejecutarlo
titulo | anio
Don Quijote de la Mancha | 1605
Cien años de soledad | 1967
El amor en los tiempos del cólera | 1985
Nada | 1945
La casa de los espíritus | 1982
Romancero gitano | 1928

Paso 2 de 5

Buscar y ordenar

WHERE filtra las filas y ORDER BY las ordena. Con BETWEEN se busca un rango (ambos extremos incluidos) y con LIKE un trozo de texto: % significa «cualquier cosa».

Puedes ejecutar solo la consulta nueva: las tablas ya están creadas desde el paso anterior.

biblioteca.sqlLo nuevo, resaltado
CREATE TABLE autores (
  id     INTEGER PRIMARY KEY,
  nombre TEXT NOT NULL,
  pais   TEXT
);

CREATE TABLE libros (
  id        INTEGER PRIMARY KEY,
  titulo    TEXT NOT NULL,
  anio      INTEGER,
  autor_id  INTEGER NOT NULL REFERENCES autores(id)
);

INSERT INTO autores (id, nombre, pais) VALUES
  (1, 'Miguel de Cervantes', 'España'),
  (2, 'Gabriel García Márquez', 'Colombia'),
  (3, 'Carmen Laforet', 'España'),
  (4, 'Isabel Allende', 'Chile'),
  (5, 'Federico García Lorca', 'España');

INSERT INTO libros (id, titulo, anio, autor_id) VALUES
  (1, 'Don Quijote de la Mancha', 1605, 1),
  (2, 'Cien años de soledad', 1967, 2),
  (3, 'El amor en los tiempos del cólera', 1985, 2),
  (4, 'Nada', 1945, 3),
  (5, 'La casa de los espíritus', 1982, 4),
  (6, 'Romancero gitano', 1928, 5);

-- Libros del siglo XX, del más antiguo al más reciente
SELECT titulo, anio
FROM libros
WHERE anio BETWEEN 1901 AND 2000
ORDER BY anio;
Al ejecutarlo
titulo | anio
Romancero gitano | 1928
Nada | 1945
Cien años de soledad | 1967
La casa de los espíritus | 1982
El amor en los tiempos del cólera | 1985

Paso 3 de 5

Libros con su autor: JOIN

JOIN … ON libros.autor_id = autores.id une cada libro con la fila de su autor, como si fueran una sola tabla. Los alias l y a acortan los nombres.

Aquí además se filtra por país del autor, un dato que está en la otra tabla: sin el JOIN no se podría.

biblioteca.sqlLo nuevo, resaltado
CREATE TABLE autores (
  id     INTEGER PRIMARY KEY,
  nombre TEXT NOT NULL,
  pais   TEXT
);

CREATE TABLE libros (
  id        INTEGER PRIMARY KEY,
  titulo    TEXT NOT NULL,
  anio      INTEGER,
  autor_id  INTEGER NOT NULL REFERENCES autores(id)
);

INSERT INTO autores (id, nombre, pais) VALUES
  (1, 'Miguel de Cervantes', 'España'),
  (2, 'Gabriel García Márquez', 'Colombia'),
  (3, 'Carmen Laforet', 'España'),
  (4, 'Isabel Allende', 'Chile'),
  (5, 'Federico García Lorca', 'España');

INSERT INTO libros (id, titulo, anio, autor_id) VALUES
  (1, 'Don Quijote de la Mancha', 1605, 1),
  (2, 'Cien años de soledad', 1967, 2),
  (3, 'El amor en los tiempos del cólera', 1985, 2),
  (4, 'Nada', 1945, 3),
  (5, 'La casa de los espíritus', 1982, 4),
  (6, 'Romancero gitano', 1928, 5);

-- Libros de autores españoles, con el nombre del autor
SELECT l.titulo, a.nombre, l.anio
FROM libros AS l
JOIN autores AS a ON l.autor_id = a.id
WHERE a.pais = 'España'
ORDER BY l.anio;
Al ejecutarlo
titulo | nombre | anio
Don Quijote de la Mancha | Miguel de Cervantes | 1605
Romancero gitano | Federico García Lorca | 1928
Nada | Carmen Laforet | 1945

Paso 4 de 5

Socios y préstamos pendientes

Un préstamo relaciona un libro con un socio en una fecha. La columna devuelto queda en NULL mientras el libro no se devuelve, así que los préstamos pendientes son los que tienen devuelto IS NULL (con = NULL no funcionaría: nada es igual a NULL).

La consulta une tres tablas para mostrar el título del libro y el nombre del socio, en vez de sus números.

biblioteca.sqlLo nuevo, resaltado
CREATE TABLE autores (
  id     INTEGER PRIMARY KEY,
  nombre TEXT NOT NULL,
  pais   TEXT
);

CREATE TABLE libros (
  id        INTEGER PRIMARY KEY,
  titulo    TEXT NOT NULL,
  anio      INTEGER,
  autor_id  INTEGER NOT NULL REFERENCES autores(id)
);

INSERT INTO autores (id, nombre, pais) VALUES
  (1, 'Miguel de Cervantes', 'España'),
  (2, 'Gabriel García Márquez', 'Colombia'),
  (3, 'Carmen Laforet', 'España'),
  (4, 'Isabel Allende', 'Chile'),
  (5, 'Federico García Lorca', 'España');

INSERT INTO libros (id, titulo, anio, autor_id) VALUES
  (1, 'Don Quijote de la Mancha', 1605, 1),
  (2, 'Cien años de soledad', 1967, 2),
  (3, 'El amor en los tiempos del cólera', 1985, 2),
  (4, 'Nada', 1945, 3),
  (5, 'La casa de los espíritus', 1982, 4),
  (6, 'Romancero gitano', 1928, 5);

CREATE TABLE socios (
  id     INTEGER PRIMARY KEY,
  nombre TEXT NOT NULL
);

CREATE TABLE prestamos (
  id         INTEGER PRIMARY KEY,
  libro_id   INTEGER NOT NULL REFERENCES libros(id),
  socio_id   INTEGER NOT NULL REFERENCES socios(id),
  fecha      TEXT NOT NULL,
  devuelto   TEXT          -- NULL mientras no se devuelva
);

INSERT INTO socios (id, nombre) VALUES (1, 'Ana'), (2, 'Luis'), (3, 'Marta');

INSERT INTO prestamos (libro_id, socio_id, fecha, devuelto) VALUES
  (2, 1, '2026-04-02', '2026-04-20'),
  (4, 2, '2026-04-10', '2026-04-25'),
  (2, 3, '2026-04-22', NULL),
  (6, 1, '2026-05-01', NULL),
  (5, 2, '2026-05-03', '2026-05-15'),
  (2, 2, '2026-05-16', '2026-05-30');

-- Libros que están fuera ahora mismo y quién los tiene
SELECT l.titulo, s.nombre, p.fecha
FROM prestamos AS p
JOIN libros AS l ON p.libro_id = l.id
JOIN socios AS s ON p.socio_id = s.id
WHERE p.devuelto IS NULL
ORDER BY p.fecha;
Al ejecutarlo
titulo | nombre | fecha
Cien años de soledad | Marta | 2026-04-22
Romancero gitano | Ana | 2026-05-01

Paso 5 de 5

Estadísticas y una vista

LEFT JOIN conserva también los libros que nunca se han prestado (con JOIN desaparecerían); COUNT(p.id) cuenta solo los préstamos que existen, así que esos libros salen con 0.

CREATE VIEW guarda la consulta con un nombre: a partir de ahí, SELECT * FROM ranking siempre da el ranking actualizado, sin volver a escribirla.

biblioteca.sqlLo nuevo, resaltado
CREATE TABLE autores (
  id     INTEGER PRIMARY KEY,
  nombre TEXT NOT NULL,
  pais   TEXT
);

CREATE TABLE libros (
  id        INTEGER PRIMARY KEY,
  titulo    TEXT NOT NULL,
  anio      INTEGER,
  autor_id  INTEGER NOT NULL REFERENCES autores(id)
);

INSERT INTO autores (id, nombre, pais) VALUES
  (1, 'Miguel de Cervantes', 'España'),
  (2, 'Gabriel García Márquez', 'Colombia'),
  (3, 'Carmen Laforet', 'España'),
  (4, 'Isabel Allende', 'Chile'),
  (5, 'Federico García Lorca', 'España');

INSERT INTO libros (id, titulo, anio, autor_id) VALUES
  (1, 'Don Quijote de la Mancha', 1605, 1),
  (2, 'Cien años de soledad', 1967, 2),
  (3, 'El amor en los tiempos del cólera', 1985, 2),
  (4, 'Nada', 1945, 3),
  (5, 'La casa de los espíritus', 1982, 4),
  (6, 'Romancero gitano', 1928, 5);

CREATE TABLE socios (
  id     INTEGER PRIMARY KEY,
  nombre TEXT NOT NULL
);

CREATE TABLE prestamos (
  id         INTEGER PRIMARY KEY,
  libro_id   INTEGER NOT NULL REFERENCES libros(id),
  socio_id   INTEGER NOT NULL REFERENCES socios(id),
  fecha      TEXT NOT NULL,
  devuelto   TEXT          -- NULL mientras no se devuelva
);

INSERT INTO socios (id, nombre) VALUES (1, 'Ana'), (2, 'Luis'), (3, 'Marta');

INSERT INTO prestamos (libro_id, socio_id, fecha, devuelto) VALUES
  (2, 1, '2026-04-02', '2026-04-20'),
  (4, 2, '2026-04-10', '2026-04-25'),
  (2, 3, '2026-04-22', NULL),
  (6, 1, '2026-05-01', NULL),
  (5, 2, '2026-05-03', '2026-05-15'),
  (2, 2, '2026-05-16', '2026-05-30');

CREATE VIEW ranking AS
SELECT l.titulo, COUNT(p.id) AS veces
FROM libros AS l
LEFT JOIN prestamos AS p ON p.libro_id = l.id
GROUP BY l.id
ORDER BY veces DESC, l.titulo;

SELECT * FROM ranking;
Al ejecutarlo
titulo | veces
Cien años de soledad | 3
La casa de los espíritus | 1
Nada | 1
Romancero gitano | 1
Don Quijote de la Mancha | 0
El amor en los tiempos del cólera | 0

Retos para seguir

Has terminado el proyecto. Si quieres ir más allá, prueba a añadirle esto por tu cuenta:

  1. Añade una restricción CHECK para que la fecha de devolución nunca sea anterior a la del préstamo.
  2. Escribe una consulta que muestre los socios con algún préstamo de más de 15 días sin devolver.
  3. Crea una tabla de géneros y relaciónala con los libros (un libro puede tener varios géneros: necesitarás una tabla intermedia).

Conceptos clave

Repasa la ruta de SQL o elige otro proyecto.