La solicitud

De Lauren Hayes, Gerente de Email Marketing
Tu objetivo
Devuelve customer_id, first_name, last_name y email (de customers) para cada cliente que tenga al menos un pedido completado en 2024 y ningún pedido completado en 2025. Una fila por cliente.
- Lee de la tabla
customerspendiente - Lee de la tabla
orderspendiente - Muestra exactamente
customer_idfirst_namelast_nameemailpendiente
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 Lauren, 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 |
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 |
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 |
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
EXCEPT devuelve las filas de la primera consulta que no aparecen en la segunda. Es la “resta” de
las operaciones de conjuntos, y el orden de las dos consultas importa.
La consulta
SELECT customer_id FROM orders
WHERE status = 'completed' AND order_date >= '2024-01-01' AND order_date < '2025-01-01'
EXCEPT
SELECT customer_id FROM orders
WHERE status = 'completed' AND order_date >= '2025-01-01' AND order_date < '2026-01-01';
De los 997 clientes que completaron un pedido en 2024, 752 también compraron en 2025 (el nivel anterior), y
245 no. Igual que UNION e INTERSECT, un EXCEPT simple devuelve filas distintas, así que cada
cliente perdido aparece una vez. Intercambia las dos consultas y obtienes otra pregunta: compradores de
2025 que no compraron en 2024.
Poner la operación de conjuntos dentro de una CTE y hacer después el join o el filtro es el patrón limpio:
la comparación ocurre solo sobre customer_id, y los nombres y correos se añaden cuando la lista ya es
definitiva.
EXCEPT ALL
EXCEPT ALL (admitido en DuckDB y Postgres) trabaja con conteos en lugar de filas distintas: cada fila de
la derecha cancela una fila coincidente de la izquierda. Con estas ramas, que son una fila por pedido,
devuelve 1,564 filas. Ese número no es clientes ni pedidos de clientes perdidos, es “pedidos de 2024 que
sobran tras cancelar uno por cada pedido de 2025”. Rara vez lo quieres para listas de personas. Es útil
para conciliar dos copias de la misma tabla, donde los duplicados son filas reales.
EXCEPT, NOT EXISTS y NOT IN
El mismo “en A pero no en B” se puede escribir de tres formas, y difieren con los NULL. Toma los clientes
que nunca tuvieron una sesión web. web_sessions.customer_id es NULL para las visitas anónimas:
SELECT customer_id FROM customers
EXCEPT
SELECT customer_id FROM web_sessions; -- 205 rows
NOT EXISTS con una subconsulta correlacionada también devuelve 205. Pero
WHERE customer_id NOT IN (SELECT customer_id FROM web_sessions) devuelve 0: una vez que la lista
contiene un NULL, NOT IN nunca puede ser verdadero. EXCEPT trata los NULL como iguales entre sí y
simplemente compara filas, así que es inmune a esa trampa. El capítulo 11 tiene un nivel entero sobre el
fallo de NOT IN.
Errores comunes
- Dirección equivocada. Léelo como “A, salvo los que también están en B” y comprueba que la primera consulta es A.
- Filtros distintos en las dos ramas. Si la rama de 2025 olvida
status = 'completed', un cliente cuyo único pedido de 2025 fue cancelado deja de contar como perdido. - Columnas extra en la comparación.
SELECT customer_id, channelllamaría perdido a un cliente que solo cambió de canal. - Llamar tasa de abandono a 245. Es un conteo. La tasa es 245 / 997 = 24.6% de los compradores de 2024.
En SQL Server
EXCEPT funciona igual en SQL Server, pero no hay EXCEPT ALL. Oracle lo llama MINUS (EXCEPT desde
la 21c), y MySQL admite EXCEPT desde la 8.0.31, así que el código más antiguo usa NOT EXISTS.
¡Linterna encendida!
Practicaste: exceptset operationschurnnot exists
- Resuelto
- Sin pistas
- Al primer intento
