Adobe Data Distiller Guide
  • Adobe Data Distiller Guide
  • What is Data Distiller?
  • UNIT 1: GETTING STARTED
    • PREP 100: Why was Data Distiller Built?
    • PREP 200: Data Distiller Use Case & Capability Matrix Guide
    • PREP 300: Adobe Experience Platform & Data Distiller Primers
    • PREP 301: Leveraging Data Loops for Real-Time Personalization
    • PREP 302: Key Topics Overview: Architecture, MDM, Personas
    • PREP 303: What is Data Distiller Business Intelligence?
    • PREP 304: The Human Element in Customer Experience Management
    • PREP 305: Driving Transformation in Customer Experience: Leadership Lessons Inspired by Lee Iacocca
    • PREP 400: DBVisualizer SQL Editor Setup for Data Distiller
  • PREP 500: Ingesting CSV Data into Adobe Experience Platform
  • PREP 501: Ingesting JSON Test Data into Adobe Experience Platform
  • PREP 600: Rules vs. AI with Data Distiller: When to Apply, When to Rely, Let ROI Decide
  • Prep 601: Breaking Down B2B Data Silos: Transform Marketing, Sales & Customer Success into a Revenue
  • Unit 2: DATA DISTILLER DATA EXPLORATION
    • EXPLORE 100: Data Lake Overview
    • EXPLORE 101: Exploring Ingested Batches in a Dataset with Data Distiller
    • EXPLORE 200: Exploring Behavioral Data with Data Distiller - A Case Study with Adobe Analytics Data
    • EXPLORE 201: Exploring Web Analytics Data with Data Distiller
    • EXPLORE 202: Exploring Product Analytics with Data Distiller
    • EXPLORE 300: Exploring Adobe Journey Optimizer System Datasets with Data Distiller
    • EXPLORE 400: Exploring Offer Decisioning Datasets with Data Distiller
    • EXPLORE 500: Incremental Data Extraction with Data Distiller Cursors
  • UNIT 3: DATA DISTILLER ETL (EXTRACT, TRANSFORM, LOAD)
    • ETL 200: Chaining of Data Distiller Jobs
    • ETL 300: Incremental Processing Using Checkpoint Tables in Data Distiller
    • [DRAFT]ETL 400: Attribute-Level Change Detection in Profile Snapshot Data
  • UNIT 4: DATA DISTILLER DATA ENRICHMENT
    • ENRICH 100: Real-Time Customer Profile Overview
    • ENRICH 101: Behavior-Based Personalization with Data Distiller: A Movie Genre Case Study
    • ENRICH 200: Decile-Based Audiences with Data Distiller
    • ENRICH 300: Recency, Frequency, Monetary (RFM) Modeling for Personalization with Data Distiller
    • ENRICH 400: Net Promoter Scores (NPS) for Enhanced Customer Satisfaction with Data Distiller
  • Unit 5: DATA DISTILLER IDENTITY RESOLUTION
    • IDR 100: Identity Graph Overview
    • IDR 200: Extracting Identity Graph from Profile Attribute Snapshot Data with Data Distiller
    • IDR 300: Understanding and Mitigating Profile Collapse in Identity Resolution with Data Distiller
    • IDR 301: Using Levenshtein Distance for Fuzzy Matching in Identity Resolution with Data Distiller
    • IDR 302: Algorithmic Approaches to B2B Contacts - Unifying and Standardizing Across Sales Orgs
  • Unit 6: DATA DISTILLER AUDIENCES
    • DDA 100: Audiences Overview
    • DDA 200: Build Data Distiller Audiences on Data Lake Using SQL
    • DDA 300: Audience Overlaps with Data Distiller
  • Unit 7: DATA DISTILLER BUSINESS INTELLIGENCE
    • BI 100: Data Distiller Business Intelligence: A Complete Feature Overview
    • BI 200: Create Your First Data Model in the Data Distiller Warehouse for Dashboarding
    • BI 300: Dashboard Authoring with Data Distiller Query Pro Mode
    • BI 400: Subscription Analytics for Growth-Focused Products using Data Distiller
    • BI 500: Optimizing Omnichannel Marketing Spend Using Marginal Return Analysis
  • Unit 8: DATA DISTILLER STATISTICS & MACHINE LEARNING
    • STATSML 100: Python & JupyterLab Setup for Data Distiller
    • STATSML 101: Learn Basic Python Online
    • STATSML 200: Unlock Dataset Metadata Insights via Adobe Experience Platform APIs and Python
    • STATSML 201: Securing Data Distiller Access with Robust IP Whitelisting
    • STATSML 300: AI & Machine Learning: Basic Concepts for Data Distiller Users
    • STATSML 301: A Concept Course on Language Models
    • STATSML 302: A Concept Course on Feature Engineering Techniques for Machine Learning
    • STATSML 400: Data Distiller Basic Statistics Functions
    • STATSML 500: Generative SQL with Microsoft GitHub Copilot, Visual Studio Code and Data Distiller
    • STATSML 600: Data Distiller Advanced Statistics & Machine Learning Models
    • STATSML 601: Building a Period-to-Period Customer Retention Model Using Logistics Regression
    • STATSML 602: Techniques for Bot Detection in Data Distiller
    • STATSML 603: Predicting Customer Conversion Scores Using Random Forest in Data Distiller
    • STATSML 604: Car Loan Propensity Prediction using Logistic Regression
    • STATSML 700: Sentiment-Aware Product Review Search with Retrieval Augmented Generation (RAG)
    • STATSML 800: Turbocharging Insights with Data Distiller: A Hypercube Approach to Big Data Analytics
  • UNIT 9: DATA DISTILLER ACTIVATION & DATA EXPORT
    • ACT 100: Dataset Activation with Data Distiller
    • ACT 200: Dataset Activation: Anonymization, Masking & Differential Privacy Techniques
    • ACT 300: Functions and Techniques for Handling Sensitive Data with Data Distiller
    • ACT 400: AES Data Encryption & Decryption with Data Distiller
  • UNIT 9: DATA DISTILLER FUNCTIONS & EXTENSIONS
    • FUNC 300: Privacy Functions in Data Distiller
    • FUNC 400: Statistics Functions in Data Distiller
    • FUNC 500: Lambda Functions in Data Distiller: Exploring Similarity Joins
    • FUNC 600: Advanced Statistics & Machine Learning Functions
  • About the Authors
