Liga de baloncesto: clasificación y funciones de ventana
Siete consultas sobre una liga de baloncesto de cuatro equipos: la clasificación con UNION ALL, el máximo anotador de cada partido, un ranking con empates (RANK), puntos acumulados, la evolución con LAG, el porcentaje de los puntos del equipo y la comparación con la media de cada posición.
- UNION ALL
- CTE (WITH)
- ROW_NUMBER, RANK
- SUM() OVER
- LAG
- PARTITION BY
- AVG() OVER
Enunciado
Una liga de baloncesto amateur de cuatro equipos lleva tres jornadas. Cada partido enfrenta a un equipo local y uno visitante, y la tabla estadisticas guarda los puntos, rebotes y asistencias de cada jugador en cada partido (cada equipo tiene tres jugadores en esta versión reducida: los puntos de un equipo en un partido son la suma de los de sus jugadores).
Las preguntas de una liga (quién es el máximo anotador de cada partido, cómo va el acumulado de cada jugador, quién está por encima de la media de su posición) son el terreno de las funciones de ventana: calculan algo sobre un grupo de filas sin agruparlas, así que cada fila conserva sus datos y además recibe el ranking, el acumulado o la media de su grupo.
Qué hay que hacer
- Escribe una consulta para cada apartado.
- Cada apartado se corrige por separado sobre una copia nueva de la base de datos, con exactamente los datos de las tablas de arriba: lo que hagas en uno no afecta a los demás.
- Importan las filas, el número y el orden de las columnas y, cuando el apartado pide un orden, el orden de las filas; los nombres de las columnas no importan. Redondea con
ROUNDcuando se pidan decimales.
Las tablas y sus datos
Base de datos del ejercicio · 4 tablas
- equipos
- jugadores
- partidos
- estadisticas
Subrayada, la clave primaria; con flecha, la columna a la que apunta cada clave ajena.
| equipo_id | nombre | ciudad |
|---|---|---|
| 1 | Leones de Madrid | Madrid |
| 2 | Tiburones de Valencia | Valencia |
| 3 | Halcones de Sevilla | Sevilla |
| 4 | Osos de Bilbao | Bilbao |
| jugador_id | nombre | equipo_id | posicion |
|---|---|---|---|
| 1 | Álex Moreno | 1 | base |
| 2 | Iker Gil | 1 | alero |
| 3 | Dani Prieto | 1 | pívot |
| 4 | Raúl Vidal | 2 | base |
| 5 | Marc Soler | 2 | alero |
| 6 | Joan Ferrer | 2 | pívot |
| 7 | Pablo Romero | 3 | base |
| 8 | Luis Cano | 3 | alero |
| 9 | Sergio Lara | 3 | pívot |
| 10 | Unai Etxeberria | 4 | base |
| 11 | Jon Ibarra | 4 | alero |
| 12 | Mikel Arana | 4 | pívot |
| partido_id | jornada | fecha | local_id | visitante_id | puntos_local | puntos_visitante |
|---|---|---|---|---|---|---|
| 1 | 1 | 2026-10-03 | 1 | 2 | 62 | 58 |
| 2 | 1 | 2026-10-03 | 3 | 4 | 56 | 61 |
| 3 | 2 | 2026-10-10 | 2 | 3 | 64 | 63 |
| 4 | 2 | 2026-10-10 | 4 | 1 | 56 | 60 |
| 5 | 3 | 2026-10-17 | 1 | 3 | 59 | 66 |
| 6 | 3 | 2026-10-17 | 2 | 4 | 61 | 66 |
| partido_id | jugador_id | puntos | rebotes | asistencias |
|---|---|---|---|---|
| 1 | 1 | 24 | 3 | 7 |
| 1 | 2 | 18 | 5 | 2 |
| 1 | 3 | 20 | 11 | 1 |
| 1 | 4 | 21 | 4 | 8 |
| 1 | 5 | 25 | 6 | 3 |
| 1 | 6 | 12 | 9 | 1 |
| 2 | 7 | 15 | 2 | 9 |
| 2 | 8 | 22 | 7 | 2 |
| 2 | 9 | 19 | 12 | 0 |
| 2 | 10 | 27 | 4 | 6 |
| 2 | 11 | 14 | 6 | 3 |
| 2 | 12 | 20 | 10 | 2 |
| 3 | 4 | 18 | 3 | 10 |
| 3 | 5 | 30 | 5 | 2 |
| 3 | 6 | 16 | 8 | 1 |
| 3 | 7 | 20 | 3 | 7 |
| 3 | 8 | 22 | 6 | 1 |
| 3 | 9 | 21 | 13 | 2 |
| 4 | 10 | 19 | 5 | 9 |
| 4 | 11 | 22 | 4 | 2 |
| 4 | 12 | 15 | 9 | 1 |
| 4 | 1 | 28 | 4 | 6 |
| 4 | 2 | 18 | 6 | 3 |
| 4 | 3 | 14 | 12 | 2 |
| 5 | 1 | 21 | 2 | 8 |
| 5 | 2 | 21 | 7 | 1 |
| 5 | 3 | 17 | 10 | 3 |
| 5 | 7 | 25 | 4 | 6 |
| 5 | 8 | 18 | 5 | 2 |
| 5 | 9 | 23 | 11 | 1 |
| 6 | 4 | 22 | 2 | 7 |
| 6 | 5 | 19 | 6 | 4 |
| 6 | 6 | 20 | 9 | 0 |
| 6 | 10 | 24 | 3 | 5 |
| 6 | 11 | 24 | 7 | 2 |
| 6 | 12 | 18 | 8 | 1 |
Ver el script SQL que crea la base de datos
1CREATE TABLE equipos (
2 equipo_id INTEGER PRIMARY KEY,
3 nombre TEXT NOT NULL,
4 ciudad TEXT NOT NULL
5);
6CREATE TABLE jugadores (
7 jugador_id INTEGER PRIMARY KEY,
8 nombre TEXT NOT NULL,
9 equipo_id INTEGER NOT NULL REFERENCES equipos(equipo_id),
10 posicion TEXT NOT NULL
11);
12CREATE TABLE partidos (
13 partido_id INTEGER PRIMARY KEY,
14 jornada INTEGER NOT NULL,
15 fecha DATE NOT NULL,
16 local_id INTEGER NOT NULL REFERENCES equipos(equipo_id),
17 visitante_id INTEGER NOT NULL REFERENCES equipos(equipo_id),
18 puntos_local INTEGER NOT NULL,
19 puntos_visitante INTEGER NOT NULL
20);
21CREATE TABLE estadisticas (
22 partido_id INTEGER NOT NULL REFERENCES partidos(partido_id),
23 jugador_id INTEGER NOT NULL REFERENCES jugadores(jugador_id),
24 puntos INTEGER NOT NULL,
25 rebotes INTEGER NOT NULL,
26 asistencias INTEGER NOT NULL,
27 PRIMARY KEY (partido_id, jugador_id)
28);
29
30INSERT INTO equipos (equipo_id, nombre, ciudad) VALUES
31 (1, 'Leones de Madrid', 'Madrid'),
32 (2, 'Tiburones de Valencia', 'Valencia'),
33 (3, 'Halcones de Sevilla', 'Sevilla'),
34 (4, 'Osos de Bilbao', 'Bilbao');
35
36INSERT INTO jugadores (jugador_id, nombre, equipo_id, posicion) VALUES
37 (1, 'Álex Moreno', 1, 'base'),
38 (2, 'Iker Gil', 1, 'alero'),
39 (3, 'Dani Prieto', 1, 'pívot'),
40 (4, 'Raúl Vidal', 2, 'base'),
41 (5, 'Marc Soler', 2, 'alero'),
42 (6, 'Joan Ferrer', 2, 'pívot'),
43 (7, 'Pablo Romero', 3, 'base'),
44 (8, 'Luis Cano', 3, 'alero'),
45 (9, 'Sergio Lara', 3, 'pívot'),
46 (10, 'Unai Etxeberria', 4, 'base'),
47 (11, 'Jon Ibarra', 4, 'alero'),
48 (12, 'Mikel Arana', 4, 'pívot');
49
50INSERT INTO partidos (partido_id, jornada, fecha, local_id, visitante_id, puntos_local, puntos_visitante) VALUES
51 (1, 1, '2026-10-03', 1, 2, 62, 58),
52 (2, 1, '2026-10-03', 3, 4, 56, 61),
53 (3, 2, '2026-10-10', 2, 3, 64, 63),
54 (4, 2, '2026-10-10', 4, 1, 56, 60),
55 (5, 3, '2026-10-17', 1, 3, 59, 66),
56 (6, 3, '2026-10-17', 2, 4, 61, 66);
57
58INSERT INTO estadisticas (partido_id, jugador_id, puntos, rebotes, asistencias) VALUES
59 (1, 1, 24, 3, 7),
60 (1, 2, 18, 5, 2),
61 (1, 3, 20, 11, 1),
62 (1, 4, 21, 4, 8),
63 (1, 5, 25, 6, 3),
64 (1, 6, 12, 9, 1),
65 (2, 7, 15, 2, 9),
66 (2, 8, 22, 7, 2),
67 (2, 9, 19, 12, 0),
68 (2, 10, 27, 4, 6),
69 (2, 11, 14, 6, 3),
70 (2, 12, 20, 10, 2),
71 (3, 4, 18, 3, 10),
72 (3, 5, 30, 5, 2),
73 (3, 6, 16, 8, 1),
74 (3, 7, 20, 3, 7),
75 (3, 8, 22, 6, 1),
76 (3, 9, 21, 13, 2),
77 (4, 10, 19, 5, 9),
78 (4, 11, 22, 4, 2),
79 (4, 12, 15, 9, 1),
80 (4, 1, 28, 4, 6),
81 (4, 2, 18, 6, 3),
82 (4, 3, 14, 12, 2),
83 (5, 1, 21, 2, 8),
84 (5, 2, 21, 7, 1),
85 (5, 3, 17, 10, 3),
86 (5, 7, 25, 4, 6),
87 (5, 8, 18, 5, 2),
88 (5, 9, 23, 11, 1),
89 (6, 4, 22, 2, 7),
90 (6, 5, 19, 6, 4),
91 (6, 6, 20, 9, 0),
92 (6, 10, 24, 3, 5),
93 (6, 11, 24, 7, 2),
94 (6, 12, 18, 8, 1);Guía paso a paso
Intenta resolverlo por tu cuenta y abre un paso solo cuando te atasques: cada uno te acerca a la solución sin dártela entera.
1. Agregar o ventana
GROUP BY convierte cada grupo en una sola fila. Una función de ventana (… OVER (PARTITION BY …)) calcula sobre el grupo pero deja todas las filas: cada jugador conserva su fila y además recibe el total de su equipo o su puesto en el ranking.
2. PARTITION BY y ORDER BY dentro de OVER
PARTITION BY define el grupo (cada partido, cada jugador, cada equipo) y ORDER BY el orden dentro del grupo. Con ORDER BY, SUM se vuelve acumulada: suma desde la primera fila hasta la actual.
SUM(s.puntos) OVER (PARTITION BY j.jugador_id ORDER BY p.jornada)3. Ventana después de agrupar
Si la consulta tiene GROUP BY, las funciones de ventana se calculan después: RANK() OVER (ORDER BY SUM(puntos) DESC) ordena los totales de cada jugador.
4. Filtrar por una función de ventana
No se puede escribir WHERE ROW_NUMBER() … = 1: el WHERE se evalúa antes. Calcula la ventana en una CTE (WITH) y filtra en la consulta de fuera.
5. Dos puntos de vista de un partido
Un partido aporta una fila al local y otra al visitante en la clasificación. UNION ALL de dos SELECT (uno para cada papel, con las columnas en el mismo orden) los pone en una sola tabla.
Resuélvelo aquí
Cada apartado se corrige por separado contra la base de datos de arriba: escribe la consulta y pulsa «Comprobar».
La clasificación: equipo, partidos jugados, victorias, derrotas, puntos a favor, puntos en contra y diferencia, ordenada por victorias (de más a menos), después por diferencia (de más a menos) y por último por nombre.
El máximo anotador de cada partido: número de partido, jugador y puntos, por número de partido. Si dos jugadores empatan, el primero por nombre.
El ranking de anotadores de la liga: puesto, jugador, equipo y puntos totales. Los empatados comparten puesto y el siguiente se salta (como en RANK). Ordenado por puesto y, a igualdad, por nombre del jugador.
Los puntos de cada jugador de los Leones de Madrid en cada jornada y su acumulado hasta esa jornada: jugador, jornada, puntos y acumulado, por jugador y jornada.
La evolución de los jugadores de los Halcones de Sevilla: jugador, jornada, puntos y diferencia con sus puntos de la jornada anterior (NULL en la primera jornada), por jugador y jornada.
Qué parte de los puntos de su equipo ha anotado cada jugador en toda la liga: equipo, jugador, puntos y porcentaje sobre el total del equipo con un decimal, por equipo y de mayor a menor porcentaje.
Los jugadores que promedian más puntos por partido que la media de su posición: jugador, posición, su media y la media de la posición (las dos con un decimal), por posición y de mayor a menor media del jugador.
Solución explicada
Ver las soluciones de todos los apartados
Apartado 1
WITH resultados AS (
SELECT local_id AS equipo_id, puntos_local AS favor, puntos_visitante AS contra FROM partidos
UNION ALL
SELECT visitante_id, puntos_visitante, puntos_local FROM partidos
)
SELECT e.nombre, COUNT(*) AS jugados,
SUM(r.favor > r.contra) AS victorias,
SUM(r.favor < r.contra) AS derrotas,
SUM(r.favor) AS favor, SUM(r.contra) AS contra,
SUM(r.favor) - SUM(r.contra) AS diferencia
FROM resultados r
JOIN equipos e ON e.equipo_id = r.equipo_id
GROUP BY e.equipo_id
ORDER BY victorias DESC, diferencia DESC, e.nombre;Apartado 2
WITH ranking AS (
SELECT s.partido_id, j.nombre, s.puntos,
ROW_NUMBER() OVER (PARTITION BY s.partido_id ORDER BY s.puntos DESC, j.nombre) AS n
FROM estadisticas s
JOIN jugadores j ON j.jugador_id = s.jugador_id
)
SELECT partido_id, nombre, puntos FROM ranking WHERE n = 1 ORDER BY partido_id;Apartado 3
SELECT RANK() OVER (ORDER BY SUM(s.puntos) DESC) AS puesto,
j.nombre, e.nombre, SUM(s.puntos) AS puntos
FROM estadisticas s
JOIN jugadores j ON j.jugador_id = s.jugador_id
JOIN equipos e ON e.equipo_id = j.equipo_id
GROUP BY j.jugador_id
ORDER BY puesto, j.nombre;Apartado 4
SELECT j.nombre, p.jornada, s.puntos,
SUM(s.puntos) OVER (PARTITION BY j.jugador_id ORDER BY p.jornada) AS acumulado
FROM estadisticas s
JOIN jugadores j ON j.jugador_id = s.jugador_id
JOIN partidos p ON p.partido_id = s.partido_id
WHERE j.equipo_id = 1
ORDER BY j.nombre, p.jornada;Apartado 5
SELECT j.nombre, p.jornada, s.puntos,
s.puntos - LAG(s.puntos) OVER (PARTITION BY j.jugador_id ORDER BY p.jornada) AS diferencia
FROM estadisticas s
JOIN jugadores j ON j.jugador_id = s.jugador_id
JOIN partidos p ON p.partido_id = s.partido_id
WHERE j.equipo_id = 3
ORDER BY j.nombre, p.jornada;Apartado 6
WITH totales AS (
SELECT j.equipo_id, j.nombre, SUM(s.puntos) AS puntos
FROM estadisticas s
JOIN jugadores j ON j.jugador_id = s.jugador_id
GROUP BY j.jugador_id
)
SELECT e.nombre, t.nombre, t.puntos,
ROUND(100.0 * t.puntos / SUM(t.puntos) OVER (PARTITION BY t.equipo_id), 1) AS porcentaje
FROM totales t
JOIN equipos e ON e.equipo_id = t.equipo_id
ORDER BY e.nombre, porcentaje DESC;Apartado 7
WITH medias AS (
SELECT j.nombre, j.posicion, AVG(s.puntos) AS media
FROM estadisticas s
JOIN jugadores j ON j.jugador_id = s.jugador_id
GROUP BY j.jugador_id
), comparadas AS (
SELECT nombre, posicion, media, AVG(media) OVER (PARTITION BY posicion) AS media_posicion
FROM medias
)
SELECT nombre, posicion, ROUND(media, 1), ROUND(media_posicion, 1)
FROM comparadas
WHERE media > media_posicion
ORDER BY posicion, media DESC;La clasificación muestra un truco general: cuando una fila habla de dos cosas (local y visitante), se «desdobla» con UNION ALL para tener una fila por equipo y partido. A partir de ahí todo es un GROUP BY normal.
ROW_NUMBER, RANK y DENSE_RANK numeran de forma distinta los empates: en el ranking, Jon Ibarra y Pablo Romero comparten el 7.º puesto y el siguiente es el 9.º. Elegir la función es elegir la regla de los empates.
Con ORDER BY dentro de OVER, SUM se convierte en un acumulado, y LAG mira la fila anterior del mismo grupo. Son las dos funciones de ventana que más se usan en informes de evolución (ventas mes a mes, puntos jornada a jornada).
Los dos últimos apartados combinan agregación y ventanas en varias CTE: primero se resume por jugador y después se compara cada jugador con su grupo. Encadenar CTE con nombres claros mantiene legible una consulta que, con subconsultas anidadas, sería difícil de seguir.
Para ir más allá
- Añade la racha de cada equipo (victorias seguidas) con
LAGsobre sus partidos ordenados por jornada. - Calcula la valoración de cada jugador en cada partido (puntos + rebotes + asistencias) y el mejor de cada jornada.
- Genera el calendario completo de ida y vuelta para los cuatro equipos con un
CROSS JOINde la tabla consigo misma.