Saltar a contenido

SQL Lab: filtros

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

Alguien te da una caja de zapatos con 949 cartas de Pokémon y te pregunta: ¿cuál es el eléctrico más rápido? No te pones a leer 949 cartas en voz alta. Sacas las eléctricas, las ordenas por velocidad, miras la carta de arriba y lees su nombre.

Cuatro movimientos con las manos. En una base de datos esos cuatro movimientos tienen nombre, y juntos forman una frase que la base de datos entiende. En la lección anterior conectaste Superset a la base de datos de clase y subiste la tabla pokemon. Ahora aprendes a pedirle menos: menos columnas, menos filas, en un orden útil. En el videojuego de crafteo de este módulo sigues en la fase de farmear y lootear, pero un buen jugador no se lleva el mapa entero a casa, solo lo que necesita la receta.

Qué aprenderás

  • Escribir una consulta que devuelva solo las columnas que necesitas, en lugar de SELECT *
  • Quedarte solo con las filas que necesitas con WHERE, usando operadores de comparación y AND / OR
  • Ordenar un resultado con ORDER BY y recortarlo con LIMIT para responder preguntas de "top N"
  • Listar los valores distintos de una columna con DISTINCT
  • Elegir el filtro especial adecuado: LIKE, IN, BETWEEN o IS NULL
  • Desactivar la trampa del día: por qué = NULL no encuentra nada, y por qué una consulta incorrecta casi nunca da error

Una caja de zapatos con 949 cartas

Una consulta es una pregunta escrita en un lenguaje que la base de datos entiende. Ese lenguaje es SQL (Structured Query Language).

Una analogía para toda esta página: la tabla pokemon es una caja de zapatos con cartas coleccionables.

En la base de datos En la caja de zapatos
La tabla pokemon La caja
Una fila Una carta: un Pokémon
Una columna Una línea impresa en todas las cartas: nombre, tipo, ataque...
Una consulta Lo que haces con las manos para responder una pregunta

La caja contiene 949 cartas, y cada carta tiene 22 líneas impresas. En esta lección usamos ocho de ellas:

Columna Qué contiene Ejemplo (charizard)
pokemon El nombre, en minúsculas charizard
type_1 Primer tipo fire
type_2 Segundo tipo, en blanco si no tiene flying
hp, attack, speed Estadísticas base, números enteros 78, 84, 100
weight Peso en kilogramos 90.5
generation_id Número de generación, de 1 a 7, en blanco en algunos 1

Abre SQL → SQL Lab en Superset, elige la base de datos Dades classe y el esquema public, y ya puedes escribir. Ejecutas una consulta con el botón Run o con Ctrl+Enter.

Por qué SQL parece inglés

En 1974, Donald Chamberlin y Raymond Boyce, dos investigadores de IBM en San José (California), publicaron un lenguaje para hacer preguntas a las bases de datos. Querían que personas que no eran programadoras pudieran leerlo, así que lo llamaron SEQUEL: Structured English Query Language. El nombre ya era una marca registrada de una empresa de aviones, así que perdió las vocales y se quedó en SQL. Más de cincuenta años después sigues escribiendo SELECT, FROM y WHERE exactamente como ellos.

La forma de una consulta

Una consulta tiene hasta cinco líneas, siempre en este orden. Cada una es un movimiento de tus manos.

Línea Con la caja de zapatos Regla
SELECT Qué líneas de la carta lees Nombres de columnas, separados por comas. * significa todas.
FROM Qué caja La tabla.
WHERE Qué cartas sacas Una condición que es verdadera o falsa para cada fila.
ORDER BY Cómo ordenas el montón ASC (primero lo pequeño, el valor por defecto) o DESC (primero lo grande).
LIMIT Cuántas cartas te quedas desde arriba Un número. Siempre al final.
flowchart LR
    A["<b>FROM</b><br>abre la caja:<br>949 cartas"] --> B["<b>WHERE</b><br>saca las cartas<br>que quieres"]
    B --> C["<b>ORDER BY</b><br>ordena el montón"]
    C --> D["<b>LIMIT</b><br>quédate con las<br>de arriba"]
    D --> E["<b>SELECT</b><br>lee solo las líneas<br>que has pedido"]

