Apuntes DAM
Volver al inicio

Liga de baloncesto: clasificación y funciones de ventana

Ejercicio de SQLMuy difícilUnos 90 minutos

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

  1. Escribe una consulta para cada apartado.
  2. 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.
  3. 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 ROUND cuando 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.

equipos
equipo_idnombreciudad
1Leones de MadridMadrid
2Tiburones de ValenciaValencia
3Halcones de SevillaSevilla
4Osos de BilbaoBilbao
jugadores
jugador_idnombreequipo_idposicion
1Álex Moreno1base
2Iker Gil1alero
3Dani Prieto1pívot
4Raúl Vidal2base
5Marc Soler2alero
6Joan Ferrer2pívot
7Pablo Romero3base
8Luis Cano3alero
9Sergio Lara3pívot
10Unai Etxeberria4base
11Jon Ibarra4alero
12Mikel Arana4pívot
partidos
partido_idjornadafechalocal_idvisitante_idpuntos_localpuntos_visitante
112026-10-03126258
212026-10-03345661
322026-10-10236463
422026-10-10415660
532026-10-17135966
632026-10-17246166
estadisticas
partido_idjugador_idpuntosrebotesasistencias
112437
121852
1320111
142148
152563
161291
271529
282272
2919120
2102746
2111463
21220102
3418310
353052
361681
372037
382261
3921132
4101959
4112242
4121591
412846
421863
4314122
512128
522171
5317103
572546
581852
5923111
642227
651964
662090
6102435
6112472
6121881
Ver el script SQL que crea la base de datos
sql
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.

sql
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».

🗄️SQLApartado 1Muy difícil

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.

🗄️SQLApartado 2Muy difícil

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.

🗄️SQLApartado 3Muy difícil

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.

🗄️SQLApartado 4Muy difícil

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.

🗄️SQLApartado 5Muy difícil

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.

🗄️SQLApartado 6Muy difícil

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.

🗄️SQLApartado 7Muy difícil

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

sql
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

sql
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

sql
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

sql
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

sql
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

sql
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

sql
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 LAG sobre 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 JOIN de la tabla consigo misma.

Dónde se explica