Powered by GitBook
On this page
  • Getting Started
  • Find Merge Policies in the Snapshot
  • Retrieve Segment Information
  • Count Profiles by Merge Policy
  • Use EXPLODE to Separate Identities in Separate Rows
  • Extract Identities Without Breaking Segment Membership Association
  • Expand Segment Membership
  • Create Segment Membership, Email, and CRMID Triples
  • Resolve Segment ID to Segment Name
  • Write Query Result to Data Lake
  • Audience Size Calculation
  • Generate Segment Name to Identities Table
  • Generate Emails Associated with a Segment Name
  • Generate Segments Associated with an Email Address
  • 3 Segment Overlap
  • 4 Segment Overlap
  1. Unit 6: DATA DISTILLER AUDIENCES

DDA 300: Audience Overlaps with Data Distiller

Learn how to leverage snapshot of profile attributes, identities and segment memberships to build exotic queries such as 3 or 4 segment overlaps

Last updated 9 months ago

Getting Started

Every day, a snapshot of the Profile attributes for every merge policy is exported to the data lake. These system datasets are hidden by default but are accessible by toggling these datasets in the data catalog.

These datasets contain information about the profile attributes, the identity map, and the segment membership as reflected at the time of creating the snapshot. The examples below show how you can explore this dataset to understand identity composition and even create exotic segment overlaps.

To first access, the Profile Snapshot datasets, navigate to Datasets and click on the filter to show system datasets. Turning this filter on will show system datasets along with others. You need to search for "profile attribute" to filter down the list. Profile attribute snapshot datasets are exported for every merge policy in the system. In the example below, there are 3 merge policies and hence there are 3 datasets.

These datasets will typically look like the following unless their name has been changed: profile_attribute_cd03f195_66b5_4a62_a6f9_c606e951c903

Even if the name has been changed, you should be able to query the dataset:

SELECT * FROM profile_attribute_cd03f195_66b5_4a62_a6f9_c606e951c903;

The columns you will see in the result will look like the following. If you see identityMap and segmentMembership fields, then you are on the right track.

