INSERT INTO — Referencia de SQL en Marketing Cloud
El único path de escritura en MC SQL — cómo funciona el wrapper INSERT INTO ... SELECT, qué hace realmente cada target action (Overwrite / Append / Update), y las reglas que evitan pérdida de datos silenciosa.
INSERT INTO ... SELECT es la única forma de escribir datos en MC SQL. No hay INSERT INTO target VALUES (...) standalone, no hay UPDATE target SET ..., no hay DELETE FROM target — cada escritura es un SELECT que produce filas + un Data Extension de destino que las recibe, con el comportamiento de merge controlado por la target action de la SQL Activity. Si te equivocás de target action, el mismo SELECT o reconstruye limpio tu DE, o duplica cada fila, o silenciosamente mergea sobre datos que querías conservar.
Sintaxis oficial
Vos en realidad no escribís la cláusula INSERT INTO — Automation Studio envuelve tu SELECT basado en el Data Extension de destino que configuraste en la SQL Query Activity y la target action que elegiste. Lo que vos escribís:
-- Este es todo el cuerpo de la query en la Activity:
SELECT
SubscriberKey,
EmailAddress,
LoyaltyTier,
LastPurchase
FROM master_subscribers
WHERE Status = 'Active';
-- Lo que la Activity ejecuta (conceptualmente):
-- <una de:>
-- 1. TRUNCATE destination_de; INSERT INTO destination_de (...) <tu SELECT>; -- Overwrite
-- 2. INSERT INTO destination_de (...) <tu SELECT>; -- Append
-- 3. MERGE destination_de USING (<tu SELECT>) ON <PK match> ...; -- UpdateLas tres target actions (configuradas en la Activity, no en SQL):
| Action | Qué hace | Setup requerido |
|---|---|---|
| Overwrite | Vacía el DE de destino, después inserta cada fila del SELECT | Ninguno — funciona en cualquier DE |
| Append | Inserta cada fila del SELECT, encima de lo que ya estaba | Ninguno — pero sin dedup; los duplicados se acumulan |
| Update | Matchea contra la primary key del DE de destino. Filas existentes: columnas no-key actualizadas. Filas nuevas: insertadas. | El DE de destino debe tener primary key configurada |
El matching de columnas es por nombre (case-insensitive). El SELECT tiene que producir columnas cuyos nombres matcheen las columnas del DE de destino. Las columnas extra del SELECT se ignoran; las columnas faltantes obtienen el valor default del destino (o NULL si no hay default).
Referencia:
- Salesforce Help — Crear una SQL Query Activity ↗
- Salesforce Help — Overview de SQL Query Activity ↗
- Salesforce Help — Overview de Data Extension ↗
Lo que sobrevive en producción
Elegí la target action antes de escribir el SELECT
El mismo SELECT produce resultados radicalmente distintos según la action. Decidí primero.
-- Este SELECT parece inocente
SELECT TOP 100 SubscriberKey, EmailAddress, LoyaltyTier
FROM master_subscribers
WHERE Status = 'Active'
ORDER BY LastPurchase DESC;
-- Bajo Overwrite: el DE termina con exactamente 100 filas. Sensato.
-- Bajo Append: el DE CRECE en 100 filas cada corrida.
-- Después de una semana de corridas diarias, 700 filas, mayormente duplicados.
-- Bajo Update: si el destino tiene SubscriberKey como PK,
-- 100 filas obtienen sus campos no-key actualizados. Las
-- otras ~9.9M filas en el destino quedan tal cual.Mismo SQL. Tres comportamientos completamente distintos en producción. Siempre respondé "qué le pasa a las filas que NO están en este SELECT?" antes de presionar guardar en la Activity.
El modo Update requiere primary key — sin ella se comporta silenciosamente como Append
Si configurás la Activity en Update pero el DE de destino no tiene primary key configurada, el merge no puede pasar — y en lugar de fallar, fallback a insertar cada fila. El comportamiento depende del tenant; algunos lo tratan como error, otros como Append. De cualquier forma, tu "update incremental" está duplicando filas.
La defensa: antes de guardar una Activity en modo Update, abrí las propiedades del DE de destino y confirmá que hay una primary key configurada en la(s) columna(s) contra las que querés mergear. Generalmente SubscriberKey, a veces una clave compuesta.
Overwrite es "limpia + inserta" — y la limpieza es incondicional
Overwrite no compara nada. Vacía el DE de destino, después inserta lo que el SELECT devuelva. Si tu SELECT devuelve cero filas (por un JOIN malo, un WHERE que filtró demasiado, una System Data View que se vació en silencio), el DE de destino termina vacío — y los datos que estaban antes desaparecieron.
-- EN RIESGO — si la rotación de _Sent vacía el join, el DE de destino
-- termina con cero filas bajo modo Overwrite
SELECT s.SubscriberKey, s.EmailAddress
FROM master_subscribers s
INNER JOIN _Sent sent
ON s.SubscriberKey = sent.SubscriberKey
WHERE sent.EventDate >= DATEADD(day, -30, GETDATE());
-- DEFENSIVO — paso de verificación en una Activity separada que
-- protege contra Overwrites de cero filas
INSERT INTO de_stg_target
SELECT s.SubscriberKey, s.EmailAddress
FROM master_subscribers s
INNER JOIN de_log_sent_30d sent -- snapshot, no _Sent directo
ON s.SubscriberKey = sent.SubscriberKey;
-- Activity 2 solo promueve si staging tiene filas arriba de un threshold:
INSERT INTO destination_de
SELECT * FROM de_stg_target
WHERE EXISTS (
SELECT 1 FROM de_stg_target
GROUP BY 1
HAVING COUNT(*) > 1000 -- el piso que sea
);El patrón: nunca Overwrite directo desde una query que toca una System Data View. Ver FROM y gotchas — #6 para la regla de snapshot SDV.
Matcheá nombres de columna explícitamente en el SELECT
SELECT * contra un Data Extension al que le agregan columnas después va a empezar a producir columnas extra que el DE de destino no tiene. El comportamiento de Salesforce con columnas extra es "ignorar en silencio" — así que tu INSERT INTO exitosamente, y una columna que se suponía iba a aterrizar en el destino falta sin error.
Siempre proyectá explícitamente:
-- EN RIESGO — si el DE de origen agrega una columna mañana, esto sigue
-- corriendo pero el destino no va a ver el valor nuevo (columna faltante
-- en destino = ignorada silenciosamente en el SELECT)
SELECT *
FROM master_subscribers;
-- DURABLE — lista explícita de columnas, rompe en voz alta si una columna
-- desaparece del origen en lugar de re-formar el destino en silencio
SELECT
SubscriberKey,
EmailAddress,
LoyaltyTier,
LastPurchase
FROM master_subscribers;Ver SELECT para el racional completo de evitar SELECT *.
La truncación de longitud es silenciosa — matcheá los largos del DE de destino
Si tu SELECT proyecta un EmailAddress de 75 chars de largo pero el DE de destino tiene EmailAddress VARCHAR(50), MC trunca a 50 chars al insertar. Sin warning, sin error. El destino termina con 'german.medina@reallylongdomainn' en lugar de la dirección completa — y un WHERE EmailAddress = 'german.medina@reallylongdomain.example.com' aguas abajo devuelve cero matches.
Defensa: cuando el DE de destino se crea, dimensioná cada columna string al MAX(LEN()) del origen más un margen de seguridad. Ver gotchas — #5.
Decisión rápida
Usá Overwrite cuando:
- Estás reconstruyendo el DE de destino desde cero en cada corrida.
- El
SELECTtiene garantizado devolver un conteo de filas sensato (sin SDVs, sin joins frágiles). - Datos rancios en el destino son peor que un momento de datos vacíos/parciales durante la rescritura.
Usá Append cuando:
- El destino es un log de eventos (sends, opens, errores) donde cada fila es un hecho nuevo, no un update de uno existente.
- Tenés dedup aguas abajo si los duplicados no son deseados.
- Combinado con una fila
de_log_runsque tagea qué corrida escribió qué batch.
Usá Update cuando:
- El DE de destino tiene primary key estable configurada.
- Estás mergeando cambios incrementales en un master DE de crecimiento lento.
- El
SELECTdevuelve el nuevo estado de cada fila, no el diff.
Stagéa a de_stg_* primero cuando:
- La query toca una System Data View (siempre snapshot primero).
- La query es lo bastante compleja como para producir cero filas en edge cases.
- Querés verificar conteo de filas antes de commitear al DE de producción.
- La query pasa los 10 minutos contra un volumen representativo de producción — partila para mantenerte bajo el timeout de 30 min.
Relacionado
- Basics — subset de T-SQL soportado y mecánica de target action
- SELECT — la proyección que se convierte en el
INSERT INTO ... SELECT - FROM — fuentes para el
SELECT, incluyendo la regla de snapshot SDV - JOIN — joineando fuentes antes del insert
- WHERE — filtrando antes del insert
- MC SQL gotchas — ver #1 (sin transacciones), #2 (sin UPDATE/DELETE), #3 (timeout 30 min), #5 (truncación de longitud), #7 (folklore de limpiar DE)
Próximas páginas de referencia: 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.