Skip to main content

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.

Marco de decisión·Actualizado 2026-05-07·Escrito por Lira · Editado por German Medina

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

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

  1. SQL Activity: SELECT ... INTO de_stg_*
  2. Verificación: check de conteo de filas, sanidad del data, alerta si está fuera del rango esperado
  3. 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.k

Ver 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 (sin SELECT *)
  • [ ] 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 SubscriberKey usan LTRIM(RTRIM(CAST(... AS NVARCHAR(255))))
  • [ ] Todas las conversiones usan TRY_CAST si 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 / OR está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

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.