WHERE — Referencia de SQL en Marketing Cloud
Cómo filtrar filas en MC SQL — operadores de comparación y lógicos, la trampa del IS NULL, los pitfalls de performance del NOT IN, y la regla de paréntesis que evita bugs silenciosos de precedencia.
WHERE es donde le decís al motor qué filas conservar. En MC SQL funciona como T-SQL — operadores de comparación, AND / OR / NOT, IN, BETWEEN, LIKE, IS NULL. Los bugs no están en la sintaxis. Están en tres lugares: manejo de NULL (lógica de tres valores), NOT IN contra sets grandes (performance), y precedencia de operadores cuando mezclás AND con OR (semántica silenciosa).
Sintaxis oficial
-- Operadores de comparación: = != <> < > <= >=
SELECT SubscriberKey, EmailAddress
FROM master_subscribers
WHERE Status = 'Active'
AND LastPurchase >= DATEADD(day, -90, GETDATE());
-- Lógicos: AND, OR, NOT (usá paréntesis cuando mezclás)
SELECT SubscriberKey
FROM master_subscribers
WHERE Status = 'Active'
AND (LoyaltyTier = 'gold' OR LoyaltyTier = 'silver');
-- IN / NOT IN contra una lista literal
SELECT SubscriberKey
FROM master_subscribers
WHERE LoyaltyTier IN ('gold', 'silver', 'platinum');
-- BETWEEN (inclusivo en ambos extremos)
SELECT SubscriberKey
FROM master_subscribers
WHERE LastPurchase BETWEEN '2026-01-01' AND '2026-03-31';
-- IS NULL / IS NOT NULL — nunca uses = NULL
SELECT SubscriberKey
FROM master_subscribers
WHERE LoyaltyTier IS NULL;
-- LIKE para pattern matching (ver la página dedicada de LIKE)
SELECT SubscriberKey
FROM master_subscribers
WHERE EmailAddress LIKE '%@enterprise.com';WHERE corre después de que FROM / JOIN resuelven filas pero antes de GROUP BY y HAVING. Filtrá filas en WHERE; filtrá agregados en HAVING.
Referencia:
- Salesforce Help — Sintaxis T-SQL soportada ↗
- Salesforce Help — Queries SQL para Data Views (ejemplos de filtrado) ↗
Lo que sobrevive en producción
IS NULL, nunca = NULL
WHERE LoyaltyTier = NULL devuelve cero filas — siempre, incluso si la columna es NULL en millones de filas — porque en lógica de tres valores NULL = NULL es desconocido, no verdadero. Lo mismo para != NULL y <> NULL.
-- BUG — saltea silenciosamente cada fila NULL
SELECT SubscriberKey
FROM master_subscribers
WHERE LoyaltyTier = NULL;
-- CORRECTO
SELECT SubscriberKey
FROM master_subscribers
WHERE LoyaltyTier IS NULL;
-- COMBINACIÓN COMÚN — "dame filas donde LoyaltyTier sea algo
-- distinto de 'gold', INCLUYENDO filas donde sea NULL"
SELECT SubscriberKey
FROM master_subscribers
WHERE LoyaltyTier <> 'gold' OR LoyaltyTier IS NULL;El tercer ejemplo es el que la mayoría se olvida. Sin el OR ... IS NULL explícito, las filas NULL se caen del resultado en silencio. Ver gotchas — #5.
Parenthesizá cuando mezclás AND y OR
AND ata más fuerte que OR. WHERE A OR B AND C parsea como WHERE A OR (B AND C), lo cual casi nunca es como lo lee el próximo dev. Siempre parenthesizá.
-- EN RIESGO — dependiendo de qué quiso decir el escritor, esto está mal
WHERE Status = 'Active' OR Status = 'Pending' AND LoyaltyTier = 'gold'
-- Lo de arriba parsea como:
WHERE Status = 'Active' OR (Status = 'Pending' AND LoyaltyTier = 'gold')
-- Si la intención era "subscribers gold que están active o pending",
-- escribilo explícito:
WHERE (Status = 'Active' OR Status = 'Pending')
AND LoyaltyTier = 'gold'Los paréntesis no cambian el parsing cuando la precedencia ya matchea tu intención — pero le dicen al próximo lector que lo pensaste. Incluilos siempre.
NOT IN contra una subquery es trampa de performance
NOT IN (SELECT key FROM big_table) se lee limpio, corre lento, y se pone dramáticamente peor a medida que la subquery crece. El optimizer de MC SQL no reescribe confiablemente NOT IN como anti-join.
-- EN RIESGO — NOT IN contra una subquery, se vuelve lento con escala
SELECT SubscriberKey
FROM master_subscribers
WHERE SubscriberKey NOT IN (
SELECT SubscriberKey FROM suppression_list
);
-- DURABLE — anti-join vía LEFT JOIN ... IS NULL
SELECT s.SubscriberKey
FROM master_subscribers s
LEFT JOIN suppression_list x
ON s.SubscriberKey = x.SubscriberKey
WHERE x.SubscriberKey IS NULL;NOT IN contra una lista literal chica (NOT IN ('inactive', 'bounced', 'unsubscribed')) está bien. NOT IN (SELECT ...) contra un Data Extension es la trampa. Ver JOIN para el idiom completo de anti-join.
Pushá el WHERE lo más cerca de la fuente posible
Cuando stagéas en múltiples Activities, filtrá en la primera Activity que toca la fuente — no en la última. Cada fila que se cae temprano es una fila que no tiene que fluir por joins, transformaciones y la escritura del DE de destino.
-- EN RIESGO — trae cada fila de master_subscribers a través del join,
-- después filtra al final. El join hace trabajo innecesario.
INSERT INTO de_stg_target
SELECT s.SubscriberKey, p.LastPurchase
FROM master_subscribers s
INNER JOIN purchases p
ON s.SubscriberKey = p.SubscriberKey
WHERE s.Status = 'Active';
-- DURABLE — primero stagéa subscribers activos, después joineá. El
-- join ve solo las filas que sobrevivieron al WHERE.
INSERT INTO de_stg_active_subs
SELECT SubscriberKey, EmailAddress
FROM master_subscribers
WHERE Status = 'Active';
INSERT INTO de_stg_target
SELECT s.SubscriberKey, p.LastPurchase
FROM de_stg_active_subs s
INNER JOIN purchases p
ON s.SubscriberKey = p.SubscriberKey;El optimizer de MC SQL muchas veces baja los filtros por vos, pero "muchas veces" no es "siempre" — y en un budget de 30 min no querés enterarte cuál query no optimizó.
Filtros de fecha: preferí ventanas en cantidad de días sobre math de meses
DATEADD(month, -3, GETDATE()) devuelve resultados inconsistentes en límites de mes (Feb 28/29, 31 Mar → 28 Feb, etc.). Para filtros WHERE que controlan elegibilidad de Send, usá conteos de días.
-- EN RIESGO — math de meses, edge cases en límites de año
WHERE LastPurchase >= DATEADD(month, -3, GETDATE())
-- ESTABLE — conteo de días explícito, se comporta igual cada corrida
WHERE LastPurchase >= DATEADD(day, -90, GETDATE()) -- "últimos ~3 meses"Si la regla de negocio es "últimos 3 meses", traducila a una cantidad de días una vez en el momento del diseño y documentala en un comentario. Ver gotchas — #8.
Decisión rápida
Usá WHERE cuando:
- Filtrás filas de
FROM/JOINbasado en valores de columna. - Sacás filas que no necesitás antes de que fluyan a joins, agregados o al DE de destino.
Usá HAVING en lugar de WHERE cuando:
- Filtrás sobre el resultado de un agregado (
HAVING COUNT(*) > 1,HAVING SUM(x) >= 100).WHEREcorre antes del grouping;HAVINGcorre después.
Andá a LEFT JOIN ... IS NULL en lugar de NOT IN cuando:
- El argumento de
NOT INes una subquery, no una lista literal chica. - Ver JOIN para el patrón anti-join.
Andá a IS NULL en lugar de = NULL cuando:
- Siempre.
= NULLestá siempre mal, incluso cuando parece que funciona (devuelve cero filas, lo que puede verse "correcto" hasta el día que una fila NULL debería haberse incluido).
Relacionado
- Basics — el subset de T-SQL soportado
- SELECT — lo que
WHEREfiltra - FROM — fuentes contra las que
WHEREcorre - JOIN — idiom anti-join para el pitfall de performance de
NOT IN - MC SQL gotchas — #5 (NULL de tres valores + truncación de longitud), #8 (aritmética de fechas)
Próximas páginas de referencia: LIKE · CASE · INSERT INTO · String / Date / Numeric / Conversion / Aggregate / Null Functions · Style Guide.
Más snippets how-to para debugging común en producción — sends de email, largo de valores, alcance de contactos, etc.