Saltar a contenido

SQL Lab: grupos

Ejercicio sql-lab-groups-annex.pdf · PDF

¿Cuántos Pokémon de fuego hay? Sabes cómo averiguarlo: una consulta con WHERE type_1 = 'fire', y la respuesta es 59. Ahora haz agua. Luego planta. Luego los otros quince tipos.

Dieciocho consultas para una pregunta pequeña. Nadie quiere eso. Y las preguntas que la gente hace de verdad a los datos casi nunca son "enséñame las cartas". Son cuántos, cuánto, cuál es la media, qué grupo gana. En la última lección cada respuesta era una lista de cartas. En esta, la respuesta es un número sobre muchas cartas, y después el mismo número para todos los grupos a la vez.

Qué aprenderás

  • Resumir una tabla entera en una fila con las funciones de agregación COUNT, SUM, AVG, MIN y MAX
  • Explicar por qué COUNT(*) y COUNT(columna) pueden dar números distintos en la misma tabla
  • Dividir una tabla en grupos con GROUP BY y calcular una cifra por grupo
  • Quedarte solo con algunos grupos con HAVING
  • Desactivar la trampa del día: WHERE y HAVING no son lo mismo
  • Leer el error must appear in the GROUP BY clause y corregir la consulta

Un número para toda la caja

Una función de agregación lee muchas filas y devuelve un valor.

Mantenemos la caja de zapatos de SQL Lab: filtros: la tabla pokemon es una caja con 949 cartas coleccionables, una fila es una carta, una columna es una línea impresa en ella. Una función de agregación lee un montón entero de cartas y dice un número.

Función Con la caja de zapatos En pokemon
COUNT(*) Cuántas cartas hay 949
MIN(x), MAX(x) El valor más pequeño y el más grande impresos en esa línea attack: 5 y 190
SUM(x) Suma esa línea de todas las cartas attack: 75419
AVG(x) La suma dividida entre cuántas hay: la media attack: 79.47...

Abre SQL → SQL Lab (base de datos Dades classe, esquema public) y ejecuta:

SELECT MIN(attack), MAX(attack), SUM(attack), AVG(attack) FROM pokemon;
min max sum avg
5 190 75419 79.4720758693361433

Entraron 949 cartas. Salió una fila. Esa es la marca de una agregación: por grande que sea la tabla, obtienes una sola línea.

La media es correcta e ilegible. Dos herramientas pequeñas lo arreglan:

SELECT ROUND(AVG(attack), 1) AS avg_attack FROM pokemon;
avg_attack
79.5
  • ROUND(valor, 1) redondea a un decimal. ROUND(valor) redondea a un número entero.
  • AS da a la columna del resultado un nombre que eliges tú. Ese nombre se llama alias.

Una agregación funciona junto con WHERE tal como esperarías: primero se sacan las cartas, después se cuentan. SELECT COUNT(*) FROM pokemon WHERE type_1 = 'fire' devuelve 59.

COUNT(*) no es COUNT(columna)

La misma tabla, tres recuentos:

SELECT COUNT(*) AS total,
       COUNT(type_2) AS with_second_type,
       COUNT(DISTINCT type_1) AS types
FROM pokemon;
total with_second_type types
949 510 18

949 y 510, de la misma caja. ¿Quién miente? Nadie. Responden a preguntas distintas:

Escribes Cuenta Resultado
COUNT(*) Cartas 949
COUNT(type_2) Cartas donde la línea type_2 no está en blanco 510
COUNT(DISTINCT type_1) Valores distintos de la línea type_1 18

Y 949 − 510 = 439: los Pokémon de un solo tipo que encontraste con IS NULL en la lección anterior.

Cuidado: las agregaciones se saltan los blancos

COUNT(columna), SUM, AVG, MIN y MAX ignoran NULL. Normalmente es lo que quieres: una media de las cartas que tienen valor. Pero significa que una media puede calcularse sobre menos filas de las que crees. Cuando un número importa, pon COUNT(*) y COUNT(columna) uno al lado del otro y mira cuántas cartas participaron de verdad.

GROUP BY: haz montones

GROUP BY divide las filas en grupos, un grupo por cada valor distinto de una columna, y devuelve una fila de resultado por grupo.

Coge una baraja y repártela en montones, uno por palo. ¿Cuántos montones? Uno por cada palo distinto. Ahora cuenta cada montón. ¿Cuántos números obtienes? Uno por montón.

