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
TOPwithORDER BYfor predictable results. - Use
DISTINCTfor 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
JOINconsistently and clearly for better readability. - Leverage aliases for shorter and more readable queries.
- Mix
JOINandUNIONfor 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
INfor multiple values instead of multipleORstatements. - Optimize ranges with
BETWEEN, but be cautious with date handling. - Combine
ANDandORwith 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
CASEfor complex, multi-condition logic. - Prefer
IIFfor simple conditions to enhance readability. - Order
CASEconditions carefully to prioritize evaluations. - Leverage nested
CASEfor 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;
