Skip to content

unified_performance_v2

This view combines all 6 ad platforms into a single cross-platform reporting interface. It uses a force-swap approach to map each platform's naming convention column into a universal CampaignName, enabling all UDFs to operate on a single column without CASE logic.

View Details

PropertyValue
Full pathpaidteam-data-warehouse.customer_ads_suse.unified_performance_v2
ArchitectureTwo-layer: CTE (UNION ALL + JOINs) + enrichment wrapper
Total columns35 (31 original + 4 status enrichment)
Total rows~837K
PlatformsGoogle Ads, Microsoft Ads, LinkedIn Ads, Reddit Ads, 6sense, StackAdapt
Created2026-02-08
Updated2026-02-09 — Status enrichment (4 new columns)

Architecture

Layer 1 (CTE "base"):
  UNION ALL of 6 platform adlevel_final_v2 tables
  + LEFT JOIN creative_final_v2 for LinkedIn & Reddit
  + Force-swap: CampaignName = naming convention for ALL platforms
  + AdCreativeName and DestinationURL as separate columns

Layer 2 (SELECT from base):
  + 4 calculated rates (CTR, CPC, CPM, CPA)
  + 8 metadata UDFs on CampaignName (no CASE needed)
  + CampaignCode (1 CASE: URL vs creative name)
  + ContentName (1 CASE: URL extraction vs creative name parsing)
  + 3 status columns + IsActive boolean
  = 35 total columns → Looker Studio data source

Source Tables

#TableRowsJOIN
1suse_google_adlevel_final_v2213KNone
2suse_microsoft_adlevel_final_v2145KNone
3suse_linkedin_adlevel_final_v2243KLEFT JOIN suse_linkedin_creative_final_v2 ON CreativeID
4suse_reddit_adlevel_final_v239KLEFT JOIN suse_reddit_creative_final_v2 ON CampaignID+AdgroupID+AdID
5suse_6sense_adlevel_final_v2193KNone
6suse_stackadapt_adlevel_final_v24KNone
7suse_linkedin_creative_final_v21.7KJoined into #3
8suse_reddit_creative_final_v2666Joined into #4

Force-Swap Mapping

The core design principle: CampaignName always contains the pipe-delimited naming convention, regardless of which source column it actually comes from. The "other" hierarchy level is placed into AdGroupName.

Why Force-Swap?

All 8 metadata UDFs (get_channel, get_product_line, get_product, get_subproduct, get_geo, get_tier, get_language, get_audience) parse the pipe-delimited naming convention. By force-mapping this into a single CampaignName column for every platform, the enrichment layer calls all UDFs with b.CampaignName -- zero CASE logic needed.

Per-Platform Hierarchy Mapping

Unified ColumnGoogleMicrosoftLinkedInReddit6senseStackAdapt
CampaignIDa.CampaignIDa.CampaignIDa.CampaignIDa.CampaignIDa.Campaign_IDa.Campaign_ID
CampaignNamea.Campaigna.CampaignNamea.CampaignNamea.Adgroupnamea.Campaigna.Campaign
AdGroupIDa.AdgroupIDa.AdGroupIDa.CampaignGroupIDa.AdgroupIDa.AdGroup_IDa.AdGroup_ID
AdGroupNamea.Adgroupa.AdGroupNamea.CampaignGroupNamea.Campaignnamea.AdGroupa.AdGroup
AdIDa.AdIDa.AdIDa.CreativeIDa.AdIDa.Ad_IDa.Ad_ID
AdCreativeNamea.Adnamea.AdTitlea.CreativeNamea.Adnamea.Ada.Ad
DestinationURLa.CreativeFinalURLa.FinalUrla.CreativeDestinationURLc.AdClickURLNULLa.Click_URL

LinkedIn Force-Swap

LinkedIn has a 3-level hierarchy: CampaignGroup (top) > Campaign (mid) > Creative (ad). The naming convention lives in CampaignName (mid-level). So:

  • CampaignName = LinkedIn's CampaignName (mid-level, where convention lives)
  • AdGroupName = LinkedIn's CampaignGroupName (top-level short label, demoted)
  • AdGroupID = LinkedIn's CampaignGroupID