Eso es todo lo que hace GROUP BY: un montón por valor distinto, una fila de resultado por montón.

SELECT type_1, COUNT(*) AS n
FROM pokemon
GROUP BY type_1
ORDER BY n DESC;

18 filas. Las tres primeras y la última:

type_1 n
water 126
normal 111
grass 84
... ...
flying 4

Las dieciocho consultas del principio de la página, en cuatro líneas. ¿Recuerdas SELECT DISTINCT type_1? También devolvía 18 filas. Los mismos montones; ahora además se cuenta cada montón.

Puedes pedir varias cifras de cada montón a la vez:

SELECT type_1, COUNT(*) AS n, ROUND(AVG(attack)) AS avg_attack
FROM pokemon
GROUP BY type_1
ORDER BY avg_attack DESC;
type_1 n avg_attack
dragon 39 109
fighting 31 99
ground 36 96
steel 30 93
... ... ...
flowchart LR
    A["<b>La caja</b><br>949 cartas"] --> B["<b>GROUP BY type_1</b><br>repártelas en montones"]
    B --> P1["water<br>126 cartas"]
    B --> P2["normal<br>111 cartas"]
    B --> P3["...16 montones más"]
    P1 --> R["<b>Resultado</b><br>18 filas:<br>una por montón"]
    P2 --> R
    P3 --> R

Cuidado: GROUP BY no ordena

Los montones salen en el orden que a la base de datos le resulte cómodo. Si quieres el montón más grande primero, dilo con ORDER BY. Y los blancos no se tiran: las filas con un NULL en la columna de agrupación forman su propio montón. Lo encontrarás en "Pruébalo".

El error que todo el mundo encuentra

Añade el nombre del Pokémon a la consulta anterior:

SELECT type_1, pokemon, COUNT(*) FROM pokemon GROUP BY type_1;
ERROR:  column "pokemon.pokemon" must appear in the GROUP BY clause
        or be used in an aggregate function

Léelo con los montones delante. El montón de agua tiene 126 cartas. ¿Qué un nombre debería imprimir la base de datos para todo el montón? No puede elegir, así que se niega.

La regla: en una consulta agrupada, cada columna de SELECT es aquello por lo que has hecho los montones, o va dentro de una función de agregación.

HAVING: tira montones enteros

HAVING se queda solo con los grupos para los que una condición es verdadera. Es un filtro para montones, no para cartas.

Mira otra vez la última fila del primer resultado: flying, 4 cartas. Una media de cuatro cartas no vale gran cosa. Quédate solo con los montones grandes:

SELECT type_1, COUNT(*) AS n, ROUND(AVG(attack)) AS avg_attack
FROM pokemon
GROUP BY type_1
HAVING COUNT(*) >= 50
ORDER BY avg_attack DESC;
type_1 n avg_attack
rock 65 90
fire 59 84
normal 111 76
grass 84 75
water 126 75
psychic 64 73
bug 79 72
electric 61 68

Quedan ocho montones. dragon, el ganador hace un momento, ha desaparecido: solo tiene 39 cartas.

Cuidado: nada de alias en HAVING

Es tentador escribir HAVING n >= 50, usando el alias de SELECT. PostgreSQL lo rechaza: en ese punto todavía no conoce n. Repite la función: HAVING COUNT(*) >= 50. En ORDER BY el alias sí funciona, como has visto más arriba.

¿WHERE o HAVING? Cartas o montones

Esta es la idea que más se confunde. Las dos líneas filtran. Filtran cosas distintas, en momentos distintos.

WHERE HAVING
Filtra Cartas (filas) Montones (grupos)
Cuándo Antes de hacer los montones Después de contar los montones
La condición trata de Una carta: type_2 IS NULL, attack > 100 Un montón entero: COUNT(*) >= 50, AVG(speed) > 80
¿Puede usar una agregación? No Sí

Por eso esto falla:

SELECT type_1, COUNT(*) FROM pokemon WHERE COUNT(*) >= 50 GROUP BY type_1;
ERROR:  aggregate functions are not allowed in WHERE

Cuando se ejecuta WHERE, todavía no hay montones. No hay nada que contar.

Una consulta puede usar las dos. Ataque medio por tipo, solo los tipos con al menos 50 Pokémon, ignorando las evoluciones Mega:

SELECT type_1, COUNT(*) AS n, ROUND(AVG(attack)) AS avg_attack
FROM pokemon
WHERE pokemon NOT LIKE '%-mega%'
GROUP BY type_1
HAVING COUNT(*) >= 50
ORDER BY avg_attack DESC;
type_1 n avg_attack
rock 62 87
fire 55 81
normal 107 75
... ... ...

"No es una Mega" es una pregunta sobre cada carta: WHERE. "Tiene al menos 50" es una pregunta sobre cada montón: HAVING. Compara con la tabla anterior: rock bajó de 90 a 87 y fire de 84 a 81. Las evoluciones Mega son muy fuertes, así que estaban subiendo las medias.

El orden real de una consulta

Ya conoces siete líneas. Las escribes en un orden. La base de datos las ejecuta en otro, y ese orden explica todas las reglas de esta página.

Lo escribes Se ejecuta Con la caja de zapatos
SELECT 5.º Lee las líneas que has pedido y calcula las agregaciones
FROM 1.º Abre la caja
WHERE 2.º Saca cartas, una por una
GROUP BY 3.º Haz montones con las cartas que quedan
HAVING 4.º Tira montones enteros
ORDER BY 6.º Ordena las filas del resultado
LIMIT 7.º Quédate con las primeras
flowchart LR
    F["<b>1 · FROM</b><br>abre la caja"] --> W["<b>2 · WHERE</b><br>saca cartas"]
    W --> G["<b>3 · GROUP BY</b><br>haz montones"]
    G --> H["<b>4 · HAVING</b><br>tira montones enteros"]
    H --> S["<b>5 · SELECT</b><br>lee y calcula"]
    S --> O["<b>6 · ORDER BY</b><br>ordena"]
    O --> L["<b>7 · LIMIT</b><br>quédate con lo de arriba"]

WHERE se ejecuta antes de que existan los montones, así que no puede contarlos. HAVING se ejecuta antes que SELECT, así que no conoce tus alias. ORDER BY se ejecuta después de SELECT, así que sí.

Pruébalo: ¿qué tipo gana?

Una consulta por pregunta, en SQL Lab. Escribe primero la tuya y luego compara.

N. Pregunta
1 hp mínimo, máximo y medio de toda la tabla (la media con un decimal), en una sola consulta.
2 Velocidad media por primer tipo, el más rápido primero. ¿Cuáles son los tres primeros?
3 Lo mismo, pero solo los tipos con al menos 30 Pokémon y una velocidad media superior a 80. ¿Quién se cae?
4 ¿Cuántos Pokémon hay por generation_id? ¿Cuántas filas salen y qué tiene de raro una de ellas?
5 Para cada primer tipo: cuántos Pokémon hay y cuántos de ellos tienen un segundo tipo. Una consulta.
Soluciones

1 — SELECT MIN(hp), MAX(hp), ROUND(AVG(hp), 1) FROM pokemon; → 1, 255 y 69.0 (la cuadrícula de SQL Lab lo muestra como 69).

2 — SELECT type_1, ROUND(AVG(speed)) AS avg_speed FROM pokemon GROUP BY type_1 ORDER BY avg_speed DESC; → flying 103, electric 87, dragon 83.

3 — Añade HAVING COUNT(*) >= 30 AND AVG(speed) > 80 después de la línea GROUP BY → electric 87, dragon 83, psychic 81. flying se cae: era el primero, pero con solo 4 cartas.

4 — SELECT generation_id, COUNT(*) AS n FROM pokemon GROUP BY generation_id ORDER BY generation_id; → 8 filas, no 7: 151, 100, 135, 107, 156, 72 y 81 para las generaciones 1 a 7, y un montón con el generation_id en blanco y 147 cartas.

5 — SELECT type_1, COUNT(*) AS n, COUNT(type_2) AS with_second FROM pokemon GROUP BY type_1 ORDER BY n DESC; → water 126 y 60, normal 111 y 44, grass 84 y 45...

La pregunta 4 deja dos cabos sueltos. ¿Quiénes son las 147 cartas sin generación? ¿Y qué es la generación 5: qué región, qué año? La tabla tiene un número, pero no lo que significa.

Ideas clave

  • Una función de agregación (COUNT, SUM, AVG, MIN, MAX) convierte muchas filas en un valor. ROUND lo ordena, AS le pone nombre con un alias.
  • COUNT(*) cuenta filas; COUNT(columna) cuenta las filas donde esa columna no está en blanco. Las agregaciones se saltan NULL.
  • GROUP BY hace un montón por cada valor distinto y devuelve una fila por montón. No ordena, y los blancos forman su propio montón.
  • En una consulta agrupada, cada columna de SELECT es una clave de montón o está dentro de una agregación.
  • WHERE filtra cartas antes de hacer los montones; HAVING filtra montones después de contarlos.
  • La base de datos ejecuta FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY → LIMIT.