Esta es la pregunta del principio de la página, escrita como consulta:

SELECT pokemon, speed
FROM pokemon
WHERE type_1 = 'electric'
ORDER BY speed DESC
LIMIT 1;
pokemon speed
electrode 150

Una fila, dos columnas. La respuesta, y nada más. Las siguientes secciones construyen esa frase línea a línea.

SELECT: lee solo las líneas que necesitas

SELECT elige las columnas del resultado. FROM nombra la tabla de la que vienen.

SELECT * FROM pokemon te entrega las 22 columnas de las 949 filas. Sirve para echar un primer vistazo, y no sirve como respuesta. Nombra las columnas en su lugar:

SELECT pokemon, type_1, attack FROM pokemon;

Sigue devolviendo 949 filas, pero solo tres columnas. Las cuatro primeras:

pokemon type_1 attack
bulbasaur grass 49
ivysaur grass 62
venusaur grass 82
charmander fire 52

Cuidado: una coma que falta no da error

Escribe SELECT pokemon type_1 FROM pokemon (sin coma) y la consulta se ejecuta. Pero obtienes una columna, llamada type_1, llena de nombres. Sin la coma, SQL lee la segunda palabra como un nuevo nombre para la primera columna. Si falta una columna que has pedido, cuenta las comas.

WHERE: saca cartas de la caja

WHERE se queda solo con las filas para las que una condición es verdadera. Una condición es una pregunta de sí o no que se hace a cada fila, una por una.

SELECT pokemon, type_1, attack FROM pokemon WHERE type_1 = 'fire';

Esto devuelve 59 filas: charmander, charmeleon, charizard, vulpix... La condición type_1 = 'fire' se preguntó 949 veces, y 59 cartas dijeron que sí.

El = es un operador de comparación. Hay seis:

Operador Significado Ejemplo Filas
= igual a type_1 = 'fire' 59
<> distinto de type_1 <> 'fire' 890
> mayor que attack > 150 19
>= mayor o igual que attack >= 150 30
< menor que speed < 20 17
<= menor o igual que speed <= 20 33

Dos reglas causan la mayoría de los errores de la primera semana de SQL:

Regla Bien Mal Qué pasa
El texto va entre comillas simples, los números no type_1 = 'fire', attack > 150 type_1 = fire Un error: la base de datos cree que fire es una columna. SQL Lab dice We can't seem to resolve the column "fire".
El texto distingue mayúsculas de minúsculas 'fire' 'Fire' Ningún error, y 0 filas.

Cuidado: cero filas no siempre significa 'no hay ninguna'

WHERE type_1 = 'Fire' no devuelve nada porque ninguna carta dice Fire con F mayúscula. La base de datos no se quejó: respondió a la pregunta que le hiciste. Antes de creerte un resultado vacío, revisa la ortografía. Y las comillas dobles no son una salida: en PostgreSQL "fire" significa un nombre de columna, 'fire' significa texto.

Dos condiciones: AND, OR

Un operador lógico une dos condiciones. AND: las dos deben ser verdaderas. OR: basta con una.

SELECT pokemon, type_1, type_2 FROM pokemon
WHERE type_1 = 'dragon' AND type_2 = 'flying';

Seis filas: dragonite, altaria, salamence, rayquaza y las formas Mega de los dos últimos. Cambia AND por OR y pregunta algo más amplio:

SELECT pokemon, type_1, type_2 FROM pokemon
WHERE type_1 = 'dragon' OR type_2 = 'dragon';

61 filas: todos los Pokémon que son dragón en cualquiera de las dos casillas. AND hace el resultado más pequeño, OR lo hace más grande.

ORDER BY y LIMIT: ordena el montón, quédate con lo de arriba

ORDER BY ordena el resultado por una columna. LIMIT se queda solo con las primeras filas.

SELECT pokemon, type_1, attack FROM pokemon
WHERE attack > 150
ORDER BY attack DESC;

19 filas, las más fuertes primero. Juntas, estas dos líneas responden a cualquier pregunta de "top N":

SELECT pokemon, attack FROM pokemon ORDER BY attack DESC LIMIT 5;
pokemon attack
mewtwo-mega-x 190
heracross-mega 185
kartana 181
deoxys-attack 180
groudon-primal 180

