1. Faro

Nivel 11 · Desafío final: retención por cohortes

La solicitud

De Catherine Hale, Directora Ejecutiva

“El mes que viene presento a inversores. Necesito tres cosas: la participación de cada categoría en los ingresos de 2025, cuánto tardan los clientes en volver para un segundo pedido y una vista mensual de retención por cohortes del primer semestre de 2025.”

Tu objetivo

Paso 1 de 3. Para los pedidos completados realizados en 2025, devuelve una fila por categoría con: category, net_revenue (redondeado a 2) y pct_of_total (la participación de la categoría en los ingresos netos totales de 2025 sumando todas las categorías, por 100, redondeada a 1). Ordena por net_revenue descendente.

  • Lee de la tabla productspendiente
  • Lee de la tabla orderspendiente
  • Muestra exactamente category net_revenue pct_of_totalpendiente

Paso 1 · 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 2 · tu consulta

    SQL

    Cargando el editor…

      ResultadoVista previa de customers · presiona Ejecutar para ver el tuyo
      Vista previa de customers
      customer_idfirst_namelast_nameemailcity
      1OwenHudsonowen.hudson@icloud.comSan Francisco
      2DouglasMendozadmendoza69@gmail.comAtlanta
      3HenryBrookshbrooks@lakesidecafe.comLos Angeles
      4BrianGrahambgraham@bluecedarhomes.comSeattle

      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.

        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
        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
        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

        El análisis de cohortes es la prueba clásica para llevar a casa de los analistas: necesita CTE, funciones de ventana, lógica de fechas y una definición clara, todo a la vez. Este desafío final te lleva hasta ahí paso a paso.

        Paso 1: participación sobre el total

        Agrega por categoría y luego divide cada fila entre el total general:

        ROUND(100.0 * net_revenue / SUM(net_revenue) OVER (), 1) AS pct_of_total

        Un OVER () vacío hace que la ventana sea todo el resultado, así que cada fila ve el mismo total. Sin subconsulta, sin join. Las participaciones deben sumar cerca de 100; si suman mucho más, un join multiplicó filas.

        Paso 2: tiempo hasta el segundo pedido

        ROW_NUMBER numera los pedidos de cada cliente en orden cronológico. LAG da entonces el intervalo de cada pedido respecto al anterior, y conservar order_number = 2 deja exactamente una fila por cliente que vuelve: el intervalo entre su primer y su segundo pedido. El desempate por order_id protege contra dos pedidos con la misma marca de tiempo.

        El starter muestra los pedidos numerados de un solo cliente. Inspeccionar una sola entidad así es como se comprueba que una ventana tiene sentido antes de agregarla.

        Los clientes que nunca hicieron un segundo pedido no entran en el promedio. Eso es correcto para “cuánto tardan en volver”, pero preséntalo junto a cuántos no volvieron nunca, o el número se ve mejor que la realidad.

        Paso 3: retención por cohortes

        Tres definiciones forman una tabla de cohortes:

        • Cohorte: el mes del primer pedido completado de un cliente. MIN(order_month) OVER (PARTITION BY customer_id) la adjunta a cada una de sus filas sin colapsarlas.
        • Actividad: un cliente está activo en un mes si hizo al menos un pedido completado. La CTE customer_months usa SELECT DISTINCT a propósito, para obtener una fila por cliente por mes, el grano correcto para “activo o no”.
        • Retenido en el mes 1: activo en el mes calendario inmediatamente posterior al mes de la cohorte.
        COUNT(DISTINCT CASE WHEN order_month = CAST(cohort_month + INTERVAL '1 month' AS DATE)
                            THEN customer_id END) AS retained_m1

        COUNT(DISTINCT CASE ...) cuenta los clientes que cumplen la condición. El CASE devuelve NULL en los demás casos, y COUNT ignora los NULL.

        El filtro de cohortes se aplica después de la ventana, en la consulta externa. Filtrar los pedidos a 2025 antes de calcular el MIN asignaría a una cohorte de 2025 a los clientes que compraron por primera vez en 2024, la misma trampa que con LAG y las medias móviles.

        Notas de dialecto

        Sumar un mes: DuckDB y Postgres aceptan date + INTERVAL '1 month' (el resultado es un timestamp, de ahí el cast). BigQuery usa DATE_ADD(cohort_month, INTERVAL 1 MONTH), MySQL DATE_ADD(cohort_month, INTERVAL 1 MONTH), Snowflake DATEADD(month, 1, cohort_month).

        Ir más allá

        La tabla de cohortes completa tiene una columna para cada desfase de mes (m1, m2, m3, …), que suele calcularse con DATEDIFF en meses entre order_month y cohort_month, agrupando por cohorte y desfase y luego pivotando. En una entrevista, acertar el mes 1, con definiciones claras dichas en voz alta, vale más que una cuadrícula completa hecha a toda prisa.

        Errores comunes

        • Filtrar los pedidos a 2025 antes del MIN. Los clientes que compraron por primera vez en 2024 quedan entonces en una cohorte de 2025. Calcula el mes de la cohorte sobre todos los pedidos y filtra por fuera.
        • Contar filas en lugar de clientes. COUNT(*) sobre filas de pedidos cuenta pedidos, no clientes activos. Usa COUNT(DISTINCT customer_id), o una CTE de una fila por cliente por mes.
        • Reportar solo el intervalo promedio hasta un segundo pedido. Cubre únicamente a los clientes que volvieron; muestra a su lado cuántos nunca hicieron un segundo pedido.
        • Numerar pedidos sin un criterio de desempate. Dos pedidos con la misma marca de tiempo pueden intercambiar el primer y el segundo puesto. Añade order_id al ORDER BY del ROW_NUMBER.

        En SQL Server

        Suma un mes con DATEADD(month, 1, cohort_month); DATEDIFF(month, cohort_month, order_month) da el desfase de mes para una cuadrícula completa de cohortes. Los intervalos en días necesitan DATEDIFF(day, prev_day, order_day), porque - entre fechas da error. Las funciones de ventana en sí no cambian.

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