Why SQL Matters in Salesforce Marketing Cloud
Salesforce Marketing Cloud (SFMC) is a powerful platform for managing customer data, designing personalized campaigns, and analyzing performance. While drag-and-drop filters offer a simple way to segment data, they often fall short for advanced use cases. That’s where SQL (Structured Query Language) becomes essential.
In SFMC, SQL allows you to:
- Customize Output: Define specific columns and rename them for better usability.
- Handle Complex Conditions: Apply advanced filtering logic beyond filters.
- Integrate Multiple Sources: Combine data from multiple data extensions or data views.
- Automate Processes: Use SQL in Query Activities within Automation Studio for scheduled segmentation.
A Beginner’s Guide to SQL in SFMC
Unlike standard SQL, SFMC supports only the SELECT
statement. You can pull data,
but you can’t modify it directly in the database (INSERT
, UPDATE
,
and DELETE
are unsupported). Here's a quick breakdown of the basic syntax:
SELECT column1, column2, ... FROM DataExtensionName WHERE condition;
Practical Example
Suppose you want to extract all active subscribers:
SELECT SubscriberKey, EmailAddress FROM _Subscribers WHERE Status = 'Active';
Advanced SQL Techniques in SFMC
1. JOIN: Combine Data Sources
SFMC allows you to join data from multiple tables. For example, if you want to combine subscriber data with their engagement data:
SELECT s.SubscriberKey, s.EmailAddress, o.EventDate AS OpenDate FROM _Subscribers s INNER JOIN _Open o ON s.SubscriberKey = o.SubscriberKey WHERE o.EventDate > DATEADD(day, -30, GETDATE());
2. CASE Statements: Conditional Logic
You can create new columns dynamically based on conditions. For instance, categorize subscribers based on their email domain:
SELECT SubscriberKey, EmailAddress, CASE WHEN EmailAddress LIKE '%@gmail.com' THEN 'Gmail User' WHEN EmailAddress LIKE '%@yahoo.com' THEN 'Yahoo User' ELSE 'Other' END AS EmailCategory FROM _Subscribers;
3. Aggregations and Grouping
Analyze your data with functions like COUNT
, SUM
, or AVG
.
For example, to count email sends per domain:
SELECT SUBSTRING(EmailAddress, CHARINDEX('@', EmailAddress) + 1, LEN(EmailAddress)) AS Domain, COUNT(*) AS TotalSends FROM _Sent GROUP BY SUBSTRING(EmailAddress, CHARINDEX('@', EmailAddress) + 1, LEN(EmailAddress)) ORDER BY TotalSends DESC;
Overcoming SQL Limitations in SFMC
- Optimize Input Data: Use filters or retention settings to limit the size of your source data extensions.
- Break Queries into Steps: Split complex queries into smaller, more manageable parts.
Real-Life Use Case: Re-Engagement Campaign
Target subscribers who:
- Have not opened an email in the last 90 days.
- Belong to the "Premium" customer segment.
SELECT s.SubscriberKey, s.EmailAddress FROM _Subscribers s LEFT JOIN _Open o ON s.SubscriberKey = o.SubscriberKey WHERE s.Status = 'Active' AND s.SubscriberKey IN (SELECT SubscriberKey FROM PremiumCustomers) AND (o.EventDate IS NULL OR o.EventDate < DATEADD(day, -90, GETDATE()));
SELECT Statement in SFMC SQL
The SELECT
statement opens each query in Salesforce Marketing Cloud SQL. It picks
the columns from the source data extensions and system data views and allows you to create
entirely new data points.
Basic Usage
The simplest version of this statement is SELECT *
, which targets all available
columns from the source. However, this is not recommended as it reduces readability
and performance:
SELECT * FROM DataExtensionName;
Instead, explicitly target columns by their names:
SELECT ContactKey, EmailAddress FROM DataExtensionName;
For column names with spaces or special characters, use square brackets:
SELECT ContactKey, EmailAddress, [External Contact Id] FROM DataExtensionName;
Aliasing with AS
Use the AS
keyword to rename columns or add custom values:
SELECT ContactKey, EmailAddress, [External Contact Id] AS ExternalContactId, 'Lead' AS RecordType, GETUTCDATE() AS ImportDate FROM DataExtensionName;
Concatenating Values
Create new columns by combining existing data, like concatenating names:
SELECT SubscriberKey, EmailAddress, FirstName + ' ' + LastName AS FullName FROM DataExtensionName;
Using the TOP Clause
Limit the number of rows returned with the TOP
clause:
SELECT TOP 10 SubscriberKey, PreferredChannel FROM DataExtensionName;
For a percentage of rows, use TOP PERCENT
:
SELECT TOP 10 PERCENT SubscriberKey, QuizScore FROM DataExtensionName ORDER BY QuizScore DESC;
WITH TIES
Include rows that match the last value of a sorted column:
SELECT TOP 3 WITH TIES SubscriberKey, QuizScore FROM DataExtensionName ORDER BY QuizScore DESC;
DISTINCT for Deduplication
Use DISTINCT
to retrieve unique values:
SELECT DISTINCT Domain FROM DataExtensionName ORDER BY Domain;
Best Practices
- Always target columns explicitly for better readability.
- Use aliases to improve output consistency.
- Combine
TOP
withORDER BY
for predictable results. - Use
DISTINCT
for deduplication and clean data extraction.
Basic FROM in SFMC SQL
The SELECT
statement alone won't do much. Once you have selected the data you
want to work with, you also need to pick the table (Data Extension or System Data View)
FROM which the query will retrieve the required columns.
SELECT ContactKey, EmailAddress FROM MasterSubscriberDE;
If the name of your Data Extension contains spaces or hyphens, enclose it in square brackets:
SELECT ContactKey, EmailAddress FROM [Master-Subscriber DE];
System Data Views
Salesforce Marketing Cloud hides the System Data Views in the UI, but you can query them
directly for tracking and technical data, like Email Sends, Opens, Bounces, etc. Use an
underscore (_
) prefix when querying them:
SELECT JobID, EmailID, EmailName, EmailSubject, EmailSendDefinition, DeliveredTime FROM _Job;
Enterprise Data Extensions
Shared and Synchronized Data Extensions are located at the Parent Business Unit level. To query them from the Parent BU, use the standard naming:
SELECT ContactKey, EmailAddress FROM SharedDataExtension;
From a child Business Unit, use the Ent.
prefix:
SELECT ContactKey, EmailAddress FROM Ent.SharedDataExtension;
Enterprise System Data Views
Using the Ent.
prefix with System Data Views allows you to query all data at
the Parent BU level:
SELECT SubscriberKey, EmailAddress, DateJoined, DateUnsubscribed FROM Ent._Subscribers;
Legacy Querying (Deprecated)
In the past, you could query Data Extensions on a child BU from the Parent BU using the child BU's MID as a prefix. This functionality has been deprecated since 2020:
SELECT ContactKey, EmailAddress FROM 5123456.DataExtensionOnChildBU;
The workaround is to use Shared Data Extensions instead.
Best Practices
- Avoid querying Data Extensions with a cumulative field width greater than 4000 characters to maintain performance.
- Use the
Ent.
prefix carefully to access data across Business Units. - Leverage Shared Data Extensions to avoid unexpected errors in automation.
SFMC SQL JOIN
Data Extensions. System Data Views. With the JOIN
statement, use one query to
rule them all. The real magic of SQL shines when you combine information from multiple
data sources.
Example Query
Check who opened the email and when by querying the _Open
System Data View:
SELECT wel.SubscriberKey, wel.EmailAddress, o.EventDate AS OpenDate FROM WelcomeCampaignSegment AS wel JOIN _Open AS o ON o.SubscriberKey = wel.SubscriberKey;
JOIN Types
Inner Join
The INNER JOIN
returns records present in both tables:
SELECT wel.SubscriberKey, wel.EmailAddress, o.EventDate AS OpenDate FROM WelcomeCampaignSegment AS wel INNER JOIN _Open AS o ON o.SubscriberKey = wel.SubscriberKey;
Left and Right Joins
LEFT JOIN
includes all records from the first table and matches from the
second table:
SELECT wel.SubscriberKey, wel.EmailAddress, o.EventDate AS OpenDate FROM WelcomeCampaignSegment AS wel LEFT JOIN _Open AS o ON o.SubscriberKey = wel.SubscriberKey;
Use WHERE
with IS NULL
for exclusions:
SELECT wel.SubscriberKey, wel.EmailAddress FROM WelcomeCampaignSegment AS wel LEFT JOIN _Open AS o ON o.SubscriberKey = wel.SubscriberKey WHERE o.SubscriberKey IS NULL;
Full Join
FULL JOIN
retrieves all records from both tables with unmatched rows having
NULL
values:
SELECT wel.SubscriberKey, wel.EmailAddress, o.EventDate AS OpenDate FROM WelcomeCampaignSegment AS wel FULL JOIN _Open AS o ON o.SubscriberKey = wel.SubscriberKey;
Self-Join
Join a table to itself to create hierarchical relationships:
SELECT c.CategoryName AS Category, pc.CategoryName AS ParentCategory FROM WristwatchesDE AS c INNER JOIN WristwatchesDE AS pc ON pc.CategoryID = c.ParentCategoryID;
Union
Concatenate rows from multiple queries:
SELECT jan.SubscriberKey, jan.EmailAddress FROM JanuaryEventParticipants AS jan UNION SELECT feb.SubscriberKey, feb.EmailAddress FROM FebruaryEventParticipants AS feb;
Intersect
Retrieve only rows existing in both queries:
SELECT bf.SubscriberKey FROM BlackFridayPromoSegment AS bf INTERSECT SELECT p.SubscriberKey FROM PurchasesDataPoint AS p;
Except
Retrieve rows existing only in the first query:
SELECT bf.SubscriberKey FROM BlackFridayPromoSegment AS bf EXCEPT SELECT p.SubscriberKey FROM PurchasesDataPoint AS p;
Best Practices
- Use
JOIN
consistently and clearly for better readability. - Leverage aliases for shorter and more readable queries.
- Mix
JOIN
andUNION
for complex data requirements. - Use Venn diagrams to visualize and debug multi-join queries.
SFMC SQL WHERE
Use WHERE
to make your query lean and to the point. With just SELECT
and FROM
, you can start building queries, but WHERE
grants you the
power to filter outcomes effectively.
Basic Example
Filter results based on conditions:
SELECT SubscriberKey, EmailAddress FROM _Subscribers WHERE Domain = 'mateuszdabrowski.pl';
Basic Operators
Common operators include:
- = : Equal to
- != or <> : Not equal to
- >, <, >=, <= : Comparison operators
-- Example: All subscribers that do not have the unsubscribed status SELECT SubscriberKey, EmailAddress FROM _Subscribers WHERE Status != 'unsubscribed';
LIKE Operator
Use LIKE
with wildcards to filter records based on partial matches:
-- Example: All subscribers with Phone subscription SELECT SubscriberKey, EmailAddress FROM MasterSubscriberDE WHERE ActiveSubscription LIKE '%Phone%';
Logical Operators
Combine conditions using AND
and OR
:
-- Example: Subscribers with Phone subscription AND are in their 12th month SELECT SubscriberKey, EmailAddress FROM MasterSubscriberDE WHERE ActiveSubscription LIKE '%Phone%' AND CONVERT(DATE, SubscriptionStart) >= DATEADD(MONTH, -12, CONVERT(DATE, GETUTCDATE())) AND CONVERT(DATE, SubscriptionStart) < DATEADD(MONTH, -11, CONVERT(DATE, GETUTCDATE()));
BETWEEN and IN Shorthands
Use BETWEEN
for ranges and IN
for multiple values:
-- Example: Filter by date range SELECT SubscriberKey, EmailAddress FROM MasterSubscriberDE WHERE SubscriptionStart BETWEEN DATEADD(MONTH, -12, CONVERT(DATE, GETUTCDATE())) AND DATEADD(MONTH, -11, CONVERT(DATE, GETUTCDATE())); -- Example: Filter by multiple statuses SELECT SubscriberKey, EmailAddress FROM _Subscribers WHERE Status IN ('held', 'unsubscribed', 'bounced');
NOT Operator
Exclude records using NOT
:
-- Example: Exclude certain statuses SELECT SubscriberKey, EmailAddress FROM _Subscribers WHERE Status NOT IN ('held', 'unsubscribed', 'bounced');
CASE Operator in WHERE
Use CASE
for conditional filtering:
-- Example: Filter based on Preferred Email SELECT SubscriberKey, EmailAddress FROM MasterSubscriberDE WHERE EmailAddress != CASE WHEN PreferredEmail = 'Personal Email' THEN PersonalEmail WHEN PreferredEmail = 'Work Email' THEN WorkEmail END;
Best Practices
- Use
IN
for multiple values instead of multipleOR
statements. - Optimize ranges with
BETWEEN
, but be cautious with date handling. - Combine
AND
andOR
with parentheses for clarity.
SFMC SQL CASE
The CASE
statement in SQL allows you to evaluate a list of conditions and return
one of multiple possible results. It is perfect for translating data, standardizing formats,
and creating dynamic outputs in Salesforce Marketing Cloud (SFMC).
Basic Example
Translate membership levels:
SELECT ContactKey, EmailAddress, CASE MemberType__c WHEN 1 THEN 'Bronze' WHEN 2 THEN 'Silver' WHEN 3 THEN 'Gold' WHEN 4 THEN 'Platinum' ELSE 'Unregistered' END AS MemberType FROM MembersDE;
In the above example:
- WHEN: Specifies the condition to check.
- THEN: Returns the value if the condition is true.
- ELSE: (Optional) Returns a default value if no conditions are met.
Search CASE
Build dynamic conditions with Search CASE:
SELECT ContactKey, EmailAddress, CASE WHEN IsAppInstalled = 1 THEN 'Push' WHEN EmailOptIn = 1 AND EmailAddress IS NOT NULL THEN 'Email' WHEN PhoneOptIn = 1 AND Mobile IS NOT NULL THEN 'Phone' END AS PreferredChannel FROM ContactsDE;
Nested CASE
You can nest CASE
statements for advanced logic:
SELECT ContactKey, CASE WHEN MemberType = 'Platinum' THEN 'Key Account Manager' WHEN MemberType IN ('Gold', 'Silver') THEN CASE WHEN IsAppInstalled = 1 THEN 'Push' WHEN EmailOptIn = 1 AND EmailAddress IS NOT NULL THEN 'Email' END WHEN MemberType = 'Bronze' THEN CASE WHEN IsAppInstalled = 1 THEN 'Push' WHEN EmailOptIn = 1 AND EmailAddress IS NOT NULL THEN 'Email' END END AS PreferredChannel FROM MembersDE;
LIKE in CASE
Use LIKE
with CASE
for text-based conditions:
SELECT ContactKey, CASE WHEN EmailAddress LIKE '%@company.com' THEN 'Internal' ELSE 'External' END AS ContactType FROM _Subscribers;
IIF Shorthand
For simpler conditions, use IIF
:
SELECT SubscriberKey, IIF(Status = 'active', 'TRUE', 'FALSE') AS Sendable FROM _Subscribers;
Dynamic Values in CASE
Use columns dynamically within CASE
or IIF
:
SELECT SubscriberKey, IIF(PreferredChannel = 'Email', EmailAddress, MobileNumber) AS PointOfContact FROM MasterDE;
Best Practices
- Use
CASE
for complex, multi-condition logic. - Prefer
IIF
for simple conditions to enhance readability. - Order
CASE
conditions carefully to prioritize evaluations. - Leverage nested
CASE
for tiered or hierarchical logic.
SFMC SQL LIKE
The LIKE
operator allows you to leverage wildcards and groups to filter records
based on value fragments. It is widely used in both SELECT
and WHERE
statements.
Basic Usage
Match email domains using wildcards:
SELECT SubscriberKey, EmailAddress FROM _Subscribers WHERE EmailAddress LIKE '%@mateuszdabrowski.pl';
This query matches all email addresses ending with @mateuszdabrowski.pl
.
Wildcards
- %: Matches zero, one, or more characters.
- _: Matches exactly one character.
Example with _
:
SELECT SubscriberKey, EmailAddress, RecommendedProducts FROM EcommerceUpsellDE WHERE RecommendedProducts LIKE '_' OR RecommendedProducts LIKE '_,%' OR RecommendedProducts LIKE '%,_,%' OR RecommendedProducts LIKE '%,_';
Group Operator
The []
group operator allows you to match any character in the group. Example:
SELECT SubscriberKey, EmailAddress, RecommendedProducts FROM EcommerceUpsellDE WHERE RecommendedProducts LIKE '[1234]' OR RecommendedProducts LIKE '[1234],%' OR RecommendedProducts LIKE '%,[1234],%' OR RecommendedProducts LIKE '%,[1234]';
Range Operator
Simplify patterns using -
for ranges:
SELECT SubscriberKey, EmailAddress, RecommendedProducts FROM EcommerceUpsellDE WHERE RecommendedProducts LIKE '[1-4]' OR RecommendedProducts LIKE '[1-4],%' OR RecommendedProducts LIKE '%,[1-4],%' OR RecommendedProducts LIKE '%,[1-4]';
Exclusion Operator
Exclude specific characters using ^
:
SELECT SubscriberKey FROM _Subscribers WHERE SubscriberKey LIKE '%[^a-zA-Z0-9]%' AND LEN(SubscriberKey) != 18;
NOT LIKE
Filter records not matching a pattern:
SELECT ContactKey, CASE WHEN EmailAddress NOT LIKE '%@mateuszdabrowski.pl' THEN 'External' ELSE 'Internal' END AS ContactType FROM _Subscribers;
SFMC SQL Date Functions
Take control over dates with built-in SQL functions. These functions are essential for manipulating and formatting date values in Salesforce Marketing Cloud (SFMC).
GETDATE & GETUTCDATE
Get the current timestamp:
SELECT GETDATE() AS CurrentDate;
For UTC timestamps:
SELECT GETUTCDATE() AS CurrentUTCDate;
DATEPART
Extract a specific part of a date:
SELECT SubscriberKey, EmailAddress FROM Ent._Subscribers WHERE DATEPART(YEAR, DateJoined) = 2020;
Shortcut examples:
SELECT SubscriberKey, EmailAddress FROM Ent._Subscribers WHERE YEAR(DateJoined) = 2020;
DATENAME
Extract the name of a date part:
SELECT SubscriberKey, DATENAME(WEEKDAY, DateJoined) AS DayJoined FROM Ent._Subscribers;
DATEADD
Add or subtract intervals from a date:
SELECT GETUTCDATE() AS TrialStartDate, DATEADD(DAY, 7, GETUTCDATE()) AS TrialEndDate;
Example with ranges:
SELECT SubscriberKey, EmailAddress FROM EventRegistration WHERE EventDate BETWEEN GETDATE() AND DATEADD(MONTH, 1, GETDATE());
DATEDIFF
Calculate the difference between two dates:
SELECT SubscriberKey, DATEDIFF(DAY, DateJoined, GETDATE()) AS SubscriptionTime FROM Ent._Subscribers;
AT TIME ZONE
Add or change timezone information:
SELECT CONVERT(DATETIME2(0), '2020-01-01T18:00:00') AT TIME ZONE 'Central Standard Time' AS CST, CONVERT(DATETIME2(0), '2020-07-01T18:00:00') AT TIME ZONE 'Central Standard Time' AS CDT;
FORMAT
Format dates for personalization or export:
SELECT FORMAT(CONVERT(DATE, '10/30/2020'), 'd', 'en-GB') AS DateOrderedProperly, FORMAT(GETUTCDATE(), 'dd/MM/yyyy') AS TodayCustomFormat, FORMAT(DateJoined, 'D', 'de-DE') AS DateExpandedIndian;
SFMC SQL Numeric Functions
Take control over numbers with built-in SQL functions. These functions are essential for manipulating and formatting numeric values in Salesforce Marketing Cloud (SFMC).
MIN and MAX
Find the minimum and maximum values:
SELECT MAX(s.BounceCount) AS HighestBounceCount FROM _Subscribers AS s;
Use case for finding competition winners:
SELECT comp.SubscriberKey, comp.CompetitionPoints FROM CompetitionDataExtension AS comp WHERE comp.CompetitionPoints = ( SELECT MAX(comp2.CompetitionPoints) FROM CompetitionDataExtension AS comp2 );
Debugging data extensions with MAX:
SELECT MAX(LEN(c.Id)) AS SubscriberKey, MAX(LEN(c.Email)) AS EmailAddress FROM Contact_Salesforce AS c;
AVG
Calculate the average value:
SELECT AVG(s.BounceCount) AS AverageBounceCount FROM _Subscribers AS s;
Find average subscriber age (example idea):
SELECT DATEDIFF(MONTH, CONVERT(DATETIME, AVG(CONVERT(FLOAT, DateJoined))), GETDATE()) AS SubscribersAverageAgeInMonths FROM _Subscribers;
SUM
Calculate the sum of values:
SELECT SUM(BounceCount) AS AllBouncesCount FROM _Subscribers;
FORMAT with Numbers
Format numeric values:
SELECT FORMAT(1234.56, 'N') AS WithoutCustomPrecision, FORMAT(1234.56, 'N1') AS RoundedWithCustomPrecision;
Custom formatting example:
SELECT FORMAT(CONVERT(BIGINT, ea.MobileNumber), '(###) ### ### ###') AS FormattedPhone FROM _Subscribers AS s INNER JOIN EnterpriseAttribute AS ea ON ea._SubscriberID = s.SubscriberID;
Another custom formatting example:
SELECT FORMAT(VIPDiscount, 'P') AS StandardFormattedVIPDiscount, FORMAT(VIPDiscount, '-00.##%') AS CustomFormattedVIPDiscount FROM SomeDataExtension;
SFMC SQL Conversion Functions
Break from the limits of source data types. Use SQL conversion functions to transform your values in Salesforce Marketing Cloud.
CAST & CONVERT
CAST
Basic syntax for type conversion:
SELECT SubscriberKey FROM Ent._Subscribers WHERE DateJoined > CAST('2020-10-30' AS DATE);
CONVERT
Similar to CAST but with additional features:
SELECT SubscriberKey FROM Ent._Subscribers WHERE DateJoined > CONVERT(DATE, '2020-10-30');
Adding style codes for non-standard formats:
SELECT SubscriberKey FROM _Subscribers WHERE DateJoined > CONVERT(DATE, '30/10/2019', 103);
Formatting Dates with CONVERT
Examples of formatted output:
SELECT CONVERT(NVARCHAR, GETDATE(), 101) AS DateFormat1, /* Output: 10/30/2020 */ CONVERT(NVARCHAR, GETDATE(), 107) AS DateFormat2 /* Output: Oct 30, 2020 */
Data Types
Commonly used data types in Salesforce Marketing Cloud:
- NVARCHAR: Best option for string use cases
- DECIMAL: Ideal for precise decimal numbers
- INT: Use for integers
- DATETIME2: Modern date and time
- DATE: For date-only values
Example of specifying precision and scale:
SELECT CONVERT(DECIMAL(5, 2), '123.99') AS FullConversion; /* Output: 123.99 */
CONVERT Date Style Codes
Popular date style codes:
- 101: MM/dd/yyyy
- 103: dd/MM/yyyy
- 107: MMM dd, yyyy
- 120: yyyy-MM-dd HH:mm:ss
Using style codes in queries:
SELECT CONVERT(NVARCHAR, GETDATE(), 101) AS DateAsString, CONVERT(DATETIME2, '2020-12-31 19:00:00', 120) AS StringAsDate;
Handling Non-Supported Formats
If a date format isn't directly supported:
SELECT CONVERT(DATETIME, LEFT('27/12/2021 07:31:01', 10), 103) + CONVERT(DATETIME, RIGHT('27/12/2021 07:31:01', 8), 108) AS ConvertedDate;