---
title: "¿Por qué mi consulta SQL tarda tanto?"
slug: "why-is-my-sql-query-taking-so-long"
published_at: "2022-10-04T07:41:59.000Z"
categories: "SQL Avanzado, Miscelánea, PL/SQL, Transact-SQL"
tags: "eficiente, ejemplos, Rendimiento, Consulta, SQL, Demora demasiado"
---

# ¿Por qué mi consulta SQL tarda tanto?

> Esta es una de las preguntas que casi siempre nos hacemos.
En este artículo, vamos a aprender un poco sobre cómo hacer una consulta eficiente.

Esta es una de las preguntas que, como Data Analysts o Data Scientists, casi siempre nos hacemos, sobre todo cuando estamos trabajando con bases de datos grandes.

![](/blog/why-is-my-sql-query-taking-so-long/image-20.png)

Además, ¿cuántas veces nos aparece el error **‘No more memory spool space for [USER]**‘? Tuve ese error tantas veces que no las puedo contar, la mayoría de las veces esto pasa porque **estamos haciendo una consulta ineficiente**.

**En este artículo, vamos a aprender un poco sobre cómo hacer una consulta eficiente**

### ¿Usar subconsultas es una buena idea?

Realmente depende de cuántas filas va a procesar la subconsulta (y cuántas filas generaría no hacerlo), dejame darte dos ejemplos:

Primero, vamos a hacer una subconsulta para obtener los nombres en una tabla que tiene todas las direcciones, nombres, y apellidos de las personas que viven en un país, quedaría algo así:

```sql
SELECT a.* FROM (SELECT name FROM database.table_with_people) a 
```

**Sin embargo, se ve un poco tosco, ¿no?** Bueno, en realidad también es ineficiente.

Cuando definís una subconsulta, tu base de datos SQL siempre va a hacer primero esa instancia de subconsulta antes de hacer la consulta real, así que en este caso, estarías haciendo una consulta y después, a partir de esa consulta, obteniendo los resultados a mostrar, esto es medio malo cuando estás trabajando con datasets muy grandes, sería más eficiente hacer esta consulta en este caso:

```sql
SELECT name FROM database.table_with_people
```

### Ahora con un poco más de complejidad

Por ejemplo, asumamos que no solo tenés las direcciones, nombres, y apellidos de todas las personas que viven en un país, sino que también tenés todo su historial de tarjeta de crédito (no solo con sus deudas de tarjeta de crédito, sino también con sus créditos, fechas, y IDs de tarjeta) en otra tabla y un ID único para identificarlos, tratemos de obtener su última transacción de tarjeta de crédito.

```sql
SELECT a.unique_id, b.credit_card_transaction AS credit_card_last_transaction FROM database.table_with_people a
JOIN database.credit_card_transactions b
on a.unique_id=b.unique_people_id
QUALIFY row_number() OVER (PARTITION BY a.unique_id ORDER BY b.date_transaction DESC)=1
```

Sin embargo, este código tal como está probablemente te va a dar un **error**, ¿por qué? Veamos qué estamos haciendo acá:

-   Primero, estamos seleccionando a todas las personas en database.table_with_people
-   Después, estamos haciendo un LEFT JOIN de todos los datos en database.credit_card_transactions
-   Por último, estamos seleccionando la última fila y ordenándolas por date_transaction (row_number() le va a poner un número ficticio a los datos y con [QUALIFY](/es/how-to-use-a-qualify-clause-in-sql) = 1 estamos obteniendo el último valor porque los valores estaban ordenados de último a primero).

Pero, ¿no te suena mal algo de todo esto? Estamos obteniendo **TODAS las transacciones de las personas en nuestra consulta PRIMERO y después FILTRANDO**. Y estás haciendo esto en una tabla que probablemente tiene miles de millones de filas de datos.

### La forma correcta de hacer una subconsulta

Entonces, ¿qué deberíamos hacer? De hecho, un buen enfoque acá sería usar una subconsulta y filtrar antes del JOIN, que quedaría algo así:

```sql
SELECT a.unique_id, b.credit_card_transaction AS credit_card_last_transaction FROM database.table_with_people a
JOIN (SELECT unique_people_id, credit_card_transaction FROM database.credit_card_transactions
     QUALIFY row_number() OVER (PARTITION BY unique_people_id ORDER BY date_transaction DESC)=1) b
on a.unique_id=b.unique_people_id
```

A pesar de que estamos consultando los mismos datos que antes, ahora tu consulta probablemente va a funcionar bien, ya que filtrás los datos ANTES de hacer el JOIN.

### INNER JOIN VERSUS LEFT JOIN

**Del mismo modo, otro error común es usar LEFT JOIN cuando solo necesitás un INNER JOIN**, tomemos la última consulta que estaba funcionando bien y veamos:

```sql
SELECT a.unique_id, b.credit_card_transaction AS credit_card_last_transaction FROM database.table_with_people a
LEFT JOIN (SELECT unique_people_id, credit_card_transaction FROM database.credit_card_transactions
     QUALIFY row_number() OVER (PARTITION BY unique_people_id ORDER BY date_transaction DESC)=1) b
on a.unique_id=b.unique_people_id
```

Por ejemplo, asumamos que todos los datos eran correctos, ¿no? Bueno, probablemente no lo sean. ¿Qué hubiera pasado si una persona no tenía transacciones? Nuestra consulta hubiera devuelto un valor nulo en unique_people_id cuando unimos las dos tablas, pero además, asumamos que todas las no-transacciones tienen un unique_people_id (creeme, esto pasa MUCHO cuando trabajás con datasets muy grandes), así que eso también hubiera sido un valor nulo… Acá tenés un pequeño gráfico de cómo funciona un LEFT JOIN (no el típico gráfico que no explica nada, un gráfico de verdad):