Fíjate en las dos últimas filas. Tres Pokémon tienen un ataque de 180: deoxys-attack, groudon-primal y rayquaza-mega. Solo caben dos en un top 5, y la base de datos eligió como quiso. Tu pantalla puede mostrar otra pareja, y los dos tendríais razón.

Cuidado: LIMIT corta los empates de forma arbitraria

Cuando varias filas comparten el valor por el que ordenas, su orden no está definido. Para decidirlo tú, añade una segunda columna de ordenación: ORDER BY attack DESC, pokemon ordena los empates por nombre. Y recuerda que LIMIT 5 solo, sin ORDER BY, te da cinco filas cualesquiera, no las cinco mejores.

DISTINCT: conocer una columna

DISTINCT elimina las filas repetidas del resultado, de modo que cada valor distinto aparece una sola vez.

Antes de filtrar por una columna, averigua qué valores puede tomar:

SELECT DISTINCT type_1 FROM pokemon ORDER BY type_1;

18 filas, de bug a water: los 18 tipos, cada uno una vez. Ahora sabes que el tipo se escribe fire, no Fire ni FIRE. DISTINCT es la cura para la trampa de las cero filas de más arriba.

Cuatro filtros especiales

Los operadores de comparación cubren los números y el texto exacto. Otros cuatro filtros cubren el resto.

Filtro Con la caja de zapatos Ejemplo Filas
LIKE Cartas cuyo texto se parece a un patrón pokemon LIKE 'pika%' 13
IN Cartas cuyo valor está en mi lista type_1 IN ('fire', 'water', 'grass') 269
BETWEEN Cartas dentro de un rango, con los dos extremos incluidos speed BETWEEN 100 AND 120 126
IS NULL Cartas donde esa línea está en blanco type_2 IS NULL 439

LIKE usa dos comodines, símbolos que representan texto que no conoces: % es cualquier texto (también ninguno), _ es exactamente un carácter. 'pika%' significa "empieza por pika": pikachu y sus doce disfraces (pikachu-rock-star, pikachu-phd...).

IN es una forma corta de varios OR: type_1 = 'fire' OR type_1 = 'water' OR type_1 = 'grass'. Pon NOT delante para lo contrario: type_1 NOT IN ('fire', 'water', 'grass') devuelve 680 filas, y 269 + 680 = 949. Nadie se ha perdido.

BETWEEN incluye los dos extremos: speed BETWEEN 100 AND 120 es lo mismo que speed >= 100 AND speed <= 120. Los dos devuelven 126. Con > y < obtendrías 83, porque los Pokémon con exactamente 100 y exactamente 120 quedan fuera.

IS NULL encuentra los blancos: los 439 Pokémon con un solo tipo. IS NOT NULL encuentra los otros 510. Este merece su propia sección.

NULL: la línea en blanco de la carta

NULL significa desconocido: aquí no hay nada escrito. No es cero, y no es un texto vacío.

Ejecuta esto y cuenta:

SELECT pokemon, type_1, type_2 FROM pokemon WHERE type_2 = NULL;

0 filas. Ningún error. ¿Han desaparecido los 439 Pokémon de un solo tipo?

No. Piensa en dos cartas con una línea en blanco. ¿Los dos blancos son iguales? No puedes saberlo: puede que ahí vaya algo, y no sabes qué. Así que cuando comparas cualquier cosa con NULL, la base de datos responde "desconocido", y WHERE solo conserva las filas donde la respuesta es un sí claro.

Escribes La base de datos responde Filas
type_2 = NULL "desconocido" para cada fila 0
type_2 = '' "no": un blanco tampoco es un texto vacío 0
type_2 IS NULL "sí" para las que están en blanco 439
type_2 IS NOT NULL "sí" para las que están rellenas 510

Por eso SQL tiene una pregunta aparte para los blancos. Recuérdalo: IS NULL, nunca = NULL.

Respuestas incorrectas que parecen correctas

Todas las trampas de esta página tienen algo en común: ninguna da error. La base de datos hace exactamente lo que escribiste y devuelve un número creíble.