There are some other interesting datasets that are generated on a daily basis by the system. Explore some of these on your own.

select * from br_segment_destination;

This will give you the list of destinations that have been mapped to a segment. Note that each row contains one such mapping and that a single destination can have multiple segments and vice versa. Also, note that segmentnamespace refers to the source of the segment i.e. whether it was created in AEP or elsewhere.

You may also see a dataset that contains the segment information. Search for a dataset that has segment definition in the schema type and you should be able to locate this dataset

select * from profile_segment_definition_6591ba8f_1422_499d_822a_543b2f7613a3

The results of the query look like the following:

And finally the destinations ID to account name mapping is available in this system dataset:

select * from dim_destination;

Let us explore another dataset that gives us information about what identities are available for mapping to a destination.

select * from br_namespace_destination
order by destinationID

The result is:

The result shows that for a given target identity (namespace) available in the destination, there are multiple source identity options, many of which are not used (isMapped = false). These identities were sourced by the profiles that were part of the audience which in turn was powered by the identity graph.

Tip: Note that if an identity is missing at the source which is mapped to a destination field, that identity is dropped from being sent. There are other identities of the same profile that may have non-zero values and they will get sent to the destination. Activation to a destination is essentially a process of identity disaggregation.

Warning: The Profile attribute export dataset does not contain profiles that have more than 50 identities. In order to avoid losing these profiles, you need to contact Adobe to configure the identity graph so that it retains the most recent identities belonging to a cookie namespace.

Just for fun, let us see what fields are being used in our destinations in my test environment:

select * from br_namespace_destination
where isMapped='true'
order by destinationID

The results are:

Find Merge Policies in the Snapshot

The following tables are created from the Profile Attribute Snapshot in the reporting star schema for the Real-Time Customer Profiles. We will write queries against these tables to get answers that will be

SELECT * FROM adwh_dim_merge_policies;

Note that there is no table name to merge policy mapping in this star schema. You need to run the first query to determine the merge policy and identify the dataset_id. Use that to search the dataset name in Univeral Search at the top and not local dataset search.

You can get the dataset_id to the dataset name by typing the following and browsing down the list of results:

SHOW TABLES

Another approach would be to copy and paste that into the search bar at the top in the AEP UI.

The dataset name will reveal itself. Click on the dataset to get the table name which you will need as the table name for all your queries.

You should be able to start exploring the profile snapshot datasets immediately.

SELECT * FROM profile_attribute_cd03f195_66b5_4a62_a6f9_c606e951c903;
SELECT person.name.lastName FROM profile_attribute_cd03f195_66b5_4a62_a6f9_c606e951c903

Retrieve Segment Information

SELECT * FROM adwh_dim_segments;

Count Profiles by Merge Policy

Merge policy is an "MDM-like" feature that lets you prioritize attribute records whenever there is a conflict i.e. either use one dataset over the other or use the timestamp to resolve that conflict. Mostly, you will see that timestamp precedence is used as the source of truth is typically a CRM system that is evolving over time, and new attribute records with updates get added to the Real-Time Customer Profile. The new records need to take precedence.

SELECT COUNT(identityMap) FROM profile_attribute_cd03f195_66b5_4a62_a6f9_c606e951c903;

If you navigate to the Profiles->Overview page, for the same merge policy, you will see the same count. Note that we had chosen Default Time-based merge policy in this example in our environment.

Use EXPLODE to Separate Identities in Separate Rows

SELECT Segmentmembership, explode(identitymap) from profile_attribute_cd03f195_66b5_4a62_a6f9_c606e951c903;

This splitting into rows destroys Identity map association.

Before you extract the identity information, you need to understand the map structure. Use the to_json feature in Data Distiller to get in place information about the schema structure without having to go to the Schemas UI screen. This is an extremely important feature with deeply nested data structures and maps.

SELECT to_json(identityMap) FROM profile_attribute_cd03f195_66b5_4a62_a6f9_c606e951c903;

If you execute the following, you will see the following:

What you see above is the identity map structure. The map has the identity namespace (email, crmid) as the unique key to index the values. Note that this is an array of values. If you need to extract the identities, you will need to use the following code:

SELECT identityMap.email.id FROM profile_attribute_cd03f195_66b5_4a62_a6f9_c606e951c903;

The results look like the following:

Typically we would explode this array

SELECT explode(identityMap.email.id) FROM profile_attribute_cd03f195_66b5_4a62_a6f9_c606e951c903;

But there is a problem - plain vanilla "explode" will get rid of rows that do not have any identity values. That is a problem because the absence of an identity is a signal and is possibly associated with other identities that the person may have. Let us fix the issue by using explode_outer from Data Distiller:

SELECT explode_outer(identityMap.email.id) AS email  FROM profile_attribute_cd03f195_66b5_4a62_a6f9_c606e951c903

But just doing an explode_outer on a single identity namespace is of no use. We have destroyed the association between identities within that same namespace. Let us generate a synthetic UUID for each profile row to keep track of our identities as we explode them. If we do this right, we will generate a table of UUIDs and related identities as a columnar table that gives us leverage to use it for a variety of operations.

If we take the first element of the email namespace and concatenate that with the first element of the crmid namespace, we are guaranteed a unique identifier. If not, it would mean that two rows in the profile export dataset have that identity in common. The identity stitching in Real-Time Customer Profile should have taken care of it and merged it into a single row.

Let us now generate the unique UUIDs along with the schema-friendly identity map structure

SELECT concat(identityMap.email.id[0],identityMap.crmid.id[0]) AS unique_id,to_json(identityMap) from profile_attribute_cd03f195_66b5_4a62_a6f9_c606e951c903

The results will look like:

But concat string function does a poor job at concatenating NULL values. We have to remedy that with COALESCE:

SELECT COALESCE(identityMap.email.id[0],identityMap.crmid.id[0]) as unique_id,to_json(identityMap) from profile_attribute_cd03f195_66b5_4a62_a6f9_c606e951c903

This works and you will get:

If there are two identity namespaces, then the explode_outer operator works on each, one at a time. Make sure you remove the to_json as we do not need it anymore:

SELECT unique_id, explode_outer(identityMap.email.id) AS email, identityMap.crmid.id AS crmid FROM
(SELECT coalesce(identityMap.email.id[0],identityMap.crmid.id[0]) AS unique_id,identityMap from profile_attribute_cd03f195_66b5_4a62_a6f9_c606e951c903)

You will get:

We need to explode this one more time for crmid and we should get:

SELECT unique_id, email, explode_outer(crmid) as crmid FROM (
SELECT unique_id, explode_outer(identityMap.email.id) AS email, identityMap.crmid.id AS crmid FROM
(SELECT coalesce(identityMap.email.id[0],identityMap.crmid.id[0]) AS unique_id,identityMap from profile_attribute_cd03f195_66b5_4a62_a6f9_c606e951c903)
)

The results would be:

Using this table, I can do some very interesting analysis. For example, we can look at the histogram of email IDs in the system:

SELECT bucket, count(unique_id) FROM(
SELECT unique_id, count(CASE WHEN email IS NULL THEN 0 ELSE email END) as bucket FROM(
SELECT unique_id, email, explode_outer(crmid) as crmid FROM (
SELECT unique_id, explode_outer(identityMap.email.id) AS email, identityMap.crmid.id AS crmid FROM
(SELECT coalesce(identityMap.email.id[0],identityMap.crmid.id[0]) AS unique_id,identityMap from profile_attribute_cd03f195_66b5_4a62_a6f9_c606e951c903)
))
GROUP BY unique_id 
ORDER BY bucket ASC)
GROUP BY bucket

The answer looks like this:

Why are we seeing 976 as a bucket and 0 counts for unique_ids? The sum of 8225 and 976 adds up to the number of profiles. Why did we even get this result in the first place? Is there something wrong with the way the SQL is written or is there something more profound happening in the identity graph?

Still cannot figure it out? What if the email and crmid identities were non-existent? You have null rows in the table that you should clean before you do any analytics. For the identity lookup use case which is the goal of this section, it would not matter,

Extract Identities Without Breaking Segment Membership Association

In our example, we will be simplifying the use case since we do not have multiple identities. We will be just using the first element to create a simple map.

SELECT identitymap.email.id[0] AS email, identitymap.crmid.id[0] AS crmid from profile_attribute_cd03f195_66b5_4a62_a6f9_c606e951c903;
SELECT Segmentmembership, identitymap.email.id[0] AS email, identitymap.crmid.id[0] AS crmid
from profile_attribute_cd03f195_66b5_4a62_a6f9_c606e951c903;