Reddit Force-Swap

Reddit encodes the full naming convention in Adgroupname (not Campaignname). So:

  • CampaignName = Reddit's Adgroupname (where convention lives)
  • AdGroupName = Reddit's Campaignname (short label, demoted)

Column Groups

Group A: Date & Platform (2)

ColumnTypeDescription
DateDATEReporting date (partition key in source tables)
PlatformSTRINGPlatform label: Google Ads, Microsoft Ads, LinkedIn Ads, Reddit Ads, 6sense Ads, StackAdapt Ads

Group B: Unified Hierarchy (6)

ColumnTypeDescription
CampaignIDSTRINGCampaign identifier (CAST to STRING for cross-platform consistency)
CampaignNameSTRINGForce-mapped naming convention. Always the pipe-delimited string. Feeds all 8 metadata UDFs.
AdGroupIDSTRINGAd group identifier (LinkedIn: CampaignGroupID; others: native)
AdGroupNameSTRINGAd group name (LinkedIn: CampaignGroupName; Reddit: Campaignname short label)
AdIDSTRINGAd/creative identifier (LinkedIn: CreativeID)
AdCreativeNameSTRINGActual creative/ad name (not force-mapped)

Group C: URL (1)

ColumnTypeDescription
DestinationURLSTRINGLanding page URL. NULL for 6sense. From creative table for LinkedIn/Reddit.

Group D: Platform Context (1)

ColumnTypeDescription
ChannelTypeSTRINGPlatform-native channel type (SEARCH, DISPLAY, SPONSORED_STATUS_UPDATE, ABM Display, etc.)

Group E: Core Metrics (3)

ColumnTypeDescription
ImpressionsINT64Total impressions
ClicksINT64Total clicks
CostFLOAT64Spend in USD (Google: CostUSD, Reddit: Spend, others: Cost)

Group F: Row-Level Rates (4)

These are per-row (Date + Ad) rates with a trailing underscore to avoid naming conflicts with Looker Studio calculated fields. In Looker Studio, create calculated fields named CTR, CPC, CPM, CPA that properly aggregate: e.g., CTR = SUM(Clicks) / SUM(Impressions).

ColumnTypeFormula
CTR_FLOAT64SAFE_DIVIDE(Clicks, Impressions)
CPC_FLOAT64SAFE_DIVIDE(Cost, Clicks)
CPM_FLOAT64SAFE_DIVIDE(Cost * 1000, Impressions)
CPA_FLOAT64SAFE_DIVIDE(Cost, NULLIF(TotalConversions, 0))

Group G: Conversions (1)

ColumnTypeDescription
TotalConversionsFLOAT64Composite per platform (see below)

Per-platform TotalConversions formula:

PlatformFormula
Googlea.Conversions
MicrosoftCAST(a.Conversions AS FLOAT64)
LinkedInOneClickLeads + DocumentCompletions + ExternalWebsiteConversions
RedditRedditLeads + ConversionLeadClicks + ConversionSignUpClicks
6sense0.0 (not tracked)
StackAdaptCAST(a.Conversions AS FLOAT64)

Group H: Engagement (2)

ColumnTypeDescription
ReachINT64Unique users/accounts reached. LinkedIn, Reddit, 6sense only. NULL for others.
Frequency_FLOAT64Avg impressions per user. LinkedIn and Reddit only. NULL for others. Trailing underscore for Looker Studio naming.

Group I: UDF Enrichment (10)

