La solicitud

De Megan Collins, Directora de Ventas
Tu objetivo
Paso 1 de 3. Para septiembre a diciembre de 2025, devuelve month (texto 'YYYY-MM', usa strftime(order_date, '%Y-%m')), orders (número de pedidos completados distintos con al menos una línea contada), net_revenue (redondeado a 2) y avg_order_value (ingreso neto / pedidos, redondeado a 2). Ordena por month de forma ascendente.
- Lee de la tabla
orderspendiente - Lee de la tabla
order_itemspendiente - Muestra exactamente
monthordersnet_revenueavg_order_valuependiente
Paso 2 de 3. Compara la temporada navideña (del 1 de noviembre al 31 de diciembre) de 2024 y 2025 por categoría. Devuelve category, rev_2024 y rev_2025 (ingreso neto de cada temporada, redondeado a 2) y yoy_change_pct ((rev_2025 - rev_2024) * 100 / rev_2024, con sumas sin redondear, redondeado a 2). Ordena por yoy_change_pct de forma descendente.
- Lee de la tabla
productspendiente - Lee de la tabla
orderspendiente - Muestra exactamente
categoryrev_2024rev_2025yoy_change_pctpendiente
Paso 3 de 3. Para 2025, devuelve channel, nov_revenue y dec_revenue (ingreso neto de noviembre y de diciembre, redondeado a 2) y mom_change_pct ((dec - nov) * 100 / nov, con sumas sin redondear, redondeado a 2). Ordena por channel de forma ascendente.
- Lee de la tabla
orderspendiente - Lee de la tabla
order_itemspendiente - Muestra exactamente
channelnov_revenuedec_revenuemom_change_pctpendiente
Este caso se desbloquea cuando terminas:
La solicitud y la lección ya se pueden leer. Resuelve todos los niveles de esos capítulos, en cualquier orden, y este caso se abre.
Paso 1 · 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.
Paso 2 · tu consulta
Cargando el editor…
Solución de referencia
| product_id | name | category | subcategory | unit_price |
|---|---|---|---|---|
| 101 | Vale Velvet Loveseat | Furniture | Sofas | 1659.00 |
| 102 | Alder Oak Coffee Table | Furniture | Tables | 919.00 |
| 103 | Orchard Rattan Dining Chair | Furniture | Chairs | 789.00 |
| 104 | Upland Oak Bookcase | Furniture | Storage | 429.00 |
Mote revisa tu resultado
Presiona Ejecutar. Cada fila que cumple el objetivo se vuelve una luciérnaga en tu linterna.
Paso 3 · 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 Megan, 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 |
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 |
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 |
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
Cómo lee un analista este ticket
El mensaje de Megan contiene tres preguntas sobre tres cortes distintos de los datos: tiempo, categoría y canal. Antes de escribir una sola línea de SQL, fija la definición que comparten las tres, porque una revisión en la que el total mensual y el total por categoría no coinciden es peor que no tener revisión:
- Ingresos = ingreso neto por línea:
quantity * unit_price * (1 - discount_pct / 100.0).discount_pctse guarda en puntos porcentuales (15 significa 15%), así que dividir entre 100 no es opcional. - Qué pedidos: solo
completed. Los pedidos cancelados nunca se enviaron, los reembolsados se devolvieron y los pendientes todavía pueden fallar. Incluirlos infla sobre todo diciembre, porque los pendientes se concentran en los últimos días del año. - Qué líneas:
quantity > 0. Un puñado de líneas tiene cantidades cero o negativas por errores de captura. - No es ingreso:
shipping_fee. Vive enorders, uno por pedido, y tiene su propia pregunta.
Escribe la definición al principio de tu cuaderno o archivo de consultas. Después, cada paso es simplemente un GROUP BY distinto sobre la misma base.
Dos herramientas que usa este caso
Etiquetas de mes con strftime. strftime(date, format) convierte una fecha o marca de tiempo
en texto con la forma que le pidas. %Y es el año de cuatro dígitos y %m el mes de dos dígitos:
SELECT order_id, order_date, strftime(order_date, '%Y-%m') AS month
FROM orders
ORDER BY order_id
LIMIT 2;
| order_id | order_date | month |
|---|---|---|
| 1001 | 2023-01-01 20:51:19 | 2023-01 |
| 1002 | 2023-01-02 07:58:46 | 2023-01 |
Como '2025-09' se ordena antes que '2025-10' como texto, ORDER BY month deja estas etiquetas en
orden de calendario. En DuckDB puedes hacer GROUP BY por el alias month.
Ponerle nombre a un paso con WITH. Una CTE (expresión de tabla común) le da nombre a una
consulta para que la siguiente la lea como si fuera una tabla. Es útil cuando quieres agregar primero
y trabajar con los totales después:
WITH by_month AS (
SELECT strftime(order_date, '%Y-%m') AS month, COUNT(*) AS orders
FROM orders
WHERE status = 'completed'
GROUP BY month
)
SELECT month, orders
FROM by_month
WHERE orders > 600
ORDER BY month;
La parte entre paréntesis se ejecuta primero y produce una tabla pequeña llamada by_month, con una
fila por mes; el SELECT que sigue filtra esa tabla y devuelve tres meses de mucha actividad: 2024-12
(629), 2025-11 (651) y 2025-12 (634). Los pasos 2 y 3 usan exactamente esto: una CTE guarda el
ingreso sin redondear de cada periodo y el SELECT exterior redondea y calcula el crecimiento.
Paso 1: la temporada frente al otoño
La base es order_items unida a orders. Cada línea tiene exactamente un pedido, así que el JOIN
agrega estado y fecha sin agregar filas. Contar pedidos es donde el grano te muerde: después del
JOIN hay una fila por línea, así que COUNT(*) cuenta líneas, no pedidos. COUNT(DISTINCT o.order_id)
es la solución, y el valor medio del pedido es el ingreso dividido entre ese conteo distinto.
Agrupar por strftime(o.order_date, '%Y-%m') da meses en texto ordenable. Filtra la fecha con un
rango semiabierto (>= '2025-09-01' AND < '2026-01-01') para no perder la última noche de
diciembre, una marca de tiempo como 2025-12-31 21:14.
Paso 2: año contra año por categoría
Comparar dos periodos lado a lado es agregación condicional: un SUM(CASE WHEN ... END) por periodo.
Las filas fuera de ambas temporadas dan NULL dentro del CASE y SUM las ignora. Una CTE mantiene
las sumas sin redondear para que el porcentaje de crecimiento se calcule con valores exactos;
redondear primero y dividir después puede mover el último decimal.
El crecimiento de las fiestas es una pregunta año contra año, no mes contra mes: comparar diciembre con noviembre te habla del calendario, comparar diciembre de 2025 con diciembre de 2024 te habla del negocio.
Paso 3: ¿diciembre superó a noviembre?
El mismo patrón, dos meses, agrupado por canal. Es de esperar que diciembre quede cerca de noviembre: el Black Friday y el Cyber Monday caen a finales de noviembre, mientras que diciembre tiene el empujón de HOLIDAY20 y una Nochebuena y una Navidad muertas. Saber qué canal creció es la parte útil de la respuesta.
SQL y pandas lado a lado
| Idea | SQL | pandas |
|---|---|---|
| ingreso por línea | expresión dentro de SUM |
una columna nueva con assign |
| etiqueta de mes | strftime(order_date, '%Y-%m') |
order_date.dt.strftime('%Y-%m') |
| pedidos distintos | COUNT(DISTINCT order_id) |
("order_id", "nunique") |
| dos periodos como columnas | SUM(CASE WHEN ...) |
filtrar y luego pivot_table |
Errores comunes
- Unir
ordersaorder_itemsy luego sumarshipping_feecuenta cada tarifa una vez por línea. BETWEEN '2025-12-01' AND '2025-12-31'sobre una marca de tiempo se detiene a medianoche del 31.- La división entera no es un riesgo aquí porque la expresión ya es decimal, pero multiplicar por
100.0en lugar de100mantiene a salvo las cuentas de porcentajes en cualquier caso.
¡Linterna encendida!
Practicaste: date filterconditional aggregationcount distinctyoymom
- Resuelto
- Sin pistas
- Al primer intento