Expand Segment Membership

SELECT  explode(Segmentmembership), email, crmid  
       FROM 
         (SELECT Segmentmembership, identitymap.email.id[0] AS email, identitymap.crmid.id[0] AS crmid
            FROM profile_attribute_cd03f195_66b5_4a62_a6f9_c606e951c903)sq

Create Segment Membership, Email, and CRMID Triples

SELECT key AS segment_id, email, crmid FROM (
SELECT explode(value), email, crmid   FROM ( 
    SELECT  explode(Segmentmembership), email, crmid  
       FROM 
         (SELECT Segmentmembership, identitymap.email.id[0] AS email,     identitymap.crmid.id[0] AS crmid
            from profile_attribute_cd03f195_66b5_4a62_a6f9_c606e951c903)
            )
            ); 

Resolve Segment ID to Segment Name

The segment_id needs to be resolved to a segment name. Note that I have to name the subqueries in order to reference them in the INNER JOIN clause. I am also creating a unique UID by concatenating the ID values

SELECT segment_name, email, crmid, concat(email, crmid) AS UID FROM 
(
SELECT key AS segment_id, email, crmid FROM (
SELECT explode(value), email, crmid   FROM ( 
    SELECT  explode(Segmentmembership), email, crmid  
       FROM 
         (SELECT Segmentmembership, identitymap.email.id[0] AS email, identitymap.crmid.id[0] AS crmid
            from profile_attribute_cd03f195_66b5_4a62_a6f9_c606e951c903)
            )
            ) ) AS A
INNER JOIN adwh_dim_segments AS B 
ON A.segment_id=B.segment;

Write Query Result to Data Lake

Materialize this table to the data lake. Use the DROP TABLE clause to wipe out an existing table

DROP TABLE  IF EXISTS segment_data;
CREATE TABLE segment_data AS (SELECT segment_name, email, crmid, concat(email, crmid) AS UID FROM 
(
SELECT key AS segment_id, email, crmid FROM (
SELECT explode(value), email, crmid   FROM ( 
    SELECT  explode(Segmentmembership), email, crmid  
       FROM 
         (SELECT Segmentmembership, identitymap.email.id[0] AS email, identitymap.crmid.id[0] AS crmid
            from profile_attribute_cd03f195_66b5_4a62_a6f9_c606e951c903)
            )
            ) ) AS A
INNER JOIN adwh_dim_segments AS B 
ON A.segment_id=B.segment);

Explore what is contained in the dataset. We should see 13K rows

SELECT * FROM segment_data;

Identify records that have NULL crmid values

SELECT * FROM segment_data
WHERE crmid IS NULL;

Audience Size Calculation

Here we will count the number of people vs what I see in the UI. There are some segments that are zero and that do not agree with UI. What do you think happened?

SELECT segment_name, count(UID)
FROM segment_data
GROUP BY segment_name

Do not materialize the dataset. Instead, let us fix this with COALESCE

SELECT segment_name, count(DISTINCT UID) FROM (SELECT segment_name, email, crmid, concat(COALESCE(email, ''), COALESCE(crmid, ''))AS UID FROM 
(
SELECT key AS segment_id, email, crmid FROM (
SELECT explode(value), email, crmid   FROM ( 
    SELECT  explode(Segmentmembership), email, crmid  
       FROM 
         (SELECT Segmentmembership, identitymap.email.id[0] AS email, identitymap.crmid.id[0] AS crmid
            from profile_attribute_cd03f195_66b5_4a62_a6f9_c606e951c903)
            )
            ) ) AS A
INNER JOIN adwh_dim_segments AS B 
ON A.segment_id=B.segment)
GROUP BY segment_name;

We still do not materialize because we need to check the count. It should be 8,225 in our case.

SELECT count(DISTINCT UID) FROM (SELECT segment_name, email, crmid, concat(COALESCE(email, ''), COALESCE(crmid, '')) AS UID FROM 
(
SELECT key AS segment_id, email, crmid FROM (
SELECT explode(value), email, crmid   FROM ( 
    SELECT  explode(Segmentmembership), email, crmid  
       FROM 
         (SELECT Segmentmembership, identitymap.email.id[0] AS email, identitymap.crmid.id[0] AS crmid
            from profile_attribute_cd03f195_66b5_4a62_a6f9_c606e951c903)
            )
            ) ) AS A
INNER JOIN adwh_dim_segments AS B 
ON A.segment_id=B.segment);

