La solicitud

De Daniel Foster, Gerente de Producto Móvil
Tu objetivo
Para cada cliente con segment 'business', devuelve customer_id y app_orders: el número de sus pedidos con channel 'app' (cualquier estado). Los clientes empresariales sin pedidos en la app deben aparecer con app_orders = 0.
- Lee de la tabla
customerspendiente - Lee de la tabla
orderspendiente - Muestra exactamente
customer_idapp_orderspendiente
Tu consulta
Cargando el editor…
Solución de referencia
| customer_id | first_name | last_name | city | |
|---|---|---|---|---|
| 1 | Owen | Hudson | owen.hudson@icloud.com | San Francisco |
| 2 | Douglas | Mendoza | dmendoza69@gmail.com | Atlanta |
| 3 | Henry | Brooks | hbrooks@lakesidecafe.com | Los Angeles |
| 4 | Brian | Graham | bgraham@bluecedarhomes.com | Seattle |
Mote revisa tu resultado
Presiona Ejecutar. Cada fila que cumple el objetivo se vuelve una luciérnaga en tu linterna.
Lo que recibe Daniel, y por qué
Tablas
Datos de la tienda Harbor & Pine, listos para consultar.
customers2,018 filas · 9 columnas
customer_idintegerfirst_namevarcharlast_namevarcharemailvarcharcityvarcharstatevarcharsignup_datedatesegmentvarcharreferral_sourcevarchar
| customer_id | first_name | last_name | city | state | signup_date | segment | referral_source | |
|---|---|---|---|---|---|---|---|---|
| 1 | Owen | Hudson | owen.hudson@icloud.com | San Francisco | CA | 2022-06-02 | consumer | search |
| 2 | Douglas | Mendoza | dmendoza69@gmail.com | Atlanta | GA | 2022-06-02 | consumer | NULL |
| 3 | Henry | Brooks | hbrooks@lakesidecafe.com | Los Angeles | CA | 2022-06-04 | business | NULL |
| 4 | Brian | Graham | bgraham@bluecedarhomes.com | Seattle | WA | 2022-06-06 | business | |
| 5 | Kyle | Jimenez | kjimenez27@yahoo.com | Indianapolis | IN | 2022-06-07 | consumer | NULL |
orders12,000 filas · 7 columnas
order_idintegercustomer_idintegerorder_datetimestampstatusvarcharchannelvarcharshipping_feedecimalcoupon_codevarchar
| order_id | customer_id | order_date | status | channel | shipping_fee | coupon_code |
|---|---|---|---|---|---|---|
| 1001 | 125 | 2023-01-01 20:51:19 | completed | web | 0.00 | NULL |
| 1002 | 156 | 2023-01-02 07:58:46 | completed | web | 0.00 | NULL |
| 1003 | 22 | 2023-01-02 10:25:27 | completed | marketplace | 4.99 | WELCOME10 |
| 1004 | 48 | 2023-01-02 11:21:51 | completed | marketplace | 4.99 | WELCOME10 |
| 1005 | 82 | 2023-01-02 11:43:36 | completed | web | 0.00 | WELCOME10 |
products120 filas · 8 columnas
product_idintegernamevarcharcategoryvarcharsubcategoryvarcharunit_pricedecimalunit_costdecimallaunched_ondateis_activeboolean
| product_id | name | category | subcategory | unit_price | unit_cost | launched_on | is_active |
|---|---|---|---|---|---|---|---|
| 101 | Vale Velvet Loveseat | Furniture | Sofas | 1659.00 | NULL | 2023-09-11 | true |
| 102 | Alder Oak Coffee Table | Furniture | Tables | 919.00 | 503.50 | 2024-05-16 | true |
| 103 | Orchard Rattan Dining Chair | Furniture | Chairs | 789.00 | 429.87 | 2023-05-27 | true |
| 104 | Upland Oak Bookcase | Furniture | Storage | 429.00 | 214.70 | 2022-12-20 | true |
| 105 | Avery Leather Bed Frame | Furniture | Beds | 449.00 | 232.70 | 2021-08-31 | true |
order_items30,338 filas · 6 columnas
order_item_idintegerorder_idintegerproduct_idintegerquantityintegerunit_pricedecimaldiscount_pctdecimal
| order_item_id | order_id | product_id | quantity | unit_price | discount_pct |
|---|---|---|---|---|---|
| 1 | 1001 | 121 | 9 | 31.99 | 0.00 |
| 2 | 1001 | 154 | 3 | 11.99 | 0.00 |
| 3 | 1002 | 154 | 4 | 11.99 | 0.00 |
| 4 | 1002 | 194 | 1 | 149.00 | 0.00 |
| 5 | 1002 | 193 | 2 | 19.99 | 0.00 |
returns1,673 filas · 5 columnas
return_idintegerorder_item_idintegerreturned_ondatereasonvarcharrefund_amountdecimal
| return_id | order_item_id | returned_on | reason | refund_amount |
|---|---|---|---|---|
| 1 | 14 | 2023-01-10 | damaged | 83.69 |
| 2 | 42 | 2023-01-10 | wrong_item | 19.99 |
| 3 | 105 | 2023-01-14 | wrong_item | 10.99 |
| 4 | 24 | 2023-01-16 | changed_mind | 26.99 |
| 5 | 126 | 2023-01-16 | late | 20.99 |
employees60 filas · 8 columnas
employee_idintegerfirst_namevarcharlast_namevarchardepartmentvarchartitlevarcharmanager_idintegerhire_datedatesalaryinteger
| employee_id | first_name | last_name | department | title | manager_id | hire_date | salary |
|---|---|---|---|---|---|---|---|
| 1 | Catherine | Hale | Executive | Chief Executive Officer | NULL | 2016-03-01 | 285000 |
| 2 | Robert | Kim | Executive | Chief Operating Officer | 1 | 2017-01-09 | 235000 |
| 3 | Megan | Collins | Sales | Head of Sales | 1 | 2018-04-16 | 172000 |
| 4 | Chloe | Gutierrez | Sales | Account Executive | 3 | 2020-01-21 | 72000 |
| 5 | Frank | Bell | Sales | Account Executive | 3 | 2023-05-21 | 73000 |
web_sessions40,000 filas · 7 columnas
session_idintegercustomer_idintegerstarted_attimestampdevicevarcharsourcevarcharpages_viewedintegerconvertedboolean
| session_id | customer_id | started_at | device | source | pages_viewed | converted |
|---|---|---|---|---|---|---|
| 1 | NULL | 2023-01-01 23:02:24 | mobile | paid | 1 | false |
| 2 | 18 | 2023-01-01 15:07:49 | desktop | paid | 1 | false |
| 3 | NULL | 2023-01-01 16:55:46 | desktop | direct | 7 | false |
| 4 | NULL | 2023-01-01 12:43:28 | mobile | organic | 3 | false |
| 5 | NULL | 2023-01-01 13:06:21 | mobile | social | 2 | false |
marketing_spend3,276 filas · 3 columnas
spend_datedatechannelvarcharamountdecimal
| spend_date | channel | amount |
|---|---|---|
| 2023-01-01 | paid | 492.99 |
| 2023-01-01 | 37.38 | |
| 2023-01-01 | social | 200.53 |
| 2023-01-02 | paid | 709.13 |
| 2023-01-02 | 64.13 |
La lección
Este es el error de join más común en analítica, y el código inicial lo tiene. La idea central: una condición de WHERE sobre la tabla derecha de un LEFT JOIN descarta las filas sin coincidencia, así que el LEFT JOIN actúa como un inner join. Ejecuta el código inicial y luego cuenta los clientes empresariales: devuelve menos filas de las que hay de clientes empresariales.
Qué sale mal
SELECT c.customer_id, COUNT(o.order_id) AS app_orders
FROM customers AS c
LEFT JOIN orders AS o ON o.customer_id = c.customer_id
WHERE c.segment = 'business'
AND o.channel = 'app' -- the bug
GROUP BY c.customer_id;
Sigue el orden lógico. El LEFT JOIN se ejecuta primero y conserva correctamente a los clientes sin pedidos,
con NULL en todas las columnas de pedido. También conserva a los clientes cuyos únicos pedidos fueron en la web, una
fila por pedido web. Luego se ejecuta WHERE o.channel = 'app'. Para ambos grupos, o.channel es NULL o
‘web’, así que la condición no es verdadera y las filas se eliminan. Los clientes que más le importan a Daniel,
los que tienen cero pedidos en la app, son justo los que desaparecen.
Una condición de WHERE sobre la tabla derecha de un LEFT JOIN lo convierte en un INNER JOIN.
La solución: filtrar en ON
Las condiciones en ON deciden qué filas del lado derecho coinciden. Nunca eliminan filas del lado izquierdo:
SELECT c.customer_id, COUNT(o.order_id) AS app_orders
FROM customers AS c
LEFT JOIN orders AS o
ON o.customer_id = c.customer_id
AND o.channel = 'app'
WHERE c.segment = 'business'
GROUP BY c.customer_id;
Ahora cada cliente empresarial se empareja solo con sus pedidos de la app. Si no hay ninguno, el cliente se
conserva con NULL, y COUNT(o.order_id) cuenta cero valores no NULL, dando 0.
La condición sobre la tabla izquierda, c.segment = 'business', se queda correctamente en WHERE: está
pensada para eliminar clientes. Ponerla en ON conservaría en cambio a todos los clientes y solo dejaría de
emparejar pedidos para los consumidores, así que los consumidores aparecerían con 0.
| condición sobre | va en |
|---|---|
| qué filas izquierdas conservar | WHERE |
| qué filas derechas cuentan como coincidencia | ON |
COUNT(*) también sería incorrecto
Con la solución aplicada, prueba COUNT(*). Los clientes sin pedidos en la app ahora muestran 1, porque el LEFT JOIN
produjo una fila para ellos (llena de NULL). Cuenta una columna de la tabla derecha en su lugar.
Nota de entrevista
“¿Por qué mi LEFT JOIN perdió filas?” es un clásico de las entrevistas de SQL y de las revisiones de pruebas técnicas. Explícalo en términos de NULL: las filas sin coincidencia tienen NULL en las columnas del lado derecho, una comparación de WHERE con NULL es desconocida, y WHERE conserva solo las filas verdaderas. Luego da la solución: mover la condición a ON, o filtrar la tabla derecha en una subconsulta antes de unirla.
Errores comunes
- Filtrar la tabla derecha en WHERE.
WHERE o.channel = 'app'elimina a todo cliente sin pedidos en la app. Muévelo al join:ON o.customer_id = c.customer_id AND o.channel = 'app'. - Parchearlo con
OR o.order_id IS NULL. Eso conserva a los clientes sin ningún pedido, pero los clientes cuyos únicos pedidos fueron en la web siguen teniendo uno.order_idno NULL y siguen desapareciendo. - Mover la condición de la tabla izquierda a ON.
ON ... AND c.segment = 'business'conserva también a todos los consumidores, cada uno con 0 pedidos en la app. Las condiciones que eligen qué clientes conservar se quedan en WHERE. - Contar con
COUNT(*). Un cliente sin coincidencia aún tiene una fila llena de NULL, así queCOUNT(*)reporta- Cuenta una columna de la tabla derecha:
COUNT(o.order_id).
- Cuenta una columna de la tabla derecha:
En SQL Server
Sin diferencia: la regla de ON frente a WHERE es SQL estándar. COUNT(o.order_id) también devuelve 0 para los
clientes sin coincidencia allí. Solo SUM de una coincidencia vacía devuelve NULL, que envolverías en ISNULL(..., 0) o
COALESCE(..., 0).
¡Linterna encendida!
Practicaste: left joinon vs wherecountzero rows
- Resuelto
- Sin pistas
- Al primer intento