ColumnUDFInputDescription
Channelget_channelCampaignNameMarketing channel (Search, Paid Social, Display, etc.)
ProductLineget_product_lineCampaignName, PlatformProduct line (Linux, Cloud Native, AI, Edge)
Productget_productCampaignName, PlatformSpecific product (SLES, Rancher, MLS, etc.)
Campaignget_subproductCampaignName, PlatformCampaign-level detail (was SubProduct). Hierarchy: ProductLine > Product > Campaign
GEOget_geoCampaignName, PlatformGeographic region (NA, EMEA, APAC, LATAM, Global)
Tierget_tierCampaignName, PlatformMarket tier (T1, T2, T1+2, etc.)
Languageget_languageCampaignName, PlatformLanguage code (EN, DE, FR, etc.)
Audienceget_audienceCampaignNameAudience segment (Prospecting, Retargeting, TOFU, etc.)
CampaignCodeget_campaign_codesee belowNumeric campaign tracking code
ContentNamesee belowsee belowHuman-readable content name

CampaignCode routing (1 CASE):

sql
get_campaign_code(
  b.CampaignName,
  CASE WHEN b.Platform IN ('Google Ads', 'Microsoft Ads')
       THEN b.DestinationURL       -- URL with utm_campaign
       ELSE b.AdCreativeName       -- creative name with code prefix
  END,
  b.Platform
)

ContentName routing (1 CASE):

sql
CASE
  WHEN b.Platform IN ('Google Ads', 'Microsoft Ads')
  THEN get_content_name_from_url(b.DestinationURL, b.Platform)  -- extract from URL
  ELSE get_content_name(b.AdCreativeName, b.CampaignName)       -- parse creative name
END

ContentName coverage: 6/6 platforms (was 4/6 in V1).

Group J: Audit (1)

ColumnTypeDescription
_ingested_atTIMESTAMPSource table ingestion timestamp

Group K: Status Enrichment (4)

ColumnTypeDescription
CampaignStatusSTRINGNormalized: ACTIVE, PAUSED, REMOVED, COMPLETED. Default PAUSED if not found.
AdGroupStatusSTRINGSame normalized values as CampaignStatus.
AdStatusSTRINGSame normalized values as CampaignStatus.
IsActiveBOOLEANTRUE only when all 3 statuses are ACTIVE.

Status Normalization

All raw platform status values are normalized via the normalize_status UDF. Google's ENABLED becomes ACTIVE, Microsoft/6sense Title Case becomes UPPER CASE. If an adlevel row has no matching status table row, all 3 statuses default to PAUSED.

LinkedIn Status Mapping

LinkedIn status columns follow the force-swap hierarchy:

  • CampaignStatus ← LinkedIn's CampaignStatus (mid-level, where naming convention lives)
  • AdGroupStatus ← LinkedIn's CampaignGroupStatus (top-level, swapped!)
  • AdStatus ← LinkedIn's CreativeStatus (bottom-level)

Status source tables:

PlatformStatus TableJOIN Keys
Googlesuse_google_status_final_v2CampaignID + AdgroupID + AdID
Microsoftsuse_microsoft_status_final_v2CampaignID + AdGroupID + AdID
LinkedInsuse_linkedin_status_final_v2CampaignGroupID + CampaignID + CreativeID
Redditsuse_reddit_status_final_v2CampaignID + AdgroupID + AdID
6sensesuse_6sense_status_final_v2Campaign_ID + AdGroup_ID + Ad_ID
StackAdapt(none — hardcoded PAUSED)

Example Queries

Cross-Platform Spend by Product Line (Last 30 Days)

sql
SELECT
  Platform,
  ProductLine,
  Product,
  SUM(Cost) AS TotalSpend,
  SUM(Impressions) AS TotalImpressions,
  SUM(Clicks) AS TotalClicks,
  SAFE_DIVIDE(SUM(Clicks), SUM(Impressions)) AS AvgCTR,
  SAFE_DIVIDE(SUM(Cost), SUM(Clicks)) AS AvgCPC
FROM `paidteam-data-warehouse.customer_ads_suse.unified_performance_v2`
WHERE Date >= DATE_SUB(CURRENT_DATE(), INTERVAL 30 DAY)
GROUP BY Platform, ProductLine, Product
ORDER BY TotalSpend DESC
LIMIT 20;

Content Performance Across All Platforms

