Índice
Formato/legibilidad
- Usar comas al inicio para separar campos
- Usar un valor dummy en la cláusula WHERE
- Indentación adecuada del código
- Considerar CTE al escribir consultas complejas
Funciones útiles
- Convertir tipos de datos usando el operador
::
- Aprovechar los anti joins
- Usar
QUALIFY para filtrar funciones de ventana
- Se puede usar
GROUP BY por posición de columna
Errores que hay que evitar
- Tener cuidado al usar
NOT IN con valores NULL
- Cambiar el nombre de campos calculados para evitar ambigüedad
- Especificar a qué tabla pertenece cada columna
- Entender el orden de ejecución
- Comentar el código
- Leer toda la documentación
Formato/legibilidad
Usar comas al inicio para separar campos
- Al separar campos en la cláusula
SELECT, usar comas al inicio puede ayudar a distinguir claramente cada nueva columna
- Las comas al inicio dan una pista visual para detectar fácilmente si falta alguna coma
SELECT
employee_id,
employee_name,
job,
salary
FROM employees;
Usar un valor dummy en la cláusula WHERE
- Usar un valor dummy en la cláusula WHERE permite agregar y quitar condiciones de forma dinámica
SELECT *
FROM employees
WHERE 1=1 -- valor dummy
AND job IN ('Clerk', 'Manager')
AND dept_no != 5;
Indentación adecuada del código
- Indentar el código mejora la legibilidad y hace más fácil entenderlo para tus colegas y para tu yo del futuro
-- Mal ejemplo:
SELECT
timeslot_date,
timeslot_channel,
overnight_fta_share,
IFF(DATEDIFF(DAY, timeslot_date, CURRENT_DATE()) > 7,
LAG(overnight_fta_share, 1) OVER (PARTITION BY timeslot_date, timeslot_channel ORDER BY timeslot_activity),
NULL) AS C7_fta_share,
IFF(DATEDIFF(DAY, timeslot_date, CURRENT_DATE()) >= 29,
LAG(overnight_fta_share, 2) OVER (PARTITION BY timeslot_date, timeslot_channel ORDER BY timeslot_activity),
NULL) AS C28_fta_share
FROM timeslot_data;
-- Buen ejemplo:
SELECT
timeslot_date,
timeslot_channel,
overnight_fta_share,
IFF(DATEDIFF(DAY, timeslot_date, CURRENT_DATE()) > 7,
LAG(overnight_fta_share, 1) OVER (PARTITION BY timeslot_date, timeslot_channel ORDER BY timeslot_activity),
NULL) AS C7_fta_share,
IFF(DATEDIFF(DAY, timeslot_date, CURRENT_DATE()) >= 29,
LAG(overnight_fta_share, 2) OVER (PARTITION BY timeslot_date, timeslot_channel ORDER BY timeslot_activity),
NULL) AS C28_fta_share
FROM timeslot_data;
Considerar CTE al escribir consultas complejas
- En vez de anidar vistas inline, se pueden usar expresiones de tabla comunes (CTE) para mejorar la legibilidad y organización del código
-- Uso de vista inline:
SELECT
vhs.movie,
vhs.vhs_revenue,
cs.cinema_revenue
FROM
(SELECT
movie_id,
SUM(ticket_sales) AS cinema_revenue
FROM tickets
GROUP BY movie_id) AS cs
INNER JOIN
(SELECT
movie,
movie_id,
SUM(revenue) AS vhs_revenue
FROM blockbuster
GROUP BY movie, movie_id) AS vhs
ON cs.movie_id = vhs.movie_id;
-- Uso de CTE:
WITH cinema_sales AS (
SELECT
movie_id,
SUM(ticket_sales) AS cinema_revenue
FROM tickets
GROUP BY movie_id
),
vhs_sales AS (
SELECT
movie,
movie_id,
SUM(revenue) AS vhs_revenue
FROM blockbuster
GROUP BY movie, movie_id
)
SELECT
vhs.movie,
vhs.vhs_revenue,
cs.cinema_revenue
FROM cinema_sales AS cs
INNER JOIN vhs_sales AS vhs
ON cs.movie_id = vhs.movie_id;
Funciones útiles
Convertir tipos de datos usando el operador ::
- En algunos RDBMS, se puede usar el operador
:: para convertir un valor a otro tipo de dato
SELECT CAST('5' AS INTEGER); -- uso de la función CAST
SELECT '5'::INTEGER; -- uso de la sintaxis ::
Aprovechar los anti joins
- Los anti joins son muy útiles para devolver filas que existen solo en una de las tablas
- También se puede usar una subconsulta, pero en general el anti join suele ser más rápido
-- Anti join:
SELECT
video_content.*
FROM video_content
LEFT JOIN archive
ON video_content.series_id = archive.series_id
WHERE archive.series_id IS NULL;
-- Subconsulta:
SELECT
*
FROM video_content
WHERE series_id NOT IN (SELECT DISTINCT series_id FROM archive);
-- Subconsulta correlacionada:
SELECT
*
FROM video_content
WHERE NOT EXISTS (
SELECT 1
FROM archive a
WHERE a.series_id = vc.series_id
);
-- EXCEPT:
SELECT series_id
FROM video_content
EXCEPT
SELECT series_id
FROM archive;
Usar QUALIFY para filtrar funciones de ventana
QUALIFY permite filtrar el resultado de funciones de ventana
- Es útil para reducir líneas de código
-- Usando QUALIFY:
SELECT
product,
market,
SUM(revenue) AS market_revenue
FROM sales
GROUP BY product, market
QUALIFY DENSE_RANK() OVER (PARTITION BY product ORDER BY SUM(revenue) DESC) <= 10
ORDER BY product, market_revenue;
-- Sin QUALIFY:
SELECT
product,
market,
market_revenue
FROM (
SELECT
product,
market,
SUM(revenue) AS market_revenue,
DENSE_RANK() OVER (PARTITION BY product ORDER BY SUM(revenue) DESC) AS market_rank
FROM sales
GROUP BY product, market
)
WHERE market_rank <= 10
ORDER BY product, market_revenue;
Se puede usar GROUP BY por posición de columna
- En lugar del nombre de la columna, se puede usar la posición de la columna en
GROUP BY u ORDER BY
- Es útil para consultas temporales, pero en código de producción siempre se debería referenciar por nombre de columna
SELECT
dept_no,
SUM(salary) AS dept_salary
FROM employees
GROUP BY 1 -- dept_no es la primera columna de la cláusula SELECT
ORDER BY 2 DESC;
Errores que hay que evitar
Tener cuidado al usar NOT IN con valores NULL
NOT IN no funciona cuando hay valores NULL
- En su lugar, se debería usar
NOT EXISTS
INSERT INTO departments (id)
VALUES (1), (2), (NULL);
-- No funciona por el valor NULL
SELECT *
FROM employees
WHERE department_id NOT IN (SELECT DISTINCT id FROM departments);
-- Solución
SELECT *
FROM employees e
WHERE NOT EXISTS (
SELECT 1
FROM departments d
WHERE d.id = e.department_id
);
Cambiar el nombre de campos calculados para evitar ambigüedad
- Si un campo calculado se renombra con el nombre de una columna existente, puede provocar comportamientos inesperados
INSERT INTO products (product, revenue)
VALUES ('Shark', 100), ('Robot', 150), ('Alien', 90);
-- La función de ventana rankea el producto 'Robot' en primer lugar
SELECT
product,
CASE product WHEN 'Robot' THEN 0 ELSE revenue END AS revenue,
RANK() OVER (ORDER BY revenue DESC)
FROM products;
Especificar a qué tabla pertenece cada columna
- En consultas complejas, indicar a qué tabla pertenece cada columna facilita rastrear problemas
SELECT
vc.video_id,
vc.series_name,
metadata.season,
metadata.episode_number
FROM video_content AS vc
INNER JOIN video_metadata AS metadata
ON vc.video_id = metadata.video_id;
Entender el orden de ejecución
- El consejo más importante para quien está aprendiendo SQL es entender el orden de ejecución
- Si entiendes el orden de ejecución, tu forma de escribir consultas cambia por completo
Comentar el código
- Al escribir código, hay que agregar comentarios que expliquen el porqué
- Tus colegas y tu yo del futuro te lo van a agradecer
SELECT
video_content.*
FROM video_content
LEFT JOIN archive -- El nuevo CMS no puede procesar el formato de video archivado
ON video_content.series_id = archive.series_id
WHERE archive.series_id IS NULL;
Leer toda la documentación
- Leer la documentación completa ayuda a evitar problemas inesperados
- No toma más que unos minutos y puede ayudar a resolver problemas inesperados
-- Si hubieras leído más la documentación, habrías podido resolver el problema de NULL
SELECT COALESCE(GREATEST(signup_date, consumption_date), signup_date, consumption_date);
-- Se puede usar la función GREATEST_IGNORE_NULLS
SELECT GREATEST_IGNORE_NULLS(signup_date, consumption_date);
Resumen de GN⁺
- Este artículo ofrece varios consejos y trucos para escribir SQL de forma más eficiente y legible
- Tiene mucha información útil tanto para quienes están aprendiendo SQL por primera vez como para analistas de datos con experiencia
- En especial, usar CTE al escribir consultas complejas, aprovechar anti joins y usar
QUALIFY puede ser de gran ayuda en el trabajo diario
- Es importante entender el orden de ejecución de SQL, comentar el código y desarrollar el hábito de leer la documentación con atención
- Otras herramientas con funciones similares incluyen PostgreSQL, MySQL y Oracle
2 comentarios
Parece que en esta publicación todas las comas iniciales están escritas como comas finales. En el original están puestas como comas iniciales.
Comentario de Hacker News
La necesidad de entender bien el servidor de base de datos y revisar con frecuencia el plan de ejecución de las consultas
EXISTSes más rápido queINNOT EXISTSyEXCEPTfuncionan de forma diferente al manejar valoresNULLUNION ALLpuede ser más rápido queORConsejos para trabajar con procedimientos almacenados complejos
Opiniones sobre la legibilidad del código
Sugerencia de usar la sintaxis FROM-first y de piping en SQL
Consejo relacionado con Anti Join
EXISTS, ya que es ventajoso para verificar la existencia de filas en subconsultas basadas en condicionesVentajas de usar coma inicial en sentencias
SELECTEn MSSQL, se recomienda usar
/* */en lugar de--al usar comentariosSe recomienda usar funciones de ventana
Debate sobre el uso de
1=1en la cláusulaWHEREIntroducción a AI2sql