Trampa Qué obtienes Lección
LIKE '%mega%' para contar las evoluciones Mega 50 en lugar de 48: se cuelan meganium y yanmega Un patrón coincide con texto, no con significado. Usa '%-mega%' y mira las filas, no solo el recuento.
= NULL 0 filas Los blancos necesitan IS NULL.
Mezclar AND y OR sin paréntesis 79 en lugar de 34 AND se aplica primero, como × antes que +. Usa paréntesis.
LIMIT con un empate Un top 5 distinto al de tu compañero Añade una segunda columna de ordenación.

La tercera, con detalle. Quieres los Pokémon de fuego o agua con un ataque superior a 100:

SELECT COUNT(*) FROM pokemon
WHERE type_1 = 'fire' OR type_1 = 'water' AND attack > 100;

79. La base de datos lo leyó como "todos los Pokémon de fuego (59), más los de agua con ataque superior a 100 (20)". Añade paréntesis alrededor de la parte del OR:

SELECT COUNT(*) FROM pokemon
WHERE (type_1 = 'fire' OR type_1 = 'water') AND attack > 100;

34. Las mismas palabras, otra pregunta. (COUNT(*) cuenta filas; abre la siguiente lección.)

Pruébalo: la caza de la Pokédex

Abre SQL Lab y responde a cada pregunta con una consulta. Escribe primero la consulta, luego abre la solución y compara tu número.

N. Pregunta
1 Nombre y peso de los tres Pokémon más pesados.
2 ¿Cuántas evoluciones Mega hay? Su nombre contiene -mega.
3 ¿Cuántos Pokémon no tienen generación (generation_id)?
4 Eléctricos, de un solo tipo, con velocidad de 100 a 120: nombres, el más rápido primero.
5 Nombre y ataque de los Pokémon de un solo tipo con ataque superior a 150, el más fuerte primero.
Soluciones

1 — SELECT pokemon, weight FROM pokemon ORDER BY weight DESC LIMIT 3; → celesteela y cosmoem (999.9), y después groudon-primal (999.7).

2 — SELECT COUNT(*) FROM pokemon WHERE pokemon LIKE '%-mega%'; → 48.

3 — SELECT COUNT(*) FROM pokemon WHERE generation_id IS NULL; → 147. Ten ese número en la cabeza: vuelve en las dos lecciones siguientes.

4 — SELECT pokemon, speed FROM pokemon WHERE type_1 = 'electric' AND type_2 IS NULL AND speed BETWEEN 100 AND 120 ORDER BY speed DESC; → 6 filas, desde zebstrika (116) hasta voltorb (100).

5 — SELECT pokemon, attack FROM pokemon WHERE type_2 IS NULL AND attack > 150 ORDER BY attack DESC; → 5 filas, la primera deoxys-attack (180).

Si SQL Lab responde con un cuadro rojo, lee el mensaje: normalmente nombra la palabra con la que tropezó. Mira justo antes de esa palabra por si falta una comilla o hay una palabra clave mal escrita. Y ejecuta una consulta cada vez: con varias en el editor, solo ves el resultado de la última.

Ideas clave

  • Una consulta tiene una forma fija: SELECT (columnas) · FROM (tabla) · WHERE (filas) · ORDER BY (orden) · LIMIT (recorte).
  • WHERE hace una condición a cada fila. El texto va entre comillas simples y distingue mayúsculas de minúsculas; los números no llevan comillas.
  • AND estrecha, OR amplía. AND se aplica primero: cuando los mezcles, usa paréntesis.
  • ORDER BY ... DESC LIMIT n responde a "top N", pero LIMIT corta los empates de forma arbitraria.
  • DISTINCT muestra los valores distintos de una columna: úsalo antes de filtrar.
  • LIKE busca un patrón, IN una lista, BETWEEN un rango con los dos extremos, IS NULL un blanco. = NULL nunca es verdadero.
  • Los errores peligrosos no dan error. Devuelven un número que parece correcto.

Comprobación rápida

1. ¿Qué consulta devuelve solo el nombre y el ataque de cada Pokémon?
  • SELECT * FROM pokemon;
  • SELECT pokemon, attack FROM pokemon;
  • SELECT pokemon attack FROM pokemon;
  • FROM pokemon SELECT pokemon, attack;

SELECT lista las columnas, separadas por comas, y va antes de FROM. * devuelve las 22 columnas. Sin la coma, la consulta se ejecuta pero renombra la primera columna: una columna en lugar de dos.