sql
SELECT
  ContentName,
  Platform,
  CampaignCode,
  SUM(Cost) AS TotalSpend,
  SUM(Impressions) AS TotalImpressions,
  SUM(Clicks) AS TotalClicks,
  SAFE_DIVIDE(SUM(Cost), NULLIF(SUM(TotalConversions), 0)) AS AvgCPA
FROM `paidteam-data-warehouse.customer_ads_suse.unified_performance_v2`
WHERE Date >= DATE_SUB(CURRENT_DATE(), INTERVAL 30 DAY)
  AND ContentName != 'Unknown'
GROUP BY ContentName, Platform, CampaignCode
ORDER BY TotalSpend DESC
LIMIT 30;

GEO Performance Breakdown

sql
SELECT
  GEO,
  Channel,
  COUNT(DISTINCT Platform) AS PlatformCount,
  SUM(Cost) AS TotalSpend,
  SUM(Clicks) AS TotalClicks,
  SAFE_DIVIDE(SUM(Clicks), SUM(Impressions)) AS AvgCTR,
  SAFE_DIVIDE(SUM(Cost), NULLIF(SUM(TotalConversions), 0)) AS AvgCPA
FROM `paidteam-data-warehouse.customer_ads_suse.unified_performance_v2`
WHERE Date >= DATE_SUB(CURRENT_DATE(), INTERVAL 90 DAY)
GROUP BY GEO, Channel
ORDER BY TotalSpend DESC;

Platform Comparison by Month

sql
SELECT
  DATE_TRUNC(Date, MONTH) AS Month,
  Platform,
  SUM(Cost) AS MonthlySpend,
  SUM(Impressions) AS MonthlyImpressions,
  SUM(Clicks) AS MonthlyClicks,
  SUM(TotalConversions) AS MonthlyConversions,
  SAFE_DIVIDE(SUM(Cost), NULLIF(SUM(TotalConversions), 0)) AS MonthlyCPA
FROM `paidteam-data-warehouse.customer_ads_suse.unified_performance_v2`
GROUP BY Month, Platform
ORDER BY Month DESC, MonthlySpend DESC;

Design Decisions

Separate AdCreativeName + DestinationURL

Unlike V1 which collapsed creative names and URLs into a single AdName column, V2 keeps them separate:

  • AdCreativeName = the actual creative/ad name (useful for LinkedIn, Reddit, 6sense, StackAdapt)
  • DestinationURL = the landing page URL (useful for Google, Microsoft campaign code extraction)

This enables the enrichment layer to use CASE logic to pick the right column for get_campaign_code and get_content_name without losing information.

Creative JOINs in Layer 1

LinkedIn and Reddit benefit from creative table JOINs to get richer name and URL data. The LEFT JOIN ensures all adlevel rows are preserved even without a creative match. COALESCE prefers creative table values but falls back to adlevel values.

ContentName from URLs

The new get_content_name_from_url UDF extracts content names from utm_campaign URL parameters for Google and Microsoft, solving the V1 limitation where these platforms returned "Unknown" for ContentName.

Extensibility

The two-layer architecture supports easy extension:

ChangeHow
New platformAdd one SELECT block to the UNION ALL in Layer 1
New creative JOINAdd LEFT JOIN in the platform's block in Layer 1
New UDF columnAdd one line in Layer 2
New data source JOINAdd LEFT JOIN in Layer 2 against the base CTE

Dependencies

  • Tables: All 6 adlevel_final_v2 tables + 2 creative_final_v2 tables (LinkedIn, Reddit) + 5 status_final_v2 tables (Google, Microsoft, LinkedIn, Reddit, 6sense)
  • UDFs: All 12 UDFs (get_channel, get_product_line, get_product, get_subproduct, get_geo, get_tier, get_language, get_audience, get_campaign_code, get_content_name, get_content_name_from_url, normalize_status)
  • Consumers: Looker Studio cross-platform dashboards
  • Spec document: UNIFIED_VIEW_SPEC.md
  • Mapping reference: UDF_MAPPING_REPORT.md

SUSE Paid Advertising Data Warehouse V2