Comprobación rápida

1. ¿Cuántas filas devuelve SELECT MAX(attack), AVG(attack) FROM pokemon;?
  • 949, una por Pokémon
  • 2, una por función
  • 1
  • 18, una por tipo

Una función de agregación lee muchas filas y devuelve un valor. Sin GROUP BY, la tabla entera es un único montón, así que el resultado es una fila, aquí con dos columnas.

2. En pokemon, COUNT(*) devuelve 949 y COUNT(type_2) devuelve 510. ¿Por qué?
  • COUNT(type_2) cuenta los valores distintos de type_2
  • COUNT(type_2) solo cuenta las filas donde type_2 no está en blanco
  • COUNT(*) cuenta algunas filas dos veces
  • 439 filas tienen un error en type_2

COUNT(*) cuenta cartas. COUNT(columna) cuenta las cartas donde esa línea no es NULL. Las 439 que faltan son los Pokémon de un solo tipo. Los valores distintos serían COUNT(DISTINCT type_2).

3. SELECT type_1, COUNT(*) FROM pokemon GROUP BY type_1; devuelve 18 filas. ¿Qué representa cada fila?
  • Un Pokémon
  • Un montón: todos los Pokémon que comparten el mismo primer tipo
  • Una columna de la tabla
  • Una generación

GROUP BY hace un montón por cada valor distinto de la columna y devuelve una fila por montón. Hay 18 primeros tipos distintos, así que hay 18 montones y 18 filas.

4. ¿Por qué SELECT type_1, pokemon, COUNT(*) FROM pokemon GROUP BY type_1; da un error?
  • Porque COUNT(*) necesita un nombre de columna dentro
  • Porque una consulta no puede seleccionar más de dos columnas
  • Porque cada montón tiene muchos nombres, y la base de datos no puede elegir uno para imprimir
  • Porque pokemon también es el nombre de la tabla

El montón de agua contiene 126 cartas con 126 nombres, pero el resultado tiene una fila por montón. En una consulta agrupada cada columna de SELECT debe ser una clave de montón (type_1) o ir dentro de una agregación.

5. Quieres los tipos con más de 50 Pokémon. ¿Dónde va la condición COUNT(*) > 50?
  • En WHERE, antes de GROUP BY
  • En HAVING, después de GROUP BY
  • En ORDER BY
  • En SELECT, junto a los nombres de las columnas

La condición trata de un montón entero, así que va en HAVING. WHERE se ejecuta antes de que existan los montones: ponla ahí y PostgreSQL responde aggregate functions are not allowed in WHERE.

6. ¿En qué orden ejecuta realmente la base de datos estas líneas?
  • SELECT → FROM → WHERE → GROUP BY → HAVING
  • FROM → GROUP BY → WHERE → HAVING → SELECT
  • FROM → WHERE → GROUP BY → HAVING → SELECT
  • FROM → SELECT → WHERE → HAVING → GROUP BY

Abre la caja (FROM), saca cartas (WHERE), haz montones (GROUP BY), tira montones enteros (HAVING), y solo entonces lee las líneas y calcula (SELECT). Escribes SELECT primero, pero se ejecuta el quinto.

7. Una tabla sales(shop, product, amount) tiene una fila por venta. Quieres el importe total por tienda, contando solo las ventas de más de 10 euros, y solo de las tiendas con más de 100 de esas ventas. Elige las dos afirmaciones verdaderas.
  • amount > 10 va en WHERE, porque es una pregunta sobre cada venta
  • COUNT(*) > 100 va en HAVING, porque es una pregunta sobre el montón de cada tienda
  • Las dos condiciones van en WHERE, unidas con AND
  • SELECT shop, product, SUM(amount) funciona con GROUP BY shop

¿Cartas o montones? Una venta es una carta: WHERE amount > 10. Una tienda es un montón: HAVING COUNT(*) > 100. La última opción falla con must appear in the GROUP BY clause: el montón de una tienda tiene muchos productos.

La generación 5 tiene 156 Pokémon, pero ¿qué región es la generación 5? Eso está escrito en otra tabla. Siguiente: SQL Lab: joins, donde lees dos tablas como una, y las 147 cartas dan problemas.