La solicitud

De Kevin Brooks, Gerente de Operaciones
Tu objetivo
Une orders con order_items. Para los pedidos con status 'completed', devuelve una fila por canal con: channel, orders (número de pedidos distintos), order_lines (número de filas de order_items) y lines_per_order (order_lines dividido entre orders, redondeado a 2).
- Lee de la tabla
orderspendiente - Lee de la tabla
order_itemspendiente - Muestra exactamente
channelordersorder_lineslines_per_orderpendiente
Tu consulta
Cargando el editor…
Solución de referencia
| order_id | customer_id | order_date | status | channel |
|---|---|---|---|---|
| 1001 | 125 | 2023-01-01 20:51:19 | completed | web |
| 1002 | 156 | 2023-01-02 07:58:46 | completed | web |
| 1003 | 22 | 2023-01-02 10:25:27 | completed | marketplace |
| 1004 | 48 | 2023-01-02 11:21:51 | completed | marketplace |
Mote revisa tu resultado
Presiona Ejecutar. Cada fila que cumple el objetivo se vuelve una luciérnaga en tu linterna.
Lo que recibe Kevin, y por qué
Tablas
Datos de la tienda Harbor & Pine, listos para consultar.
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 |
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 |
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 |
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 |
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
Cuando unes una tabla con otra donde cada fila coincide con muchas filas, las filas de la primera tabla se repiten una vez por coincidencia. Esto se llama fan-out, y es el origen de la mayoría de los números inflados en analítica.
Grano
Toda tabla tiene un grano: lo que representa una fila. orders es una fila por pedido. order_items es
una fila por línea de pedido. Tras
FROM orders AS o
JOIN order_items AS i ON i.order_id = o.order_id
el resultado está al grano de línea de pedido. Un pedido con tres líneas aparece tres veces, y cada
columna de orders, incluida shipping_fee, se copia en las tres filas.
Qué se rompe
COUNT(*)ahora cuenta líneas, no pedidos. La consulta del código inicial reporta aproximadamente de dos a tres veces el número real de pedidos.SUM(o.shipping_fee)suma el costo de envío de cada pedido una vez por línea. Un cargo de $6.95 en un pedido de tres líneas se cuenta como $20.85. Finanzas lo notará antes que tú.AVG(o.shipping_fee)queda ponderado hacia los pedidos con muchas líneas.
Nada da error. La consulta se ejecuta, los números parecen plausibles, y están mal.
Soluciones
Cuenta claves distintas para los conteos del lado “uno”:
COUNT(DISTINCT o.order_id) AS orders,
COUNT(*) AS order_lines
Agrega antes de unir para las sumas del lado “uno”. Resume primero las líneas por pedido, para que el join vuelva a ser uno a uno:
SELECT o.channel,
SUM(o.shipping_fee) AS shipping,
SUM(l.lines) AS order_lines
FROM orders AS o
JOIN (
SELECT order_id, COUNT(*) AS lines
FROM order_items
GROUP BY order_id
) AS l ON l.order_id = o.order_id
WHERE o.status = 'completed'
GROUP BY o.channel;
Lo harás de forma limpia con CTE en el capítulo 7. No uses SUM(DISTINCT o.shipping_fee): dos
pedidos distintos con el mismo cargo de $6.95 se colapsarían en uno.
Razones tras un join
lines_per_order son las líneas de pedido divididas entre los pedidos. Ambos son enteros, así que multiplica por 1.0 antes de
dividir. En Postgres, COUNT(*) / COUNT(DISTINCT o.order_id) truncaría 2.6 a 2. DuckDB y
MySQL devuelven un decimal, el / de BigQuery siempre devuelve un float, pero * 1.0 mantiene la consulta portable.
Cómo detectar el fan-out
Antes de agregar, ejecuta el join con COUNT(*) y compáralo con el conteo de filas de la tabla cuyo
grano quieres. Si esperabas una fila por pedido y obtuviste más, tienes fan-out. Los analistas senior hacen
esta comprobación por reflejo cada vez que añaden un join.
Nota de entrevista
“Nuestro panel de ingresos se duplicó después de que alguien añadió un join con la tabla de promociones. ¿Qué pasó?”
es una pregunta de escenario común. Nombra el fan-out, explica el grano y ofrece ambas soluciones:
COUNT(DISTINCT ...) para los conteos y preagregación para las sumas.
Errores comunes
- Contar pedidos con
COUNT(*)tras el join. Cuenta líneas de pedido. UsaCOUNT(DISTINCT o.order_id)para los pedidos. - Sumar una columna a nivel de pedido tras el join.
SUM(o.shipping_fee)suma cada cargo una vez por línea. Agrega primero las líneas por pedido y luego haz el join. - Usar
SUM(DISTINCT o.shipping_fee)como solución. Dos pedidos distintos con el mismo cargo de $6.95 se colapsan en uno, así que el total sale demasiado bajo en lugar de demasiado alto. - Dividir dos conteos sin
* 1.0. En Postgres y SQL Server,COUNT(*) / COUNT(DISTINCT o.order_id)trunca 2.6 a 2.
En SQL Server
Aquí la división entera muerde: COUNT(*) / COUNT(DISTINCT o.order_id) divide dos valores int y devuelve un
número entero (2 en lugar de 2.5). Mantén el * 1.0, o escribe
CAST(COUNT(*) AS decimal(10, 2)) / COUNT(DISTINCT o.order_id).
¡Linterna encendida!
Practicaste: fan outone to manycount distinctgrain
- Resuelto
- Sin pistas
- Al primer intento
