1. Faro
  2. Faro

Nivel 1 · Costo por conversión en SQL

La solicitud

De Jordan Reyes, Director de Marketing

“Finanzas me pide justificar el presupuesto de 2025. ¿Puedes mostrarme qué tan bien convierte cada fuente de tráfico, cuánto nos costó cada sesión convertida por canal y cómo se movió la búsqueda de pago mes a mes?”

Tu objetivo

Paso 1 de 3. Devuelve source, sessions (número de sesiones), conversions (sesiones convertidas) y conversion_rate_pct (conversions * 100 / sessions, redondeado a 2). Ordena por conversion_rate_pct de forma descendente.

  • Lee de la tabla web_sessionspendiente
  • Muestra exactamente source sessions conversions conversion_rate_pctpendiente
  • 5 filas, ordenadas como se pidependiente

Paso 1 · tu consulta

SQL

Cargando el editor…

    ResultadoVista previa de web_sessions · presiona Ejecutar para ver el tuyo
    Vista previa de web_sessions
    session_idcustomer_idstarted_atdevicesource
    1NULL2023-01-01 23:02:24mobilepaid
    2182023-01-01 15:07:49desktoppaid
    3NULL2023-01-01 16:55:46desktopdirect
    4NULL2023-01-01 12:43:28mobileorganic

    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 web_sessions · presiona Ejecutar para ver el tuyo
      Vista previa de web_sessions
      session_idcustomer_idstarted_atdevicesource
      1NULL2023-01-01 23:02:24mobilepaid
      2182023-01-01 15:07:49desktoppaid
      3NULL2023-01-01 16:55:46desktopdirect
      4NULL2023-01-01 12:43:28mobileorganic

      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 web_sessions · presiona Ejecutar para ver el tuyo
        Vista previa de web_sessions
        session_idcustomer_idstarted_atdevicesource
        1NULL2023-01-01 23:02:24mobilepaid
        2182023-01-01 15:07:49desktoppaid
        3NULL2023-01-01 16:55:46desktopdirect
        4NULL2023-01-01 12:43:28mobileorganic

        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.

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

        La lección

        Cómo lee un analista este ticket

        Jordan necesita tres números por canal: cuántas visitas trajo, cuántas de esas convirtieron y cuánto costó. Dos tablas los contienen, y ninguna está al grano de la respuesta:

        • web_sessions tiene una fila por visita. source dice de dónde vino la visita y converted dice si terminó en una compra.
        • marketing_spend tiene una fila por día y por canal. Sus valores de channel (paid, email, social) coinciden con web_sessions.source, pero organic y direct no tienen ningún gasto, porque nadie paga por ellos.

        La respuesta es una fila por fuente (o por fuente y mes). Así que el plan es: agrega cada tabla a ese grano por su cuenta y después une los dos resultados pequeños.

        Paso 1: tasa de conversión

        Una tabla, un GROUP BY. El COUNT(*) FILTER (WHERE converted) de DuckDB cuenta solo las filas convertidas; SUM(CASE WHEN converted THEN 1 ELSE 0 END) es la forma portable de escribirlo. Multiplica por 100.0 antes de dividir para que la división sea decimal.

        Ten cuidado con cómo describes el resultado. En este conjunto de datos las sesiones no están vinculadas a pedidos, así que esta es una tasa de conversión de sesiones tal como la reportó la herramienta de analítica web, no un conteo de pedidos.

        Paso 2: la trampa de la duplicación y su solución

        La consulta tentadora une las tablas en bruto:

        SELECT w.source, SUM(m.amount) ...
        FROM web_sessions AS w
        JOIN marketing_spend AS m ON m.channel = w.source

        Cada sesión de paid coincide con cada día de gasto de paid, así que el gasto se multiplica por el número de sesiones. Los números salen en miles de millones y nadie lo nota hasta que lo nota el director financiero. Incluso unir también por fecha repite el gasto de cada día una vez por cada sesión de ese día.

        La solución es el patrón del capítulo de CTE: preagrega antes de unir. Una CTE reduce las sesiones a una fila por fuente, otra reduce el gasto a una fila por canal, y entonces el JOIN es uno a uno. Parte del lado de las sesiones con un LEFT JOIN para que organic y direct se queden en la respuesta con gasto NULL. Dejarlas fuera ocultaría que aportan gratis una gran parte de las conversiones.

        NULLIF(a, b) devuelve NULL cuando a es igual a b, y a en caso contrario. Así, NULLIF(c.conversions, 0) convierte un cero en NULL, y cualquier cosa dividida entre NULL es NULL:

        SELECT 5000 / NULLIF(25, 0) AS per_conversion,   -- 200.0
               5000 / NULLIF(0, 0)  AS no_conversions;   -- NULL, not an error

        Un canal con gasto y sin conversiones da entonces un costo por conversión NULL. Postgres y la mayoría de los almacenes de datos lanzan un error de división entre cero sin esto, así que escríbelo de forma explícita.

        Paso 3: mes a mes para paid

        La misma receta, con month añadido al grano en ambos lados. strftime(spend_date, '%Y-%m') crea la etiqueta de mes en texto ('2025-03'), exactamente como en el caso de las fiestas; constrúyela igual en ambas CTE para que las claves del JOIN coincidan. Las dos CTE tienen una fila por mes, así que aquí un JOIN interno es seguro. Las cifras mensuales de paid son ruidosas porque las conversiones por mes son números pequeños; fíjate en la dirección, no en los decimales.

        SQL y pandas lado a lado

        Idea SQL pandas
        contar donde es verdadero COUNT(*) FILTER (WHERE converted) ("converted", "sum")
        preagregar una CTE con GROUP BY groupby(...).agg(...) en un nuevo DataFrame
        conservar fuentes sin gasto LEFT JOIN merge(how="left")
        división segura NULLIF(x, 0) dividir entre 0 da inf; reemplázalo por NaN

        Errores comunes

        • Unir tablas en bruto con granos distintos. Pregúntate siempre «¿cuántas filas por clave tiene cada lado?» antes de un JOIN.
        • Días de gasto faltantes. Faltan doce filas de día y canal en marketing_spend por caídas del seguimiento. Los totales quedan ligeramente bajos; dilo en el informe en lugar de inventar valores.
        • Filtrar el año solo en un lado. Ambas tablas necesitan el filtro de 2025, o el gasto de tres años se divide entre las conversiones de uno.
        • Atribución. «Fuente de la sesión» es atribución de último contacto. Es el punto de partida habitual y la primera pregunta habitual de Finanzas; menciónalo.

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