2. WHERE type_1 = 'Fire' devuelve 0 filas y ningún error. ¿Cuál es la razón más probable?
  • La tabla no tiene Pokémon de fuego
  • El texto debe ir entre comillas dobles
  • El texto distingue mayúsculas de minúsculas, y la tabla guarda fire en minúsculas
  • WHERE no puede comparar texto

La base de datos comparó cada carta con Fire y ninguna coincidió, porque el texto distingue mayúsculas de minúsculas. Cero filas no prueba que "no hay ninguna": ejecuta SELECT DISTINCT type_1 para ver cómo están escritos realmente los valores.

3. ¿Qué devuelve SELECT pokemon, speed FROM pokemon ORDER BY speed DESC LIMIT 3;?
  • Tres Pokémon cualesquiera
  • Los tres Pokémon más lentos
  • Los tres Pokémon más rápidos
  • Todos los Pokémon con una velocidad superior a 3

ORDER BY speed DESC ordena el montón con la mayor velocidad arriba, y LIMIT 3 se queda con las tres primeras cartas. Sin ORDER BY, LIMIT 3 daría tres filas cualesquiera.

4. ¿Por qué WHERE type_2 = NULL devuelve 0 filas?
  • Porque todos los Pokémon tienen un segundo tipo
  • Porque NULL debe escribirse entre comillas
  • Porque NULL significa desconocido, así que una comparación con él nunca es un sí claro
  • Porque = solo funciona con números

NULL no es un valor: significa "aquí no hay nada escrito". Compararlo da "desconocido", y WHERE solo conserva las filas que responden que sí. La pregunta correcta es type_2 IS NULL, que encuentra 439 filas.

5. Cuentas las evoluciones Mega con LIKE '%mega%' y obtienes 50. El número real es 48. ¿Qué ha pasado?
  • LIKE ignora la primera y la última letra de un nombre
  • El patrón también coincide con meganium y yanmega, que no son evoluciones Mega
  • % solo coincide con un carácter
  • Dos evoluciones Mega están guardadas dos veces

Un patrón coincide con texto, no con significado. %mega% significa "contiene mega en cualquier sitio". Las evoluciones Mega se escriben con un guion, así que '%-mega%' devuelve los 48 correctos. Mira siempre las filas, no solo el recuento.

6. WHERE type_1 = 'fire' OR type_1 = 'water' AND attack > 100 devuelve 79 filas. Con paréntesis alrededor de la parte del OR devuelve 34. ¿Por qué?
  • Los paréntesis hacen la consulta más rápida, así que llegan menos filas
  • OR se aplica primero, así que la primera versión ignora el ataque
  • AND se aplica primero: sin paréntesis la condición del ataque solo vale para agua
  • La primera versión cuenta algunos Pokémon dos veces

AND se aplica antes que OR, como × antes que +. Sin paréntesis obtienes los 59 Pokémon de fuego más los 20 de agua fuertes. Con paréntesis, la condición del ataque vale para los dos tipos: 34.

7. Una tabla songs tiene las columnas title, artist, year y streams. Elige las dos consultas que devuelven correctamente las cinco canciones más reproducidas de 2020 a 2023.
  • SELECT title, streams FROM songs WHERE year BETWEEN 2020 AND 2023 ORDER BY streams DESC LIMIT 5;
  • SELECT title, streams FROM songs WHERE year >= 2020 AND year <= 2023 ORDER BY streams DESC LIMIT 5;
  • SELECT title, streams FROM songs WHERE year BETWEEN 2020 AND 2023 LIMIT 5;
  • SELECT title, streams FROM songs WHERE year > 2020 AND year < 2023 ORDER BY streams DESC LIMIT 5;

BETWEEN incluye los dos extremos, así que equivale a >= y <=. La tercera consulta no tiene ORDER BY: devuelve cinco canciones cualesquiera. La cuarta usa > y <, que descarta sin avisar 2020 y 2023.

Hasta ahora cada respuesta era una lista de cartas. ¿Y si la pregunta es "¿cuántos Pokémon de fuego hay?", y luego lo mismo para los 18 tipos a la vez? Siguiente: SQL Lab: grupos.