Estudia por tu cuenta · Programación · curso 24 de 24
Bases de datos y SQL
Dónde guarda sus datos un sitio de verdad y cómo se los pide. Aprendes a pensar una tabla, a escribir consultas SQL (SELECT, WHERE, COUNT, JOIN), a meter y cambiar datos sin romper nada, y a conectar todo desde PHP sin que nadie te meta trampa.
SELECT nombre, puntos\n FROM partida\n WHERE puntos > 10;
Antes de empezar: conviene haber visto algo de PHP (variables, arreglos y foreach) y saber qué es una tabla con filas y columnas.
Qué es una base de datos
Lección 1 de 8
Una base de datos guarda tablas. Cada fila es una cosa, cada columna es un dato de esa cosa, y el id dice cuál cosa es.
Lo que hay que saber
Una base de datos es un lugar donde se guardan datos ordenados para encontrarlos rápido, aunque sean miles. La lista de tu grupo es una base de datos chiquita: un renglón por alumno, con su nombre y su grupo. La tienda de la esquina tiene una con sus productos, su precio y cuántos quedan. El banco tiene una con cada cuenta y cada depósito. Y este sitio también: guarda alumnos, partidas y calificaciones en un archivo que se llama SQLite. Cada vez que juegas en la sala, se escribe un renglón nuevo ahí.
Los datos viven en tablas. Una tabla tiene filas y columnas, como una hoja de cálculo. Cada fila es una cosa (un alumno) y se llama registro. Cada columna es un dato de esa cosa (su nombre) y se llama campo. Así se ve la tabla alumno de este sitio:
| id | nombre | grupo |
|---|---|---|
| 1 | Ana | 1A |
| 2 | Beto | 1A |
| 3 | Caro | 1B |
| 4 | Dani | 1B |
Cuatro filas: cuatro alumnos. Tres columnas: tres datos de cada uno. La fila 3 se lee así: «el alumno número 3 se llama Caro y está en 1B».
Cada columna guarda un tipo de dato, y solo ese. Se decide al crear la tabla:
| Tipo | En SQL | Ejemplos |
|---|---|---|
| texto | TEXT | nombre, grupo, un teléfono |
| número entero | INTEGER | puntos, edad, cuántos quedan |
| número decimal | REAL | precio 12.50, estatura 1.58 |
| fecha | TEXT con la forma AAAA-MM-DD | 2026-10-21 |
| sí o no | INTEGER con 0 o 1 | ¿ya pagó?, ¿tiene permiso? |
Fíjate: el teléfono es texto, aunque tenga puros dígitos. No lo vas a sumar ni a restar, y puede empezar con 0. Regla fácil: si no vas a hacer cuentas con él, es texto.
La columna id es la llave primaria: un número que identifica a cada fila. Tiene tres promesas: es única (no hay dos filas con el mismo id), no se repite aunque borres una fila (el 3 no se vuelve a usar) y no cambia. Si Caro se cambia a 1A, cambia su grupo; su id sigue siendo 3. Por eso todo lo demás del sitio (sus partidas, sus calificaciones) apunta a su id y no a su nombre: puede haber dos Caros en la escuela, pero no dos id 3.
Así se hace, paso a paso
Leer la tabla partida
Esta es la tabla partida del sitio. Cada vez que alguien termina un juego, entra una fila:
| id | alumno | juego | puntos | dia |
|---|---|---|---|---|
| 1 | 1 | recta | 80 | 2026-10-05 |
| 2 | 2 | recta | 65 | 2026-10-05 |
| 3 | 1 | equis | 90 | 2026-10-06 |
Paso 1 · ¿Qué es cada fila? Una partida jugada. Hay 3 filas: 3 partidas.
Paso 2 · ¿Qué dice cada columna? id es el número de la partida. alumno es el id del alumno que la jugó (no su nombre). juego es la clave del juego. puntos es lo que sacó. dia es la fecha.
Paso 3 · Lee la fila 3. «La partida 3 la jugó el alumno 1, en el juego equis, sacó 90 puntos, el 6 de octubre de 2026». Y el alumno 1, según la tabla alumno, es Ana.
Paso 4 · Tipos. id, alumno y puntos son INTEGER; juego y dia son TEXT.
3 partidas, 5 datos de cada una. Ana (id 1) jugó dos veces: recta con 80 y equis con 90.
Confundir fila con columna. La fila va de lado (toda la info de UNA cosa); la columna va de arriba abajo (UN dato de todas las cosas). «Cuántos alumnos hay» se cuenta en filas; «qué datos guardo» se cuenta en columnas.
Usar el nombre como llave. Si hay dos Danis, ¿a cuál le pones la calificación? El id no se repite; el nombre sí.
Usar el número de lista como id. El número de lista cambia cada año y cuando alguien se da de baja. El id no cambia nunca.
Ejercicios
- En la tabla alumno de arriba, ¿cuántos registros hay y cuántos campos?
ver la respuesta
4 registros y 3 camposCómo se llega- Registros = filas: 4 alumnos.
- Campos = columnas: id, nombre, grupo.
- ¿Qué tipo de columna usarías para guardar el precio de un chocolate ($12.50)?
ver la respuesta
REAL (decimal)Cómo se llega- Tiene punto decimal y sí vas a hacer cuentas con él.
- ¿Qué tipo usarías para el teléfono de la mamá de un alumno?
ver la respuesta
TEXTCómo se llega- No vas a sumar teléfonos.
- Puede empezar con 0 y un número entero lo perdería.
- En la tabla partida, la fila 2 dice alumno = 2. ¿Quién jugó esa partida?
ver la respuesta
BetoCómo se llega- alumno guarda el id del alumno.
- En la tabla alumno, id 2 es Beto.
- Caro (id 3) se cambia al grupo 1A y además la escuela le da un número de lista nuevo. ¿Qué cambia en la tabla alumno?
ver la respuesta
Solo su grupo: pasa a 1A. El id sigue siendo 3.Cómo se llega- El grupo es un dato que puede cambiar.
- El id es la llave primaria: no cambia nunca.
- La tienda de la escuela quiere una tabla de productos. Di tres columnas que necesita y de qué tipo.
ver la respuesta
nombre (TEXT), precio (REAL), cantidad (INTEGER), más el idCómo se llega- ¿Qué quiero saber de cada producto? Cómo se llama, cuánto cuesta, cuántos quedan.
- Cada uno con su tipo: texto, decimal, entero.
Diseñar una tabla antes de llenarla
Lección 2 de 8
Primero piensa qué necesitas saber de cada cosa. Un dato por celda, nada repetido, nombres de columna en minúsculas sin espacios ni acentos.
Lo que hay que saber
Antes de llenar una tabla hay que pensarla. La pregunta es siempre la misma: «¿qué necesito saber de cada cosa?». Para la tienda de la escuela: de cada producto quiero su nombre, su precio y cuántos quedan. Eso son tres columnas, más el id. Lo que no vas a usar no lo guardes; lo que vas a usar, no lo olvides, porque agregar una columna después de tener 500 filas es un dolor.
Hay tres reglas que separan una tabla buena de una que da problemas:
- Un dato por celda. Una celda con «Ana, Beto» no sirve: no puedes buscar a Beto ni contar cuántos hay. Si una cosa tiene varios datos iguales (un equipo tiene varios alumnos), eso son varias filas, no una celda larga.
- No repitas información. Si en cada alumno escribes «1A, maestro Israel, salón 12», ese dato se repite 30 veces y el día que cambie el salón tienes que corregir 30 filas (y se te va a escapar una). El alumno lleva solo su grupo: 1A. Lo demás del grupo va una sola vez, en una tabla grupo.
- Nombres de columna limpios: en minúsculas, sin espacios, sin acentos ni ñ, y si tienen dos palabras se unen con guion bajo: fecha_nacimiento, no «Fecha de Nacimiento». Las computadoras se pelean con los espacios y los acentos; tú no quieres pelear con ellas.
Compara. Esta tabla está mal pensada:
| Nombre completo | Grupo | Teléfonos |
|---|---|---|
| Ana López | 1A, salón 12, maestro Israel | 555-1234, 555-9876 |
Y esta es la misma información, bien pensada (el salón y el maestro viven en la tabla grupo):
| id | nombre | apellido | grupo | telefono |
|---|---|---|---|---|
| 1 | Ana | López | 1A | 555-1234 |
Cuando ya sabes las columnas y sus tipos, la tabla se crea con una orden de SQL. SQL es el lenguaje con el que se habla con la base de datos, y esta es su primera palabra:
CREATE TABLE producto ( id INTEGER PRIMARY KEY, nombre TEXT, precio REAL, cantidad INTEGER );
Se lee: «crea la tabla producto con estas columnas». PRIMARY KEY marca al id como llave primaria, y SQLite le va poniendo 1, 2, 3… solito. Cada columna lleva su tipo. Las órdenes de SQL terminan con punto y coma.
Así se hace, paso a paso
Diseñar la tabla de libros del salón
Paso 1 · ¿Qué necesito saber de cada libro? Su título, quién lo escribió, cuántas páginas tiene y si está prestado o no.
Paso 2 · Nombres y tipos. titulo TEXT (sin acento aunque se diga «título»), autor TEXT, paginas INTEGER, prestado INTEGER con 0 o 1. Y el id.
Paso 3 · ¿Algo repetido? Si guardo a quién está prestado, no escribo su nombre: escribo su id de alumno en una columna prestado_a. Si está en el estante, va vacío.
Paso 4 · Escribo la orden.
CREATE TABLE libro ( id INTEGER PRIMARY KEY, titulo TEXT, autor TEXT, paginas INTEGER, prestado_a INTEGER );
Cinco columnas, cada una con un solo dato y su tipo; el alumno que lo tiene se guarda por id.
Guardar la edad. La edad cambia cada año y nadie la va a actualizar. Guarda fecha_nacimiento: la edad se calcula cuando haga falta.
Meter dos datos en una celda «porque caben». «555-1234, 555-9876» en una celda: después no puedes marcar al segundo. Si una cosa tiene varios teléfonos, es otra tabla (telefono: id, alumno, numero).
Columnas con espacios o acentos. «Año de ingreso» funciona hoy y se rompe mañana en otro programa. anio_ingreso siempre funciona.
Ejercicios
- ¿Cuál de estos nombres de columna está bien escrito: «Fecha Nac», «fecha_nacimiento», «fechaNacimiento» o «fecha-nacimiento»?
ver la respuesta
fecha_nacimientoCómo se llega- Minúsculas, sin espacios, palabras unidas con guion bajo.
- ¿Qué está mal en una celda que dice «Ana, Beto, Caro»?
ver la respuesta
Tiene tres datos en una celda; deben ser tres filasCómo se llega- Un dato por celda.
- Con tres nombres juntos no puedes buscar ni contar a uno solo.
- Para la tienda: ¿guardas el precio del chocolate en la tabla producto o lo escribes en cada venta?
ver la respuesta
En producto, una sola vezCómo se llega- Si lo escribes en cada venta se repite cientos de veces.
- La venta guarda el id del producto; el precio se busca ahí.
- Escribe el CREATE TABLE de una tabla mascota con id, nombre, tipo (perro, gato…) y peso en kilos con decimales.
ver la respuesta
CREATE TABLE mascota (id INTEGER PRIMARY KEY, nombre TEXT, tipo TEXT, peso REAL);Cómo se llega- Cada columna con su tipo.
- El peso lleva decimales: REAL.
- Termina con punto y coma.
- Un amigo pone en la tabla alumno la columna edad INTEGER. ¿Qué problema va a tener en un año?
ver la respuesta
Todas las edades quedan viejas; mejor guardar fecha_nacimientoCómo se llega- La edad cambia sola con el tiempo; la fecha de nacimiento no.
- La edad se calcula a partir de la fecha.
- Diseña la tabla para la lista de asistencia de un grupo: ¿qué columnas, de qué tipo, y qué no debe ir repetido?
ver la respuesta
asistencia: id, alumno (id del alumno), dia (fecha), presente (0 o 1). El nombre del alumno no va: ya está en alumno.Cómo se llega- Cada fila: un alumno en un día.
- Se apunta al alumno por su id, no por su nombre, para no repetirlo 200 veces.
SELECT: pedir datos
Lección 3 de 8
SELECT columnas FROM tabla. El resultado es una tabla nueva que se puede ordenar con ORDER BY y recortar con LIMIT; la original no cambia.
Lo que hay que saber
La orden más usada de SQL es SELECT: «dame datos». Se dice qué columnas quieres y de qué tabla. Para toda esta lección usamos la tabla partida:
| id | alumno | juego | puntos | dia |
|---|---|---|---|---|
| 1 | 1 | recta | 80 | 2026-10-05 |
| 2 | 2 | recta | 65 | 2026-10-05 |
| 3 | 1 | equis | 90 | 2026-10-06 |
| 4 | 3 | recta | 70 | 2026-10-06 |
| 5 | 4 | equis | 55 | 2026-10-07 |
SELECT juego, puntos FROM partida;
Se lee «selecciona juego y puntos de partida». La base contesta con una tabla nueva que solo tiene esas dos columnas, con las 5 filas:
| juego | puntos |
|---|---|
| recta | 80 |
| recta | 65 |
| equis | 90 |
| recta | 70 |
| equis | 55 |
Si quieres todas las columnas, en vez de escribirlas una por una pones un asterisco: SELECT * FROM partida;. El asterisco significa «todo».
Para ordenar el resultado se agrega ORDER BY y la columna. De menor a mayor es lo normal (puedes escribir ASC, que significa ascendente); de mayor a menor se pone DESC:
SELECT juego, puntos FROM partida ORDER BY puntos DESC;
Ahora las filas salen 90, 80, 70, 65, 55. Si solo quieres las primeras, LIMIT recorta: LIMIT 3 deja las tres primeras del resultado ya ordenado. Así se saca «las 3 mejores partidas»:
SELECT juego, puntos FROM partida ORDER BY puntos DESC LIMIT 3;
| juego | puntos |
|---|---|
| equis | 90 |
| recta | 80 |
| recta | 70 |
Para leer el resultado piensa en esto: es una tabla nueva, hecha al momento. La tabla partida sigue exactamente igual, con sus 5 filas y 5 columnas. SELECT solo mira; nunca cambia nada. Por eso puedes probar las consultas que quieras sin miedo.
Así se hace, paso a paso
Las dos partidas con menos puntos
Paso 1 · ¿Qué columnas quiero ver? El juego y los puntos: SELECT juego, puntos.
Paso 2 · ¿De qué tabla? FROM partida.
Paso 3 · ¿En qué orden? De menos a más puntos: ORDER BY puntos (sin DESC, porque quiero los chicos primero).
Paso 4 · ¿Cuántas? Dos: LIMIT 2.
SELECT juego, puntos FROM partida ORDER BY puntos LIMIT 2;
Paso 5 · Leo el resultado. Ordenadas quedan 55, 65, 70, 80, 90; me quedo con las dos primeras.
| juego | puntos |
|---|---|
| equis | 55 |
| recta | 65 |
equis 55 y recta 65. La tabla partida sigue con sus 5 filas.
Olvidar DESC. «Las mejores» son las de más puntos: ORDER BY puntos DESC. Sin DESC te salen las peores.
LIMIT sin ORDER BY. LIMIT 3 solo recorta las tres primeras en el orden que estén guardadas. Si quieres «las 3 mejores», primero ordena.
Cambiar el orden de las palabras. FROM partida SELECT juego no funciona. Siempre: SELECT, FROM, ORDER BY, LIMIT.
Ejercicios
- ¿Qué devuelve SELECT juego FROM partida; con la tabla de arriba?
ver la respuesta
Una columna con 5 filas: recta, recta, equis, recta, equisCómo se llega- Solo la columna juego.
- Todas las filas, en su orden original.
- Escribe la consulta que da todas las columnas de todas las partidas.
ver la respuesta
SELECT * FROM partida;Cómo se llega- El asterisco es «todas las columnas».
- Escribe la consulta que muestra alumno y puntos, de mayor a menor puntaje.
ver la respuesta
SELECT alumno, puntos FROM partida ORDER BY puntos DESC;Cómo se llega- Las columnas que pido: alumno, puntos.
- De mayor a menor: DESC.
- ¿Qué devuelve SELECT dia FROM partida ORDER BY dia DESC LIMIT 1;?
ver la respuesta
2026-10-07 (el día más reciente)Cómo se llega- Ordenar fechas DESC pone la más nueva primero.
- LIMIT 1 deja solo esa.
- ¿Qué está mal en: SELECT juego puntos FROM partida;?
ver la respuesta
Falta la coma entre juego y puntosCómo se llega- Las columnas se separan con comas.
- Después de correr SELECT * FROM partida ORDER BY puntos DESC LIMIT 1;, ¿cuántas filas tiene la tabla partida?
ver la respuesta
5, igual que antesCómo se llega- SELECT no cambia la tabla.
- El resultado de 1 fila es una tabla nueva, aparte.
WHERE: filtrar filas
Lección 4 de 8
WHERE pone una condición y solo pasan las filas que la cumplen. El texto va entre comillas simples; AND pide las dos cosas, OR con una basta.
Lo que hay que saber
Casi nunca quieres todas las filas. Quieres «las partidas de recta» o «los alumnos de 1A». Para eso está WHERE: una condición que cada fila tiene que cumplir para salir en el resultado. Seguimos con la tabla partida de la lección anterior.
SELECT juego, puntos FROM partida WHERE puntos >= 70;
La base revisa fila por fila: ¿sus puntos son 70 o más? Si sí, pasa; si no, se queda fuera. Haz tú la misma revisión en una tabla de seguimiento:
| id | juego | puntos | ¿puntos >= 70? |
|---|---|---|---|
| 1 | recta | 80 | sí ✓ |
| 2 | recta | 65 | no |
| 3 | equis | 90 | sí ✓ |
| 4 | recta | 70 | sí ✓ (70 es igual, y pedí «o igual») |
| 5 | equis | 55 | no |
Pasan las filas 1, 3 y 4. Los comparadores son los de matemáticas: = igual, <> distinto, < menor, > mayor, <= menor o igual, >= mayor o igual. Ojo con la diferencia entre > 70 (pasan 80 y 90) y >= 70 (pasan 80, 90 y 70).
Cuando comparas con texto, el texto va entre comillas simples: WHERE juego = 'recta'. Sin comillas, la base cree que recta es el nombre de una columna y da error. Los números van sin comillas.
Se pueden juntar condiciones. AND exige las dos; OR se conforma con una; NOT le da la vuelta a una:
WHERE juego = 'recta' AND puntos >= 70 → filas 1 y 4 WHERE juego = 'equis' OR puntos >= 80 → filas 1, 3 y 5 WHERE NOT juego = 'recta' → filas 3 y 5
Hay dos ayudas más. LIKE busca texto parecido: el signo % significa «lo que sea», así que nombre LIKE 'A%' da los nombres que empiezan con A. Y BETWEEN es un rango que incluye los dos extremos: puntos BETWEEN 60 AND 80 es lo mismo que puntos >= 60 AND puntos <= 80.
WHERE se combina con lo que ya sabes. El orden de las palabras es fijo: primero filtras, luego ordenas, luego recortas:
SELECT juego, puntos FROM partida WHERE juego = 'recta' ORDER BY puntos DESC LIMIT 2;
Así se hace, paso a paso
Partidas de recta con menos de 75 puntos, de mayor a menor
Paso 1 · Escribo la consulta en orden.
SELECT id, puntos FROM partida WHERE juego = 'recta' AND puntos < 75 ORDER BY puntos DESC;
Paso 2 · Reviso fila por fila con las dos condiciones (AND: las dos tienen que cumplirse).
| id | juego | puntos | ¿recta? | ¿< 75? | ¿pasa? |
|---|---|---|---|---|---|
| 1 | recta | 80 | sí | no | no |
| 2 | recta | 65 | sí | sí | sí ✓ |
| 3 | equis | 90 | no | no | no |
| 4 | recta | 70 | sí | sí | sí ✓ |
| 5 | equis | 55 | no | sí | no |
Paso 3 · Ordeno las que pasaron de mayor a menor: 70 y luego 65.
Dos filas: id 4 con 70 puntos, id 2 con 65 puntos.
Olvidar las comillas. WHERE juego = recta da error: la base busca una columna llamada recta. Es WHERE juego = 'recta'.
Confundir AND con OR. «Partidas de recta o de equis» es juego = 'recta' OR juego = 'equis'. Con AND no sale ninguna: una fila no puede ser de los dos juegos a la vez.
Confundir > con >=. «70 o más» es >= 70. «Más de 70» es > 70, y el 70 se queda fuera.
Ejercicios
- Con la tabla partida, ¿qué filas devuelve SELECT * FROM partida WHERE juego = 'equis';?
ver la respuesta
Las filas 3 y 5Cómo se llega- Reviso la columna juego fila por fila.
- Solo 3 y 5 dicen equis.
- ¿Cuántas filas devuelve WHERE puntos > 70? ¿Y WHERE puntos >= 70?
ver la respuesta
2 filas (80 y 90) y 3 filas (80, 90 y 70)Cómo se llega- Con > el 70 no pasa.
- Con >= el 70 sí pasa.
- Escribe la consulta que da los puntos de las partidas del alumno 1.
ver la respuesta
SELECT puntos FROM partida WHERE alumno = 1;Cómo se llega- alumno es número: sin comillas.
- ¿Qué está mal en: SELECT * FROM partida WHERE juego = recta;?
ver la respuesta
Falta poner recta entre comillas simplesCómo se llega- Sin comillas la base cree que recta es una columna.
- ¿Qué filas devuelve WHERE juego = 'recta' OR puntos > 85?
ver la respuesta
Las filas 1, 2, 3 y 4Cómo se llega- Pasan las de recta: 1, 2, 4.
- Y además las de más de 85: la 3.
- Con OR basta una de las dos.
- Escribe la consulta que da el id y los puntos de las partidas con puntos entre 60 y 80, de mayor a menor.
ver la respuesta
SELECT id, puntos FROM partida WHERE puntos BETWEEN 60 AND 80 ORDER BY puntos DESC;Cómo se llega- BETWEEN incluye 60 y 80.
- Pasan 80, 65 y 70; ordenadas: 80, 70, 65.
Contar y sumar: COUNT, SUM, AVG, GROUP BY
Lección 5 de 8
COUNT cuenta filas, SUM suma, AVG promedia, MAX y MIN buscan el mayor y el menor. GROUP BY hace la cuenta por grupos: cuántas partidas por juego.
Lo que hay que saber
Muchas preguntas no piden filas sino un número: ¿cuántas partidas hay? ¿cuántos puntos en total? ¿cuál fue la mejor? Para eso SQL tiene funciones que resumen una columna completa en un solo valor. Seguimos con la tabla partida:
| id | alumno | juego | puntos |
|---|---|---|---|
| 1 | 1 | recta | 80 |
| 2 | 2 | recta | 65 |
| 3 | 1 | equis | 90 |
| 4 | 3 | recta | 70 |
| 5 | 4 | equis | 55 |
| Función | Qué hace | Con la tabla |
|---|---|---|
| COUNT(*) | cuenta las filas | 5 |
| SUM(puntos) | suma la columna | 80 + 65 + 90 + 70 + 55 = 360 |
| AVG(puntos) | el promedio | 360 ÷ 5 = 72 |
| MAX(puntos) | el mayor | 90 |
| MIN(puntos) | el menor | 55 |
SELECT COUNT(*) FROM partida; → 5 SELECT SUM(puntos) FROM partida; → 360 SELECT MAX(puntos) FROM partida WHERE juego = 'recta'; → 80
Fíjate en la última: primero WHERE filtra, luego la función resume. De las partidas de recta (80, 65, 70) el máximo es 80. Así contestas «¿cuántas partidas de equis hay?»: SELECT COUNT(*) FROM partida WHERE juego = 'equis'; da 2.
¿Y si quiero el conteo de cada juego, no de uno? Podrías hacer una consulta por juego, pero GROUP BY lo hace de un jalón: junta las filas que tienen el mismo valor y aplica la función a cada montón:
SELECT juego, COUNT(*) AS partidas FROM partida GROUP BY juego;
| juego | partidas |
|---|---|
| equis | 2 |
| recta | 3 |
Sale una fila por juego. AS partidas es un alias: le pone nombre a la columna del resultado, porque «COUNT(*)» es feo de leer y además así puedes usarla para ordenar. Esto es exactamente lo que hace la página de estadísticas del sitio para saber qué juego se juega más:
SELECT juego, COUNT(*) AS partidas FROM partida GROUP BY juego ORDER BY partidas DESC;
Así se hace, paso a paso
Puntos totales de cada alumno, del mejor al peor
Paso 1 · ¿Qué resumo y por qué grupos? Sumo puntos, un montón por alumno: SUM(puntos) con GROUP BY alumno.
Paso 2 · Le pongo nombre y ordeno por él.
SELECT alumno, SUM(puntos) AS total FROM partida GROUP BY alumno ORDER BY total DESC;
Paso 3 · Hago los montones a mano para comprobar. Alumno 1: 80 + 90 = 170. Alumno 2: 65. Alumno 3: 70. Alumno 4: 55.
| alumno | total |
|---|---|
| 1 | 170 |
| 3 | 70 |
| 2 | 65 |
| 4 | 55 |
Cuatro filas, una por alumno; el alumno 1 va primero con 170.
Sumar lo que hay que contar. «¿Cuántas partidas?» es COUNT(*), no SUM(puntos). COUNT cuenta filas; SUM suma valores.
Usar GROUP BY y poner columnas sueltas. SELECT juego, puntos FROM partida GROUP BY juego no tiene sentido: ¿cuáles puntos, si hay tres de recta? Con GROUP BY, lo que no es el grupo tiene que ir dentro de una función.
Promediar sin pensar. AVG(puntos) WHERE juego = 'equis' es (90 + 55) ÷ 2 = 72.5, no 72. Primero el filtro, luego el promedio.
Ejercicios
- Con la tabla de arriba, ¿qué da SELECT COUNT(*) FROM partida WHERE juego = 'recta';?
ver la respuesta
3Cómo se llega- Filas de recta: 1, 2 y 4.
- Son 3.
- ¿Qué da SELECT SUM(puntos) FROM partida WHERE juego = 'recta';?
ver la respuesta
215Cómo se llega- Filtro: 80, 65, 70.
- 80 + 65 + 70 = 215.
- ¿Qué da SELECT MIN(puntos) FROM partida WHERE alumno = 1;?
ver la respuesta
80Cómo se llega- Partidas del alumno 1: 80 y 90.
- La menor es 80.
- Escribe la consulta que da el promedio de puntos de las partidas de equis.
ver la respuesta
SELECT AVG(puntos) FROM partida WHERE juego = 'equis'; (da 72.5)Cómo se llega- Filtro por juego, luego AVG.
- (90 + 55) ÷ 2 = 72.5.
- ¿Qué devuelve SELECT alumno, COUNT(*) AS n FROM partida GROUP BY alumno ORDER BY n DESC LIMIT 1;?
ver la respuesta
alumno 1 con n = 2Cómo se llega- Partidas por alumno: 1 → 2, 2 → 1, 3 → 1, 4 → 1.
- Ordenadas de más a menos y me quedo con la primera.
- ¿Qué está mal en: SELECT juego, puntos FROM partida GROUP BY juego;?
ver la respuesta
puntos no está en una función ni en el GROUP BY; sería SUM(puntos) o MAX(puntos)Cómo se llega- Con GROUP BY cada fila del resultado es un montón.
- De un montón de 3 partidas, ¿cuál puntos pondría?
INSERT, UPDATE, DELETE: meter, cambiar y borrar
Lección 6 de 8
INSERT mete una fila, UPDATE cambia filas, DELETE las borra. Las dos últimas llevan WHERE siempre: sin WHERE le pegan a toda la tabla.
Lo que hay que saber
Hasta ahora solo hemos mirado los datos. Hay tres órdenes que los cambian. Con estas sí hay que tener cuidado: no hay botón de deshacer.
INSERT mete una fila nueva. Dices la tabla, las columnas y los valores en el mismo orden. El id no se pone: SQLite le da el siguiente número solito.
INSERT INTO partida (alumno, juego, puntos, dia) VALUES (2, 'equis', 85, '2026-10-08');
Esto es lo que corre el sitio cada vez que terminas un juego. Los textos van entre comillas simples; los números no. Si das 4 columnas tienes que dar 4 valores, y en ese orden.
UPDATE cambia filas que ya existen. Dices la tabla, qué columna poner a qué valor con SET, y a cuáles filas con WHERE:
UPDATE alumno SET grupo = '1A' WHERE id = 3;
Caro (id 3) pasa a 1A. Solo ella, porque el WHERE la señala. Puedes cambiar varias columnas separando con comas: SET grupo = '1A', nombre = 'Carolina'.
DELETE borra filas completas. Tabla y WHERE:
DELETE FROM partida WHERE id = 5;
Ahora lo importante. ¿Qué pasa si escribes DELETE FROM partida; sin WHERE? Que la condición es «todas» y se borran todas las partidas. De todos los alumnos. De todo el año. Lo mismo con UPDATE alumno SET grupo = '1A'; sin WHERE: todos los alumnos de la escuela aparecen en 1A. Por eso la costumbre de los que trabajan con bases de datos es escribir primero el WHERE, o probar la condición con un SELECT antes: SELECT * FROM partida WHERE id = 5; te enseña qué vas a borrar. Si te gusta lo que ves, cambias SELECT * por DELETE.
Y una decisión de diseño de este sitio: nunca borra calificaciones ni partidas. Cuando hay que «limpiar» el periodo, no se usa DELETE: se guarda una fecha de corte y las consultas filtran con WHERE dia >= esa fecha. Una fila borrada no vuelve; una fila marcada se puede volver a mirar. Si algún día un alumno dice «yo sí jugué», el dato sigue ahí.
Así se hace, paso a paso
Corregir una partida que se capturó mal
La partida 2 dice 65 puntos, pero Beto sacó 75. Hay que corregirla, solo esa.
Paso 1 · Veo qué voy a tocar.
SELECT * FROM partida WHERE id = 2;
Sale una sola fila: id 2, alumno 2, recta, 65. Es la que quiero.
Paso 2 · Cambio SELECT * por UPDATE con el mismo WHERE.
UPDATE partida SET puntos = 75 WHERE id = 2;
Paso 3 · Compruebo. Vuelvo a correr el SELECT del paso 1: ahora dice 75. Las demás filas siguen igual.
Una sola fila cambió. Si hubiera olvidado el WHERE, las 5 partidas tendrían 75.
Olvidar el WHERE. DELETE FROM partida; borra todo. No hay deshacer. Escribe el WHERE primero.
Valores en otro orden. INSERT INTO alumno (nombre, grupo) VALUES ('1A', 'Ana') guarda un alumno llamado 1A del grupo Ana. Las columnas y los valores van en el mismo orden.
Creer que DELETE borra la tabla. DELETE borra filas; la tabla con sus columnas sigue ahí, vacía. (Borrar la tabla completa es otra orden, DROP TABLE, que mejor ni aprendas todavía.)
Ejercicios
- Escribe la orden para meter al alumno Fer en el grupo 1B.
ver la respuesta
INSERT INTO alumno (nombre, grupo) VALUES ('Fer', '1B');Cómo se llega- Columnas y valores en el mismo orden.
- Textos entre comillas; el id se pone solo.
- La tabla partida tiene 5 filas. Corres DELETE FROM partida WHERE juego = 'equis'; ¿Cuántas quedan?
ver la respuesta
3Cómo se llega- Filas de equis: 3 y 5. Se borran 2.
- 5 − 2 = 3.
- ¿Qué está mal en: UPDATE alumno grupo = '1B' WHERE id = 4;?
ver la respuesta
Falta SET antes de grupoCómo se llega- UPDATE tabla SET columna = valor WHERE condición.
- ¿Qué pasa si corres UPDATE partida SET puntos = 100; sin WHERE?
ver la respuesta
Todas las partidas quedan con 100 puntosCómo se llega- Sin WHERE la condición es «todas las filas».
- Escribe la orden que le suma 10 puntos a todas las partidas de recta (pista: puntos = puntos + 10).
ver la respuesta
UPDATE partida SET puntos = puntos + 10 WHERE juego = 'recta';Cómo se llega- SET puntos = puntos + 10 toma lo que hay y le suma 10.
- WHERE juego = 'recta' lo limita a esas filas.
- El maestro quiere «limpiar» las partidas del periodo pasado. ¿Por qué este sitio no usa DELETE y qué hace en su lugar?
ver la respuesta
Porque borrado no vuelve; guarda una fecha de corte y filtra con WHERE dia >= fechaCómo se llega- Marcar se puede deshacer; borrar no.
- Las consultas solo muestran lo que está después del corte.
Relaciones: dos tablas que se conectan
Lección 7 de 8
La partida guarda el id del alumno, no su nombre: eso es una llave foránea. JOIN junta las dos tablas por ese id para ver el nombre junto a la partida.
Lo que hay que saber
La tabla partida dice «alumno = 1», no «Ana». ¿Por qué no poner el nombre, que se entiende más? Porque el nombre se repetiría en cada partida (Ana juega 200 veces: 200 Anas), si Ana corrige su nombre habría que cambiarlo 200 veces, y si hay dos Anas no sabrías cuál. El id es único y no cambia; el nombre, no. Así que la partida guarda el id, y el nombre vive una sola vez en la tabla alumno.
Esa columna que apunta al id de otra tabla se llama llave foránea. alumno.id es la llave primaria; partida.alumno es la llave foránea que apunta a ella. Es la flecha del dibujo:
Cuando quieres ver el nombre junto a la partida, le pides a la base que junte las dos tablas por esa flecha. Eso es JOIN: «pega a cada partida la fila del alumno cuyo id coincida».
SELECT alumno.nombre, partida.juego, partida.puntos FROM partida JOIN alumno ON alumno.id = partida.alumno;
Se lee: «de partida, junta con alumno donde el id del alumno sea igual al alumno de la partida». Como ahora hay dos tablas, cada columna se escribe con su tabla adelante (alumno.nombre) para que no haya confusión. La base recorre cada partida, busca su alumno y arma una fila con datos de las dos:
| nombre | juego | puntos |
|---|---|---|
| Ana | recta | 80 |
| Beto | recta | 65 |
| Ana | equis | 90 |
| Caro | recta | 70 |
| Dani | equis | 55 |
Ana aparece dos veces porque tiene dos partidas; su nombre se escribió una sola vez en la tabla alumno y JOIN lo «copió» al momento. Eso es lo bonito: el dato vive en un lugar y se ve donde haga falta. Después del JOIN sigue todo lo demás igual: WHERE, GROUP BY, ORDER BY.
Así se hace, paso a paso
Los puntos totales de cada alumno, con su nombre
Paso 1 · ¿Qué necesito de cada tabla? El nombre está en alumno; los puntos en partida. Dos tablas: necesito JOIN.
Paso 2 · ¿Por dónde se unen? ON alumno.id = partida.alumno.
Paso 3 · Sumo por alumno. SUM(puntos) con GROUP BY alumno.nombre (lección de contar y sumar).
SELECT alumno.nombre, SUM(partida.puntos) AS total FROM partida JOIN alumno ON alumno.id = partida.alumno GROUP BY alumno.nombre ORDER BY total DESC;
Paso 4 · Compruebo a mano. Ana 80 + 90 = 170, Caro 70, Beto 65, Dani 55.
| nombre | total |
|---|---|
| Ana | 170 |
| Caro | 70 |
| Beto | 65 |
| Dani | 55 |
Así se arma la tabla de la sala: nombres con puntos, aunque partida solo guarde ids.
Unir por las columnas equivocadas. ON alumno.id = partida.id junta la partida 3 con el alumno 3, y no tiene nada que ver. La flecha va de partida.alumno a alumno.id.
Olvidar el ON. Sin ON la base junta cada partida con cada alumno: 5 partidas × 4 alumnos = 20 filas sin sentido.
Guardar el nombre «para no hacer JOIN». Al principio parece más fácil. Al primer nombre corregido o a la segunda Ana, deja de serlo.
Ejercicios
- En las tablas de arriba, ¿de quién es la partida con id 4?
ver la respuesta
De CaroCómo se llega- La partida 4 dice alumno = 3.
- En la tabla alumno, id 3 es Caro.
- ¿Cuál es la llave primaria y cuál la foránea en esta relación?
ver la respuesta
Primaria: alumno.id. Foránea: partida.alumnoCómo se llega- La primaria identifica la fila en su tabla.
- La foránea apunta a ella desde la otra tabla.
- Escribe la consulta que muestra nombre y puntos de las partidas de recta.
ver la respuesta
SELECT alumno.nombre, partida.puntos FROM partida JOIN alumno ON alumno.id = partida.alumno WHERE partida.juego = 'recta';Cómo se llega- JOIN por el id.
- Luego WHERE con el juego.
- ¿Cuántas filas da el JOIN de arriba si se agrega WHERE alumno.grupo = '1A'?
ver la respuesta
3Cómo se llega- En 1A están Ana (id 1) y Beto (id 2).
- Partidas del 1 y del 2: filas 1, 2 y 3.
- ¿Qué está mal en: SELECT nombre, puntos FROM partida JOIN alumno ON alumno.id = partida.id;?
ver la respuesta
Une por partida.id en vez de partida.alumnoCómo se llega- partida.id es el número de la partida, no del alumno.
- La flecha es partida.alumno → alumno.id.
- Se agrega a Eli (id 5, grupo 1A) sin partidas. ¿Aparece en el JOIN? ¿Por qué?
ver la respuesta
No: no hay ninguna partida con alumno = 5 que juntarCómo se llega- JOIN arma una fila por cada partida que encuentra su alumno.
- Sin partidas, no hay fila que armar.
Desde PHP: preguntarle a la base sin que te metan trampa
Lección 8 de 8
PHP abre la conexión, prepara la consulta con huecos (?) y pasa los valores aparte. Así lo que escribe el usuario nunca se vuelve SQL. Luego un foreach pinta la tabla.
Lo que hay que saber
Las consultas que escribiste en la consola las escribe, en la vida real, un programa. En este sitio ese programa es PHP. Son siempre cuatro pasos: abrir la conexión, preparar la consulta, ejecutar con los valores y recorrer el resultado.
$db = new PDO('sqlite:datos/sitio.db'); // 1. abrir
$q = $db->prepare('SELECT nombre, grupo FROM alumno WHERE grupo = ?'); // 2. preparar
$q->execute([$grupo]); // 3. ejecutar con el valor
$filas = $q->fetchAll(); // 4. traer las filas
Lo nuevo es el signo ?. En vez de escribir el grupo dentro del SQL, dejas un hueco, y el valor se pasa aparte en execute, dentro de un arreglo. La base recibe por un lado la orden y por el otro el dato, y nunca los confunde. Eso se llama consulta preparada.
¿Por qué tanto rodeo? Porque el grupo lo escribió alguien en un formulario, y no sabes qué escribió. Imagina que pegas el valor directo:
$sql = "SELECT nombre FROM alumno WHERE nombre = '" . $nombre . "'"; // ¡NO!
Si en el formulario escriben Ana, el SQL queda WHERE nombre = 'Ana'. Bien. Pero si alguien escribe ' OR 1=1, el SQL queda así:
SELECT nombre FROM alumno WHERE nombre = '' OR 1=1
Y como 1=1 siempre es cierto, pasan todas las filas: se lleva la lista completa. Y si escribe '; DELETE FROM alumno; … ya te imaginas. A esto se le llama inyección SQL: meter SQL por donde se esperaba un dato. Es el ataque más viejo y más común de internet, y la consulta preparada lo mata de raíz: con el ?, lo que escriban es un dato y nada más. Si escriben ' OR 1=1, la base busca un alumno que se llame literalmente «' OR 1=1» y no encuentra ninguno.
Lo último es mostrar el resultado. fetchAll devuelve un arreglo con una fila por elemento, y cada fila es un arreglo con las columnas. Con un foreach se pinta una tabla HTML:
<table> <?php foreach ($filas as $f): ?> <tr><td><?= esc($f['nombre']) ?></td><td><?= esc($f['grupo']) ?></td></tr> <?php endforeach; ?> </table>
Cada vuelta del foreach es una fila de la base y pinta un <tr>. El esc() es el mismo truco en la otra dirección: lo que viene de la base se muestra como texto, nunca como HTML. Los datos entran por un hueco y salen escapados; así ningún usuario puede meterle código a tu página ni a tu base.
Así se hace, paso a paso
Las partidas de un alumno, desde PHP
La página recibe el id del alumno en la dirección (?alumno=3) y quiere mostrar sus partidas.
Paso 1 · Tomo el valor sin confiar en él. $id = (int)($_GET['alumno'] ?? 0); lo convierto a entero, por si acaso.
Paso 2 · Preparo con hueco y ejecuto con el valor aparte.
$q = $db->prepare('SELECT juego, puntos FROM partida WHERE alumno = ? ORDER BY puntos DESC');
$q->execute([$id]);
$filas = $q->fetchAll();
Paso 3 · Recorro y pinto.
<?php foreach ($filas as $f): ?> <tr><td><?= esc($f['juego']) ?></td><td><?= (int)$f['puntos'] ?></td></tr> <?php endforeach; ?>
Paso 4 · Pruebo con un valor raro. Si en la dirección ponen ?alumno=3 OR 1=1, el (int) lo deja en 3 y el ? lo trata como dato. Salen solo las partidas del 3.
Con alumno = 3 salen las partidas de Caro ordenadas por puntos; con basura en la dirección no sale nada raro.
Pegar el valor con punto dentro del SQL. "… WHERE nombre = '" . $nombre . "'" es la puerta abierta a la inyección. Siempre ? y execute.
Preparar y no ejecutar. prepare solo deja lista la consulta; sin execute no pasa nada y $filas queda vacío.
Olvidar el arreglo en execute. Es execute([$grupo]), con corchetes, aunque sea un solo valor. Un ? por cada valor, en el mismo orden.
Ejercicios
- ¿En qué orden van los cuatro pasos para pedirle datos a la base desde PHP?
ver la respuesta
Abrir la conexión, preparar la consulta, ejecutar con los valores, recorrer el resultadoCómo se llega- new PDO, prepare, execute, fetchAll + foreach.
- ¿Para qué sirve el ? en prepare('… WHERE grupo = ?')?
ver la respuesta
Es un hueco: el valor se pasa aparte en execute y nunca se mezcla con el SQLCómo se llega- La base recibe la orden y el dato por separado.
- Escribe las dos líneas de PHP que buscan las partidas del juego guardado en $juego, usando un hueco.
ver la respuesta
$q = $db->prepare('SELECT * FROM partida WHERE juego = ?'); $q->execute([$juego]);Cómo se llega- El ? en lugar del valor.
- El valor en el arreglo de execute.
- Un formulario pide el nombre y el programa hace "… WHERE nombre = '" . $nombre . "'". Alguien escribe ' OR 1=1 ¿Qué devuelve la consulta?
ver la respuesta
Todos los alumnos, porque 1=1 siempre es ciertoCómo se llega- El SQL queda WHERE nombre = '' OR 1=1.
- OR con algo siempre cierto deja pasar todas las filas.
- Con el mismo formulario pero usando prepare con ? y execute, alguien escribe ' OR 1=1 ¿Qué pasa?
ver la respuesta
Nada: busca un alumno llamado «' OR 1=1» y no encuentra ningunoCómo se llega- Con el hueco, lo escrito es un dato, no SQL.
- fetchAll devolvió 7 filas. ¿Cuántos <tr> pinta el foreach? ¿Y si devolvió 0?
ver la respuesta
7 renglones; con 0 filas no pinta ningunoCómo se llega- Una vuelta del foreach por cada fila.
- Con el arreglo vacío el foreach no entra ni una vez.
El examen de bases de datos y sql
Diez preguntas de todas las lecciones, del mismo nivel que los ejercicios. Hay cuatro versiones parecidas: si no te va bien, lo repites con otra. Se aprueba con 7 de 10.
Puedes hacerlo sin entrar, pero si entras con tu nombre el resultado se guarda en tu tablero.
Hacer el examen →