La solicitud

De Tyler Morgan, Ingeniero de Datos Junior
Tu objetivo
Desde customers, trata los correos como iguales tras quitar los espacios y pasar a minúsculas. Devuelve una fila por correo que aparece en más de un registro de cliente, con: email (sin espacios y en minúsculas), records (número de filas de clientes), first_customer_id (el customer_id más bajo) y last_customer_id (el customer_id más alto).
- Lee de la tabla
customerspendiente - Muestra exactamente
emailrecordsfirst_customer_idlast_customer_idpendiente - 18 filaspendiente
Tu consulta
Cargando el editor…
Solución de referencia
| customer_id | first_name | last_name | city | |
|---|---|---|---|---|
| 1 | Owen | Hudson | owen.hudson@icloud.com | San Francisco |
| 2 | Douglas | Mendoza | dmendoza69@gmail.com | Atlanta |
| 3 | Henry | Brooks | hbrooks@lakesidecafe.com | Los Angeles |
| 4 | Brian | Graham | bgraham@bluecedarhomes.com | Seattle |
Mote revisa tu resultado
Presiona Ejecutar. Cada fila que cumple el objetivo se vuelve una luciérnaga en tu linterna.
Lo que recibe Tyler, y por qué
Tablas
Datos de la tienda Harbor & Pine, listos para consultar.
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 |
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 |
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
Los registros duplicados son uno de los problemas de calidad de datos más comunes que encuentra un analista. Los clientes se registran dos veces, un trabajo de sincronización se ejecuta dos veces, una importación del CRM repite un lote. Cada duplicado infla el “conteo de clientes” y divide el historial de una persona entre dos ID, lo que deprime el valor de vida y las tasas de recompra.
Paso 1: elige la clave
¿Qué hace que dos filas sean “el mismo cliente”? Aquí es el correo, pero solo después de normalizarlo.
'Owen.Hudson@iCloud.com ' y 'owen.hudson@icloud.com' son cadenas distintas y la misma persona.
Así que la clave es LOWER(TRIM(email)). Elegir la clave es una decisión de negocio; anota lo que elegiste.
Paso 2: agrupa y conserva los grupos de más de uno
SELECT
LOWER(TRIM(email)) AS email,
COUNT(*) AS records,
MIN(customer_id) AS first_customer_id,
MAX(customer_id) AS last_customer_id
FROM customers
GROUP BY LOWER(TRIM(email))
HAVING COUNT(*) > 1;
HAVING COUNT(*) > 1 es el buscador clásico de duplicados. MIN(customer_id) da el registro superviviente
para una fusión, suponiendo que los ID aumentan con el tiempo, algo que Tyler puede confirmar con signup_date.
Paso 3: mira las filas completas
Para inspeccionar cada fila duplicada, no solo una línea por correo, une de vuelta con la tabla:
WITH dupes AS (
SELECT LOWER(TRIM(email)) AS email_key
FROM customers
GROUP BY 1
HAVING COUNT(*) > 1
)
SELECT c.*
FROM customers AS c
JOIN dupes AS d ON d.email_key = LOWER(TRIM(c.email))
ORDER BY d.email_key, c.customer_id;
Contar personas únicas
Hasta que se fusionen los registros, reporta los clientes como COUNT(DISTINCT LOWER(TRIM(email))) en lugar de
COUNT(*), y explica por qué. Harbor & Pine tiene 2,018 filas de clientes pero 2,000 personas distintas. Una diferencia pequeña
aquí, pero en un CRM real los duplicados pueden llegar al 5-10 % de las filas.
Conservar una fila por grupo
Encontrar duplicados es la mitad del trabajo. Para elegir una fila por clave con todas sus columnas, la herramienta estándar es
ROW_NUMBER(), que se cubre en el próximo capítulo:
QUALIFY ROW_NUMBER() OVER (PARTITION BY LOWER(TRIM(email)) ORDER BY customer_id) = 1
QUALIFY funciona en DuckDB, BigQuery y Snowflake; en Postgres y MySQL envuelves la función de ventana
en un CTE y filtras en la consulta externa.
Errores comunes
- Agrupar por el correo en bruto y concluir “no hay duplicados”.
- Usar
SELECT DISTINCT *, que solo elimina las filas idénticas en todas las columnas. Los clientes duplicados suelen diferir en ID y fecha de registro, así que DISTINCT no elimina nada. - Borrar duplicados sin mover sus pedidos al ID superviviente. Es una decisión de ingeniería; el trabajo de un analista es encontrar y dimensionar el problema.
En SQL Server
El patrón GROUP BY … HAVING COUNT(*) > 1 es idéntico. Con la intercalación habitual que no distingue mayúsculas,
GROUP BY email ya fusiona Anna@x.com y anna@x.com, pero no ' anna@x.com', así que conserva el TRIM
(SQL Server 2017+, o LTRIM(RTRIM(...))).
¡Linterna encendida!
Practicaste: duplicateshavingnormalizationdata quality
- Resuelto
- Sin pistas
- Al primer intento