The above query result does not agree with what we see below. Where is the difference?

Exploding segmentMembership eliminated unsegmented profiles.

SELECT COUNT(identityMap) FROM profile_attribute_cd03f195_66b5_4a62_a6f9_c606e951c903;
SELECT COUNT(identityMap) FROM profile_attribute_cd03f195_66b5_4a62_a6f9_c606e951c WHERE Segmentmembership IS NULL903

If you add 6500 (profiles attached to at least one segment)+1725 (profiles not attached to any segment), you get 8225 which is our count.

Generate Segment Name to Identities Table

DROP TABLE  IF EXISTS segment_data;
CREATE TABLE segment_data AS (
SELECT segment_name, email, crmid, concat(COALESCE(email, ''), COALESCE(crmid, '')) AS UID FROM 
(
SELECT key AS segment_id, email, crmid FROM (
SELECT explode(value), email, crmid   FROM ( 
    SELECT  explode(Segmentmembership), email, crmid  
       FROM 
         (SELECT Segmentmembership, identitymap.email.id[0] AS email, identitymap.crmid.id[0] AS crmid
            from profile_attribute_cd03f195_66b5_4a62_a6f9_c606e951c903)
            )
            ) ) AS A
INNER JOIN adwh_dim_segments AS B 
ON A.segment_id=B.segment);

Let us create some analytical queries

SELECT segment_name, count(DISTINCT UID), COUNT(DISTINCT email) AS count_email, COUNT(DISTINCT crmid) AS count_crmid
FROM segment_data
GROUP BY segment_name;

Double-check:

SELECT * FROM segment_data;
SELECT COUNT(DISTINCT UID) FROM segment_data;

Generate Emails Associated with a Segment Name

SELECT segment_name, array_agg(email) as email_list, array_agg(crmid) as crm_list
FROM segment_data
GROUP BY segment_name;

Generate Segments Associated with an Email Address

SELECT email, array_agg(segment_name)
FROM segment_data
GROUP BY email

3 Segment Overlap

SELECT * FROM (SELECT UID, array_agg(segment_name) AS seg_array FROM segment_data GROUP BY UID) WHERE array_contains(seg_array, 'United States') AND array_contains(seg_array, 'Saurabh New') AND array_contains(seg_array, 'Winter wear')

4 Segment Overlap

SELECT * FROM (SELECT UID, array_agg(segment_name) AS seg_array FROM segment_data GROUP BY UID)
WHERE array_contains(seg_array, 'United States') AND array_contains(seg_array, 'Saurabh New') AND array_contains(seg_array, 'Winter wear') 
AND array_contains(seg_array, 'Male Gender Test Segment')

Even before we typed this query, we should have been able to predict the kinds of answers we would have gotten. The lowest-sized audience will give you the upper limit on the overlap. Can you figure out which of these segments is that?

However, if we execute the query, we see the not-so-surprising result.

Scrolling to right, you will see identityMap field and then the segmentMembership.
Destination to segment napping
Segment ID to segment mapping raw table.
Destination ID to Destination Account Name mapping
Source identity namespaces that could map to the destination identity namespace.
Deestination identity fields being used in my environment.
Copy the dataset_id from the query result
Query result
Dataset name is available for dataset id typed into Univeersal search
Query result
Query result
Query result
Query result
Profiles Overview page has merge policy filter that gives you the same count.
Query result
Identity maps structure
Array of identity values
Exploding the array helps you extract the identities.
explode_outer retains the nulls for the identity namespaces
Generate the unique ID without llosing identity associations.
COALESCE function retrieves the first non-zero value in a list and is a good choice for a unique ID for the profile in our example.
email identities have been separated into separate rows without breaking profile association.
Identity lookup table in relational form
Query result
Query result
Query result
Query result.
Query result
Query result
Query result
Query result
Query result
Query result
Query result
Query result
Query result
Query result
Query result
Query result
Query result
Query Result
Query Result
Query result.
Page cover image