Aprender / Hoja de referencia
Hoja de referencia de SQL para testers
El SQL que usan los testers para verificar los datos detrás de una UI o una API: encontrar el registro, comprobar que cambió y buscar duplicados, huérfanos y NULL que no deberían estar ahí. La sintaxis es SQL estándar que funciona en PostgreSQL, MySQL 8 y SQLite. Cuando una base de datos se comporta distinto, la nota lo indica.
Revisada con SQL estándar; revisado con PostgreSQL 16, MySQL 8 y SQLite 3
Buscar filas
| Tarea | Código |
|---|---|
| Columnas de una tabla | SELECT id, email FROM users; |
| Filtrar | SELECT * FROM orders WHERE status = 'paid' AND total > 100; |
| Uno de varios valores | WHERE status IN ('paid', 'shipped') |
| Rango (ambos extremos incluidos) | WHERE created_at BETWEEN '2026-01-01' AND '2026-01-31' |
| Patrón | WHERE email LIKE '%@example.com' |
| Valor ausente | WHERE phone IS NULL |
| Ordenar, más recientes primero | ORDER BY created_at DESC |
| Primeras 10 filas | LIMIT 10 |
| Valores distintos | SELECT DISTINCT status FROM orders; |
SQL Server usa SELECT TOP 10 en lugar de LIMIT. BETWEEN sobre timestamps solo incluye la medianoche de la fecha final; usa < día siguiente para días completos.
Agregar
| Tarea | Código |
|---|---|
| Contar filas | SELECT COUNT(*) FROM orders; |
| Contar valores no NULL | SELECT COUNT(shipped_at) FROM orders; |
| Contar valores distintos | SELECT COUNT(DISTINCT user_id) FROM orders; |
| Totales por grupo | SELECT status, COUNT(*), SUM(total) FROM orders GROUP BY status; |
| Filtrar grupos | SELECT user_id, COUNT(*) FROM orders GROUP BY user_id HAVING COUNT(*) > 5; |
| Mínimo, máximo, media | SELECT MIN(total), MAX(total), AVG(total) FROM orders; |
WHERE filtra las filas antes de agrupar; HAVING filtra los grupos después.
Join
| Tarea | Código |
|---|---|
| Solo las filas que coinciden | SELECT o.id, u.email FROM orders o JOIN users u ON u.id = o.user_id; |
| Conservar todas las filas de la izquierda | SELECT u.email, o.id FROM users u LEFT JOIN orders o ON o.user_id = u.id; |
| Filas sin coincidencia (anti-join) | SELECT u.* FROM users u LEFT JOIN orders o ON o.user_id = u.id WHERE o.id IS NULL; |
| Lo mismo, con NOT EXISTS | SELECT * FROM users u WHERE NOT EXISTS (SELECT 1 FROM orders o WHERE o.user_id = u.id); |
Un join sobre una clave uno a muchos repite el lado "uno". Si SUM() parece demasiado grande después de un join, el join multiplicó las filas.
Comprobaciones de datos que hacen los testers
| Tarea | Código |
|---|---|
| Duplicados | SELECT email, COUNT(*) FROM users GROUP BY email HAVING COUNT(*) > 1; |
| Huérfanos (hijo sin padre) | SELECT o.* FROM orders o LEFT JOIN users u ON u.id = o.user_id WHERE u.id IS NULL; |
| Campo obligatorio vacío | SELECT * FROM users WHERE email IS NULL OR TRIM(email) = ''; |
| Valores fuera de rango | SELECT * FROM order_items WHERE quantity <= 0 OR unit_price < 0; |
| Total de cabecera ≠ líneas del pedido | SELECT o.id FROM orders o JOIN order_items i ON i.order_id = o.id GROUP BY o.id, o.total HAVING o.total <> SUM(i.quantity * i.unit_price); |
| La fila existe tras una acción en la UI | SELECT * FROM orders WHERE user_id = 42 ORDER BY created_at DESC LIMIT 1; |
Trampas con NULL
| Trampa | Qué ocurre |
|---|---|
| WHERE col = NULL | Nunca es verdadero. Usa IS NULL / IS NOT NULL. |
| WHERE status <> 'paid' | Omite las filas donde status es NULL. Añade OR status IS NULL si las quieres. |
| NOT IN (subconsulta que devuelve un NULL) | No devuelve ninguna fila. Usa NOT EXISTS en su lugar. |
| COUNT(col) vs COUNT(*) | COUNT(col) omite los NULL; COUNT(*) cuenta todas las filas. |
| Aritmética con NULL | price + NULL es NULL. Envuélvelo con COALESCE(col, 0) si 0 es el valor por defecto correcto. |
Funciones de ventana
| Tarea | Código |
|---|---|
| Numerar filas por grupo | ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY created_at DESC) |
| Última fila de cada grupo | SELECT * FROM (SELECT o.*, ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY created_at DESC) AS rn FROM orders o) t WHERE rn = 1; |
| Ranking con empates | RANK() OVER (ORDER BY total DESC) / DENSE_RANK() … |
| Valor de la fila anterior | LAG(status) OVER (PARTITION BY test_name ORDER BY run_at) |
| Total acumulado | SUM(total) OVER (ORDER BY created_at) |
Compatibles con PostgreSQL, MySQL 8+, SQLite 3.25+ y SQL Server. Una función de ventana no se puede usar en WHERE; filtra en una consulta exterior.
Modificar datos con seguridad
| Tarea | Código |
|---|---|
| Insertar | INSERT INTO users (email, name) VALUES ('qa+1@example.com', 'QA One'); |
| Actualizar (siempre con WHERE) | UPDATE orders SET status = 'cancelled' WHERE id = 1043; |
| Borrar (siempre con WHERE) | DELETE FROM sessions WHERE user_id = 42; |
| Probarlo y deshacerlo | BEGIN; UPDATE orders SET status = 'cancelled' WHERE id = 1043; SELECT status FROM orders WHERE id = 1043; ROLLBACK; |
Ejecuta primero el mismo WHERE como SELECT y comprueba el número de filas antes de hacer UPDATE o DELETE.