![](/blog/why-is-my-sql-query-taking-so-long/image-12-1024x197.png)

Como podés ver en nuestro súper gráfico, Mary Condo tiene dos relaciones con la tabla dos, esto significa que va a aparecer dos veces en tus resultados del SELECT, una por cada fila del join que coincide. Y John Bay todavía no tiene un unique_id (quizás porque no está asignado), así que va a coincidir con un credit_card_transaction que no va a ser el suyo. Entonces, ¿cómo mitigamos estos errores?

### Vamos llegando

Bueno, una forma, ya la vimos antes, ¿no? Aunque sea otra consulta, usar qualify para obtener solo un valor por unique people id funcionaría, pero el error de NULL todavía va a seguir ahí, así que quizás deberías hacer algo así:

```sql
SELECT a.unique_id, b.credit_card_transaction AS credit_card_last_transaction FROM database.table_with_people a
LEFT JOIN (SELECT unique_people_id, credit_card_transaction FROM database.credit_card_transactions
     QUALIFY row_number() OVER (PARTITION BY unique_people_id ORDER BY date_transaction DESC)=1) b
on a.unique_id=b.unique_people_id
WHERE a.unique_id is NOT NULL
```

Esto nos va a dar los resultados solo de las personas que sí tienen unique_id, ¿pero qué pasa con las personas que tienen unique_id Y no tienen ninguna transacción? Bueno, también van a aparecer en el resultado. Esto va a significar que estamos consultando nuestra segunda tabla para estas personas, y en realidad no tienen ningún resultado que nos importe, solo estábamos buscando la última transacción de tarjeta de crédito de las personas que realmente tienen transacciones de tarjeta de crédito. **Una forma ineficiente de resolver esto sería:**

```sql
SELECT a.unique_id, b.credit_card_transaction AS credit_card_last_transaction FROM database.table_with_people a
LEFT JOIN (SELECT unique_people_id, credit_card_transaction FROM database.credit_card_transactions
     QUALIFY row_number() OVER (PARTITION BY unique_people_id ORDER BY date_transaction DESC)=1) b
on a.unique_id=b.unique_people_id
WHERE a.unique_id is NOT NULL and b.unique_people_id is NOT NULL
```

Y de nuevo, como en nuestras primeras subconsultas, **estamos filtrando DESPUÉS de consultar los datos**.

### Haciendo la consulta correcta para el resultado que queremos

Sin embargo, esta última consulta probablemente no te va a dar un error, es realmente ineficiente. De todas formas, una manera de resolver esto de una vez por todas sería usar **INNER JOIN**, solo va a consultar y mostrar los datos que coinciden, y no vas a tener tantos problemas en el rendimiento de tu consulta, sería algo así:

```sql
SELECT a.unique_id, b.credit_card_transaction AS credit_card_last_transaction FROM database.table_with_people a
INNER JOIN (SELECT unique_people_id, credit_card_transaction FROM database.credit_card_transactions
     QUALIFY row_number() OVER (PARTITION BY unique_people_id ORDER BY date_transaction DESC)=1) b
on a.unique_id=b.unique_people_id
WHERE a.unique_id is NOT NULL
```

### ¿Dónde debería poner mi cláusula WHERE?

Ahora asumamos que no necesitás la última transacción de tarjeta de crédito, en cambio necesitás la suma de todas las transacciones que hizo cada persona el mes pasado, una forma de hacer esto sería:

```sql
SELECT a.unique_id, SUM(b.credit_card_transaction) AS sum_of_last_month_transactions FROM database.table_with_people a
JOIN database.credit_card_transactions b
on a.unique_id=b.unique_people_id
WHERE a.unique_id is NOT NULL AND b.date_transaction BETWEEN date and date-31
```

En este punto, ya podés ver de qué estoy hablando, ¿no? Esto va a agotar completamente tu uso de memoria, de nuevo, estás filtrando el resultado después de que la consulta ya terminó.

Una buena solución para esto sería, de nuevo, **una subconsulta filtrando los datos ANTES de hacer el join de las consultas**.

```sql
SELECT a.unique_id, SUM(b.credit_card_transaction) AS sum_of_last_month_transactions FROM database.table_with_people a
JOIN (SELECT unique_people_id, credit_card_transaction FROM database.credit_card_transactions WHERE date_transaction BETWEEN date and date-31) b
on a.unique_id=b.unique_people_id
WHERE a.unique_id is NOT NULL
```

### Conclusión

Hacer una consulta buena y eficiente depende realmente de los datos y las tablas con las que estés trabajando, siempre deberías tratar de minimizar el impacto en memoria de tu consulta y tratar de descubrir cómo funcionaría la consulta de forma más eficiente. Viste acá algunas formas de lograr esto con algunos ejemplos que te van a ayudar en tu vida profesional, realmente deberías pensar, DE VERDAD, cuál es la información que estás tratando de encontrar y cuál sería la mejor forma de lograrlo. Las bases de datos siempre van a funcionar mejor sin subconsultas, pero a veces los datos que necesitás son tan pesados que sin usarlas seguramente te vas a quedar sin memoria.

Podés aprender más sobre subconsultas y cómo usarlas en Microsoft SQL Server [acá](https://learn.microsoft.com/en-us/sql/relational-databases/performance/subqueries?view=sql-server-ver16).

