SQL de Marketing Cloud: Style Guide
Las reglas opinionadas que Cleon aplica a cada SQL Activity de MC que entrega — naming, formato, comentarios, patrones a preferir, anti-patrones a rechazar — destiladas de los gotchas y las páginas de referencia en un solo documento de disciplina.
Esta es la página donde Cleon deja de describir lo que MC SQL es y empieza a decir lo que hacemos con él. Salesforce define lo que funciona. Las páginas de referencia documentan la sintaxis. Los gotchas documentan lo que rompe a escala. Esta Style Guide es la disciplina que mantiene una implementación mantenible un año después de que la entregamos.
Usala como checklist antes de mergear cualquier SQL Activity nueva a producción. Las reglas son cortas a propósito — cuando una regla necesita explicación, la explicación está en la página que linkea.
Naming
Los Data Extensions siguen una convención de prefijo
Decidida una vez, antes de crear el primer DE. Renombrar 200 después es la alternativa.
| Prefijo | Contiene |
|---|---|
| DE_ | Master Data Extension propiedad de la implementación |
| de_stg_ | DE de staging — se reconstruye en cada corrida, seguro de truncar |
| de_log_ | Log de corrida — append-only, indexado por fecha |
| de_log_<sdv>_ | Snapshot de una System Data View (ej. de_log_sent_30d) |
| de_lookup_ | DE de referencia / config que marketing edita sin tocar SQL |
| TS_ | Triggered Send Definition |
| J_ | Journey |
| Auto_ | Automation |
| CR_ | Code Resource |
El patrón: el prefijo comunica lifecycle, el resto del nombre comunica propósito. de_stg_active_subs_30d se lee de un pasada.
Los aliases de columna llevan intención
Una columna llamada EmailAddressLower lleva la decisión (esto se lowereó en momento de query). Una columna llamada Email1 no lleva nada. Siempre aliasá los valores normalizados con un nombre que documente la normalización.
Los aliases de tabla son cortos, minúscula, y consistentes
s para subscribers, p para purchases, lt para loyalty_tier. Una sola letra cuando solo hay una fuente con esa inicial; dos letras cuando habría colisión. Nunca Master_Subscribers AS Master_Subscribers. Ver FROM.
Formato
Una columna por línea para SELECT con más de 3 columnas
-- Más de 3 columnas, línea-por-columna hace los diffs y code review legibles
SELECT
s.SubscriberKey,
s.EmailAddress,
s.LoyaltyTier,
s.LastPurchase,
p.Amount AS LastPurchaseAmount
FROM master_subscribers s
INNER JOIN purchases p
ON s.SubscriberKey = p.SubscriberKey;Keywords UPPER, identificadores minúscula / case-configurado
SELECT, FROM, WHERE, INNER JOIN, GROUP BY, etc. en mayúscula. Los nombres de tabla matchean la configuración del DE (que es case-insensitive pero la consistencia hace los diffs más limpios). Los nombres de columna siguen cómo están definidos en el DE.
Las condiciones de JOIN en su propia línea indentada
FROM master_subscribers s
INNER JOIN purchases p
ON s.SubscriberKey = p.SubscriberKey
INNER JOIN loyalty_tier lt
ON s.LoyaltyTier = lt.TierCodeCuando el ON está en su propia línea, la estructura del join se vuelve scaneable. Múltiples condiciones ON van una por línea.
Indentación a 2 espacios, nunca tabs
Los tabs renderean distinto entre editores y herramientas de code review. Dos espacios es la convención en la UI de SQL Activity y en nuestros docs.
Comentarios
No comentes lo que el código hace — comentá el por qué
El código dice qué. Los comentarios agregan el contexto que el código no puede llevar.
-- INÚTIL — repite lo que el código ya dice
-- Conseguir todos los subscribers activos
SELECT * FROM master_subscribers WHERE Status = 'Active';
-- ÚTIL — explica por qué este filtro específico
-- Excluyendo el status 'pending' porque el webhook de verificación
-- (agregado 2026-04) no se dispara para imports legacy hasta el día 7.
SELECT SubscriberKey, EmailAddress
FROM master_subscribers
WHERE Status = 'Active';Comentá el recibo para elecciones no-obvias
Cuando cortás un atajo, comentalo con la fecha y la razón. El próximo dev (seguido vos, seis meses después) necesita el recibo.
-- TEMPORAL — reemplazar antes del 2026-06-15
-- (recordatorio en calendario seteado el 2026-05-15)
-- Razón: sender ID hardcodeado hasta que el nuevo SAP package termine
-- la verificación.
DECLARE @senderId INT = 1234567;Ver Principios de Marketing Cloud — #11 para la disciplina de código temporal.
Patrones a preferir
INSERT INTO ... SELECT vía wrapper de Activity
Nunca escribas INSERT VALUES, UPDATE, DELETE, MERGE standalone en MC SQL — no funcionan. Siempre dale forma a la query como SELECT y dejá que la target action de la Activity maneje el comportamiento de merge. Ver INSERT INTO.
Stagéa, validá, después promové
Para cualquier query no trivial, partila en:
- SQL Activity:
SELECT ... INTO de_stg_* - Verificación: check de conteo de filas, sanidad del data, alerta si está fuera del rango esperado
- SQL Activity:
INSERT INTO production_de SELECT * FROM de_stg_*(o la target action apropiada)
Tres Activities, tres checkpoints, recuperable. Ver INSERT INTO y Principios MC — #1.
LEFT JOIN ... IS NULL para supresión / anti-join
No uses NOT IN (SELECT ...) contra orígenes grandes. El anti-join es el patrón durable. Ver JOIN y WHERE.
COALESCE sobre ISNULL
Multi-arg, portátil, tipos predecibles. ISNULL es para el caso raro donde su comportamiento específico es lo que querés. Ver Funciones de NULL.
TRY_CAST sobre CAST cuando el origen está sucio
Una fila mala si no mata la Activity sin rollback. Ver Funciones de conversión y gotchas — #1.
LTRIM(RTRIM(CAST(... AS NVARCHAR(255)))) para joins de SubscriberKey
La fuente única más común de bug silencioso. Ver JOIN, Funciones de string, gotchas — #9.
Snapshot las System Data Views antes de leerlas en producción
Nunca FROM _Sent (o _Open, etc.) directamente en una Activity scheduleada. Snapshot a de_log_* una vez, leé del snapshot. Ver FROM y gotchas — #6.
Conteos de días sobre math de meses para filtros de fecha
DATEADD(day, -90, GETDATE()) es estable. DATEADD(month, -3, GETDATE()) no. Traducí "últimos 3 meses" a "últimos 90 días" una vez en momento de diseño. Ver Funciones de fecha y gotchas — #8.
Tipos de JOIN explícitos, nunca joins implícitos por coma
-- EVITAR
FROM a, b WHERE a.k = b.k
-- PREFERIR
FROM a INNER JOIN b ON a.k = b.kVer JOIN.
Patrones a rechazar
SELECT * en cualquier Activity de producción
Los cambios de schema del origen re-forman el destino en silencio. Siempre proyectá columnas explícitas. Ver SELECT.
NOT IN (SELECT ...) contra un Data Extension
Trampa de performance que se pone peor con escala. Usá anti-join. Ver WHERE y JOIN.
ROW_NUMBER() OVER (...) como mecanismo de dedup
Edition-dependent. Usá el patrón MAX-por-grupo de dos Activities. Ver Funciones agregadas y gotchas — #4.
WHERE ... = NULL (o != NULL)
Siempre devuelve cero filas. Usá IS NULL / IS NOT NULL. Ver WHERE, Funciones de NULL, gotchas — #5.
Envolver una columna en una función adentro de WHERE
Mata el índice. Movele la función al lado del literal o stagéa el valor normalizado. Ver WHERE, Funciones de fecha, Funciones de string.
Mezclar AND con OR sin paréntesis
AND ata más fuerte que OR. Siempre parenthesizá cuando mezclás. Ver WHERE.
Activity en modo Update sin primary key en el destino
Silenciosamente se comporta como Append. Ver INSERT INTO.
WHERE 1=2 para "limpiar" un Data Extension
Folklore que no siempre funciona. Usá la API o la acción "Clear Data" de la UI explícitamente. Ver gotchas — #7.
El check de disciplina antes de mergear
Antes de que cualquier SQL Activity nueva pase de staging a una Automation scheduleada, recorré este checklist:
- [ ] El naming sigue la convención de prefijo (DE / de_stg_ / de_log_ / TS_ / etc.)
- [ ] Todas las columnas explícitas en el
SELECT(sinSELECT *) - [ ] El origen es tu propio DE, no una System Data View directamente
- [ ] La target action se eligió primero; la forma del SELECT matchea (Overwrite / Append / Update)
- [ ] Si es
Update: el DE de destino tiene primary key configurada - [ ] Todos los joins de
SubscriberKeyusanLTRIM(RTRIM(CAST(... AS NVARCHAR(255)))) - [ ] Todas las conversiones usan
TRY_CASTsi el origen no es tu propio DE limpio - [ ] Todos los checks de NULL usan
IS NULL/IS NOT NULL, nunca= NULL - [ ] Los filtros de fecha usan conteos de días, no math de meses
- [ ] Sin envolver funciones en columnas adentro de
WHERE(o es intencional y el origen es chico) - [ ] Las mezclas
AND/ORestán parenthesizadas - [ ]
NOT IN (SELECT ...)reescrito como anti-join si el origen es no-trivial - [ ] Las queries multi-fuente stagéan cada join en su propia Activity
- [ ] El runtime estimado contra volumen de producción está bajo los 10 minutos (bien debajo del timeout duro de 30 min)
- [ ] Los comentarios explican el por qué de cualquier elección no-obvia
- [ ] Cualquier código "temporal" tiene fecha de expiración y recordatorio en calendario
Cuando los doce se disparan, la Activity está lista para entregar.
Relacionado
- Principios de Marketing Cloud desde producción — las meta-reglas arriba de los específicos de SQL
- MC SQL gotchas — las formas de falla que esta Style Guide está diseñada para prevenir
- Cada página de referencia de este catálogo — Basics, SELECT, FROM, JOIN, WHERE, LIKE, CASE, INSERT INTO, funciones de String / Date / Numeric / Conversion / Aggregate / Null
Progreso del catálogo: con esta Style Guide, las 15 páginas de referencia + decision-framework de la sección SQL están entregadas. El trabajo restante del catálogo son 3 snippets how-to de debugging (Email Sends, Value Length, All Contacts).
Si encontrás una regla que falte — o una de estas reglas siendo violada en nuestro trabajo público — escribinos a hello@wearecleon.com. La agregamos, o la corregimos y lo decimos.