1. Faro
  2. Faro

Nivel 1 · Revisión de la temporada navideña en SQL

La solicitud

De Megan Collins, Directora de Ventas

“Antes de la reunión de arranque de enero necesito la historia de las fiestas de 2025: cómo se comparó la temporada con los meses de otoño, qué categorías crecieron frente a las fiestas de 2024 y si diciembre superó a noviembre en cada canal.”

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 month orders net_revenue avg_order_valuependiente

Paso 1 · tu consulta

SQL

Cargando el editor…

    ResultadoVista previa de orders · presiona Ejecutar para ver el tuyo
    Vista previa de orders
    order_idcustomer_idorder_datestatuschannel
    10011252023-01-01 20:51:19completedweb
    10021562023-01-02 07:58:46completedweb
    1003222023-01-02 10:25:27completedmarketplace
    1004482023-01-02 11:21:51completedmarketplace

    Mote revisa tu resultado

    Presiona Ejecutar. Cada fila que cumple el objetivo se vuelve una luciérnaga en tu linterna.

    Paso 2 · tu consulta

    SQL

    Cargando el editor…

      ResultadoVista previa de products · presiona Ejecutar para ver el tuyo
      Vista previa de products
      product_idnamecategorysubcategoryunit_price
      101Vale Velvet LoveseatFurnitureSofas1659.00
      102Alder Oak Coffee TableFurnitureTables919.00
      103Orchard Rattan Dining ChairFurnitureChairs789.00
      104Upland Oak BookcaseFurnitureStorage429.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

      SQL

      Cargando el editor…

        ResultadoVista previa de orders · presiona Ejecutar para ver el tuyo
        Vista previa de orders
        order_idcustomer_idorder_datestatuschannel
        10011252023-01-01 20:51:19completedweb
        10021562023-01-02 07:58:46completedweb
        1003222023-01-02 10:25:27completedmarketplace
        1004482023-01-02 11:21:51completedmarketplace

        Mote revisa tu resultado

        Presiona Ejecutar. Cada fila que cumple el objetivo se vuelve una luciérnaga en tu linterna.

        Tablas

        Datos de la tienda Harbor & Pine, listos para consultar.

        orders12,000 filas · 7 columnas
        • order_idinteger
        • customer_idinteger
        • order_datetimestamp
        • statusvarchar
        • channelvarchar
        • shipping_feedecimal
        • coupon_codevarchar
        Primeras 5 filas de orders
        order_idcustomer_idorder_datestatuschannelshipping_feecoupon_code
        10011252023-01-01 20:51:19completedweb0.00NULL
        10021562023-01-02 07:58:46completedweb0.00NULL
        1003222023-01-02 10:25:27completedmarketplace4.99WELCOME10
        1004482023-01-02 11:21:51completedmarketplace4.99WELCOME10
        1005822023-01-02 11:43:36completedweb0.00WELCOME10
        order_items30,338 filas · 6 columnas
        • order_item_idinteger
        • order_idinteger
        • product_idinteger
        • quantityinteger
        • unit_pricedecimal
        • discount_pctdecimal
        Primeras 5 filas de order_items
        order_item_idorder_idproduct_idquantityunit_pricediscount_pct
        11001121931.990.00
        21001154311.990.00
        31002154411.990.00
        410021941149.000.00
        51002193219.990.00
        products120 filas · 8 columnas
        • product_idinteger
        • namevarchar
        • categoryvarchar
        • subcategoryvarchar
        • unit_pricedecimal
        • unit_costdecimal
        • launched_ondate
        • is_activeboolean
        Primeras 5 filas de products
        product_idnamecategorysubcategoryunit_priceunit_costlaunched_onis_active
        101Vale Velvet LoveseatFurnitureSofas1659.00NULL2023-09-11true
        102Alder Oak Coffee TableFurnitureTables919.00503.502024-05-16true
        103Orchard Rattan Dining ChairFurnitureChairs789.00429.872023-05-27true
        104Upland Oak BookcaseFurnitureStorage429.00214.702022-12-20true
        105Avery Leather Bed FrameFurnitureBeds449.00232.702021-08-31true
        customers2,018 filas · 9 columnas
        • customer_idinteger
        • first_namevarchar
        • last_namevarchar
        • emailvarchar
        • cityvarchar
        • statevarchar
        • signup_datedate
        • segmentvarchar
        • referral_sourcevarchar
        Primeras 5 filas de customers
        customer_idfirst_namelast_nameemailcitystatesignup_datesegmentreferral_source
        1OwenHudsonowen.hudson@icloud.comSan FranciscoCA2022-06-02consumersearch
        2DouglasMendozadmendoza69@gmail.comAtlantaGA2022-06-02consumerNULL
        3HenryBrookshbrooks@lakesidecafe.comLos AngelesCA2022-06-04businessNULL
        4BrianGrahambgraham@bluecedarhomes.comSeattleWA2022-06-06businesspinterest
        5KyleJimenezkjimenez27@yahoo.comIndianapolisIN2022-06-07consumerNULL
        returns1,673 filas · 5 columnas
        • return_idinteger
        • order_item_idinteger
        • returned_ondate
        • reasonvarchar
        • refund_amountdecimal
        Primeras 5 filas de returns
        return_idorder_item_idreturned_onreasonrefund_amount
        1142023-01-10damaged83.69
        2422023-01-10wrong_item19.99
        31052023-01-14wrong_item10.99
        4242023-01-16changed_mind26.99
        51262023-01-16late20.99
        employees60 filas · 8 columnas
        • employee_idinteger
        • first_namevarchar
        • last_namevarchar
        • departmentvarchar
        • titlevarchar
        • manager_idinteger
        • hire_datedate
        • salaryinteger
        Primeras 5 filas de employees
        employee_idfirst_namelast_namedepartmenttitlemanager_idhire_datesalary
        1CatherineHaleExecutiveChief Executive OfficerNULL2016-03-01285000
        2RobertKimExecutiveChief Operating Officer12017-01-09235000
        3MeganCollinsSalesHead of Sales12018-04-16172000
        4ChloeGutierrezSalesAccount Executive32020-01-2172000
        5FrankBellSalesAccount Executive32023-05-2173000
        web_sessions40,000 filas · 7 columnas
        • session_idinteger
        • customer_idinteger
        • started_attimestamp
        • devicevarchar
        • sourcevarchar
        • pages_viewedinteger
        • convertedboolean
        Primeras 5 filas de web_sessions
        session_idcustomer_idstarted_atdevicesourcepages_viewedconverted
        1NULL2023-01-01 23:02:24mobilepaid1false
        2182023-01-01 15:07:49desktoppaid1false
        3NULL2023-01-01 16:55:46desktopdirect7false
        4NULL2023-01-01 12:43:28mobileorganic3false
        5NULL2023-01-01 13:06:21mobilesocial2false
        marketing_spend3,276 filas · 3 columnas
        • spend_datedate
        • channelvarchar
        • amountdecimal
        Primeras 5 filas de marketing_spend
        spend_datechannelamount
        2023-01-01paid492.99
        2023-01-01email37.38
        2023-01-01social200.53
        2023-01-02paid709.13
        2023-01-02email64.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_pct se 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 en orders, 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 orders a order_items y luego sumar shipping_fee cuenta 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.0 en lugar de 100 mantiene a salvo las cuentas de porcentajes en cualquier caso.

        Elige qué puede ejecutarse en este sitio. Puedes cambiarlo cuando quieras desde "Configurar cookies", al pie de cada página.