feat(MA): add campaign name mappings configuration and validation (#40182)

Co-authored-by: Javier Bahamondes <javierbahamondes@Javiers-MacBook-Pro.local>
This commit is contained in:
Javier Bahamondes
2025-10-23 12:36:39 -03:00
committed by GitHub
parent 0602183cfa
commit 515d0d25ea
22 changed files with 1248 additions and 389 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 184 KiB

After

Width:  |  Height:  |  Size: 191 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 184 KiB

After

Width:  |  Height:  |  Size: 187 KiB

View File

@@ -326,6 +326,7 @@ export const FEATURE_FLAGS = {
REPLAY_CLIENT_SIDE_DECOMPRESSION: 'replay-client-side-decompression', // owner: @pauldambra #team-replay
COHORT_CALCULATION_CHUNKED: 'cohort-calculation-chunked', // owner: @gustavo #team-feature-flags
EXPERIMENTS_USE_NEW_QUERY_BUILDER: 'experiments-use-new-query-builder', // owner: @andehen #team-experiments
ADVANCE_MARKETING_ANALYTICS_SETTINGS: 'advance-marketing-analytics-settings', // owner: @jabahamondes #team-web-analytics
} as const
export type FeatureFlagKey = (typeof FEATURE_FLAGS)[keyof typeof FEATURE_FLAGS]

View File

@@ -16663,6 +16663,18 @@
"attribution_window_days": {
"type": "number"
},
"campaign_name_mappings": {
"additionalProperties": {
"additionalProperties": {
"items": {
"type": "string"
},
"type": "array"
},
"type": "object"
},
"type": "object"
},
"conversion_goals": {
"items": {
"$ref": "#/definitions/ConversionGoalFilter"

View File

@@ -4023,6 +4023,7 @@ export interface MarketingAnalyticsConfig {
conversion_goals?: ConversionGoalFilter[]
attribution_window_days?: number
attribution_mode?: AttributionMode
campaign_name_mappings?: Record<string, Record<string, string[]>>
}
export enum MarketingAnalyticsBaseColumns {

View File

@@ -81,6 +81,9 @@ SettingsEnvironmentRevenueAnalytics.args = { sectionId: 'environment-revenue-ana
export const SettingsEnvironmentMarketingAnalytics: Story = Template.bind({})
SettingsEnvironmentMarketingAnalytics.args = { sectionId: 'environment-marketing-analytics' }
SettingsEnvironmentMarketingAnalytics.parameters = {
featureFlags: [...Object.values(FEATURE_FLAGS), 'advance-marketing-analytics-settings'],
}
export const SettingsEnvironmentWebAnalytics: Story = Template.bind({})
SettingsEnvironmentWebAnalytics.args = { sectionId: 'environment-web-analytics' }

View File

@@ -0,0 +1,181 @@
import { useActions, useValues } from 'kea'
import { useState } from 'react'
import { IconPlusSmall, IconTrash } from '@posthog/icons'
import { LemonButton, LemonInput, LemonSelect, LemonTag } from '@posthog/lemon-ui'
import { externalDataSources } from '~/queries/schema/schema-general'
import { marketingAnalyticsSettingsLogic } from '../../logic/marketingAnalyticsSettingsLogic'
import { VALID_NATIVE_MARKETING_SOURCES } from '../../logic/utils'
const SEPARATOR = ','
export function CampaignNameMappingsConfiguration(): JSX.Element {
const { marketingAnalyticsConfig } = useValues(marketingAnalyticsSettingsLogic)
const { updateCampaignNameMappings } = useActions(marketingAnalyticsSettingsLogic)
const campaignMappings = marketingAnalyticsConfig?.campaign_name_mappings || {}
const [selectedSource, setSelectedSource] = useState<string>('')
const [newCleanName, setNewCleanName] = useState('')
const [newRawValues, setNewRawValues] = useState('')
const availableSources = externalDataSources.filter((source) =>
VALID_NATIVE_MARKETING_SOURCES.includes(source as any)
)
const updateMappings = (newMappings: Record<string, Record<string, string[]>>): void => {
updateCampaignNameMappings(newMappings)
}
const addMapping = (): void => {
if (!selectedSource || !newCleanName.trim() || !newRawValues.trim()) {
return
}
const rawValuesArray = newRawValues
.split(SEPARATOR)
.map((v) => v.trim())
.filter((v) => v.length > 0)
const sourceMappings = campaignMappings[selectedSource] || {}
updateMappings({
...campaignMappings,
[selectedSource]: {
...sourceMappings,
[newCleanName.trim()]: rawValuesArray,
},
})
setNewCleanName('')
setNewRawValues('')
}
const removeMapping = (source: string, cleanName: string): void => {
const sourceMappings = { ...campaignMappings[source] }
delete sourceMappings[cleanName]
if (Object.keys(sourceMappings).length === 0) {
const newMappings = { ...campaignMappings }
delete newMappings[source]
updateMappings(newMappings)
} else {
updateMappings({
...campaignMappings,
[source]: sourceMappings,
})
}
}
const totalMappings = Object.values(campaignMappings).reduce(
(sum, sourceMappings) => sum + Object.keys(sourceMappings).length,
0
)
return (
<div className="space-y-4">
<div>
<h3 className="text-lg font-semibold mb-1">Campaign name mappings</h3>
<p className="text-muted mb-4">
Map UTM campaign values to your ad platform campaign names for proper conversion attribution. Ad
platforms (LinkedIn, Google, TikTok, etc.) don't store UTM parameters—they only have campaign names.
PostHog joins conversions to paid campaigns by matching{' '}
<code className="text-xs">utm_campaign</code> values with campaign names from your ad integrations.
If your <code className="text-xs">utm_campaign</code> doesn't exactly match your ad platform
campaign name (e.g., "2025q3_paid_social_linkedin" vs "TOFU Video Views | LinkedIn | Global"), your
conversions won't attribute to the paid campaign. Use this to map multiple UTM variations to the
correct campaign name.
</p>
</div>
{totalMappings > 0 && (
<div className="border rounded p-4 space-y-4">
<h4 className="font-semibold">Current mappings ({totalMappings})</h4>
{Object.entries(campaignMappings).map(([source, sourceMappings]) => (
<div key={source} className="space-y-2">
<div className="font-medium text-sm text-muted">{source}</div>
{Object.entries(sourceMappings).map(([cleanName, rawValues]) => (
<div
key={cleanName}
className="flex items-start justify-between bg-bg-light rounded p-3"
>
<div className="flex-1">
<div className="font-medium mb-2">{cleanName}</div>
<div className="flex flex-wrap gap-1">
{(rawValues as string[]).map((rawValue) => (
<LemonTag key={rawValue}>{rawValue}</LemonTag>
))}
</div>
</div>
<LemonButton
type="secondary"
size="small"
icon={<IconTrash />}
onClick={() => removeMapping(source, cleanName)}
tooltip="Remove mapping"
/>
</div>
))}
</div>
))}
</div>
)}
<div className="border rounded p-4 space-y-3">
<h4 className="font-semibold">Add new mapping</h4>
<div className="space-y-3">
<div>
<label className="block text-sm font-medium mb-1">Data source</label>
<LemonSelect
value={selectedSource}
onChange={setSelectedSource}
options={[
{ label: 'Select a source...', value: '' },
...availableSources.map((source) => ({ label: source, value: source })),
]}
fullWidth
/>
</div>
<div>
<label className="block text-sm font-medium mb-1">Ad platform campaign name</label>
<LemonInput
value={newCleanName}
onChange={setNewCleanName}
placeholder="e.g., campaign name from the Data Warehouse table (e.g., TOFU Video Views | LinkedIn | Global)"
fullWidth
/>
<div className="text-xs text-muted mt-1">
The exact campaign name from your ad platform (LinkedIn, Google, etc.)
</div>
</div>
<div>
<label className="block text-sm font-medium mb-1">UTM campaign values to map</label>
<LemonInput
value={newRawValues}
onChange={setNewRawValues}
placeholder="e.g., utm campaign from the url (e.g., 2025q3_paid_social_linkedin)"
fullWidth
/>
<div className="text-xs text-muted mt-1">
Comma-separated list of utm_campaign values that should map to this campaign
</div>
</div>
<LemonButton
type="primary"
icon={<IconPlusSmall />}
onClick={addMapping}
disabled={!selectedSource || !newCleanName.trim() || !newRawValues.trim()}
fullWidth
>
Add mapping
</LemonButton>
</div>
</div>
</div>
)
}

View File

@@ -1,4 +1,7 @@
import { LemonCollapse } from '@posthog/lemon-ui'
import { BaseCurrency } from 'lib/components/BaseCurrency/BaseCurrency'
import { FlaggedFeature } from 'lib/components/FlaggedFeature'
import { Scene } from 'scenes/sceneTypes'
import { sceneConfigurations } from 'scenes/scenes'
@@ -7,6 +10,7 @@ import { SceneDivider } from '~/layout/scenes/components/SceneDivider'
import { SceneTitleSection } from '~/layout/scenes/components/SceneTitleSection'
import { AttributionSettings } from './AttributionSettings'
import { CampaignNameMappingsConfiguration } from './CampaignNameMappingsConfiguration'
import { ConversionGoalsConfiguration } from './ConversionGoalsConfiguration'
import { NativeExternalDataSourceConfiguration } from './NativeExternalDataSourceConfiguration'
import { NonNativeExternalDataSourceConfiguration } from './NonNativeExternalDataSourceConfiguration'
@@ -46,6 +50,18 @@ export function MarketingAnalyticsSettings({
<NonNativeExternalDataSourceConfiguration />
<SceneDivider />
<SelfManagedExternalDataSourceConfiguration />
<SceneDivider />
<FlaggedFeature flag="advance-marketing-analytics-settings">
<LemonCollapse
panels={[
{
key: 'advanced-marketing-settings',
header: 'Advanced marketing settings',
content: <CampaignNameMappingsConfiguration />,
},
]}
/>
</FlaggedFeature>
</SceneContent>
)
}

View File

@@ -14,6 +14,7 @@ const createEmptyConfig = (): MarketingAnalyticsConfig => ({
conversion_goals: [],
attribution_window_days: DEFAULT_ATTRIBUTION_WINDOW_DAYS,
attribution_mode: AttributionMode.LastTouch,
campaign_name_mappings: {},
})
export const marketingAnalyticsSettingsLogic = kea<marketingAnalyticsSettingsLogicType>([
@@ -47,6 +48,9 @@ export const marketingAnalyticsSettingsLogic = kea<marketingAnalyticsSettingsLog
updateAttributionMode: (mode: AttributionMode) => ({
mode,
}),
updateCampaignNameMappings: (campaignNameMappings: Record<string, Record<string, string[]>>) => ({
campaignNameMappings,
}),
}),
reducers(({ values }) => ({
marketingAnalyticsConfig: [
@@ -138,6 +142,12 @@ export const marketingAnalyticsSettingsLogic = kea<marketingAnalyticsSettingsLog
}
return { ...state, attribution_mode: mode }
},
updateCampaignNameMappings: (state: MarketingAnalyticsConfig | null, { campaignNameMappings }) => {
if (!state) {
return { ...createEmptyConfig(), campaign_name_mappings: campaignNameMappings }
}
return { ...state, campaign_name_mappings: campaignNameMappings }
},
},
],
savedMarketingAnalyticsConfig: [
@@ -188,6 +198,7 @@ export const marketingAnalyticsSettingsLogic = kea<marketingAnalyticsSettingsLog
removeConversionGoal: updateCurrentTeam,
updateAttributionWindowWeeks: updateCurrentTeam,
updateAttributionMode: updateCurrentTeam,
updateCampaignNameMappings: updateCurrentTeam,
}
}),
loaders(({ values }) => ({

View File

@@ -211,10 +211,17 @@ class TeamMarketingAnalyticsConfigSerializer(serializers.ModelSerializer):
attribution_mode = serializers.ChoiceField(
choices=[(mode.value, mode.value.replace("_", " ").title()) for mode in AttributionMode], required=False
)
campaign_name_mappings = serializers.JSONField(required=False)
class Meta:
model = TeamMarketingAnalyticsConfig
fields = ["sources_map", "conversion_goals", "attribution_window_days", "attribution_mode"]
fields = [
"sources_map",
"conversion_goals",
"attribution_window_days",
"attribution_mode",
"campaign_name_mappings",
]
def update(self, instance, validated_data):
# Handle sources_map with partial updates
@@ -240,6 +247,9 @@ class TeamMarketingAnalyticsConfigSerializer(serializers.ModelSerializer):
if "attribution_mode" in validated_data:
instance.attribution_mode = validated_data["attribution_mode"]
if "campaign_name_mappings" in validated_data:
instance.campaign_name_mappings = validated_data["campaign_name_mappings"]
instance.save()
return instance

View File

@@ -626,7 +626,8 @@
"posthog_teammarketinganalyticsconfig"."attribution_window_days",
"posthog_teammarketinganalyticsconfig"."attribution_mode",
"posthog_teammarketinganalyticsconfig"."sources_map",
"posthog_teammarketinganalyticsconfig"."conversion_goals"
"posthog_teammarketinganalyticsconfig"."conversion_goals",
"posthog_teammarketinganalyticsconfig"."campaign_name_mappings"
FROM "posthog_teammarketinganalyticsconfig"
WHERE "posthog_teammarketinganalyticsconfig"."team_id" = 99999
LIMIT 21
@@ -3258,7 +3259,8 @@
"posthog_teammarketinganalyticsconfig"."attribution_window_days",
"posthog_teammarketinganalyticsconfig"."attribution_mode",
"posthog_teammarketinganalyticsconfig"."sources_map",
"posthog_teammarketinganalyticsconfig"."conversion_goals"
"posthog_teammarketinganalyticsconfig"."conversion_goals",
"posthog_teammarketinganalyticsconfig"."campaign_name_mappings"
FROM "posthog_teammarketinganalyticsconfig"
WHERE "posthog_teammarketinganalyticsconfig"."team_id" = 99999
LIMIT 21

View File

@@ -138,6 +138,7 @@ class TestQueryRunner(BaseTest):
"attribution_mode": "last_touch",
"attribution_window_days": 90,
"base_currency": "USD",
"campaign_name_mappings": {},
"sources_map": {
"01977f7b-7f29-0000-a028-7275d1a767a4": {
"cost": "cost",
@@ -200,7 +201,7 @@ class TestQueryRunner(BaseTest):
runner = TestQueryRunner(query={"some_attr": "bla"}, team=team)
cache_key = runner.get_cache_key()
assert cache_key == "cache_335c3a8a6a92c0551e6b428e5bef5aee"
assert cache_key == "cache_db05113c9937a0e206afcc48c43e0a87"
def test_cache_key_runner_subclass(self):
TestQueryRunner = self.setup_test_query_runner_class()
@@ -214,7 +215,7 @@ class TestQueryRunner(BaseTest):
runner = TestSubclassQueryRunner(query={"some_attr": "bla"}, team=team)
cache_key = runner.get_cache_key()
assert cache_key == "cache_a1ce3db3977f9e2c80431c2ce9679d26"
assert cache_key == "cache_6369d6d904d6ce9dea56120059bdceba"
def test_cache_key_different_timezone(self):
TestQueryRunner = self.setup_test_query_runner_class()
@@ -225,7 +226,7 @@ class TestQueryRunner(BaseTest):
runner = TestQueryRunner(query={"some_attr": "bla"}, team=team)
cache_key = runner.get_cache_key()
assert cache_key == "cache_236279f500076c3545cc9cadf63f2838"
assert cache_key == "cache_ee3b91f6f3800eba3e0420c65bbe082c"
@mock.patch("django.db.transaction.on_commit")
def test_cache_response(self, mock_on_commit):

View File

@@ -0,0 +1,23 @@
# Generated migration
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("posthog", "0889_add_schema_models"),
]
operations = [
migrations.AddField(
model_name="teammarketinganalyticsconfig",
name="_campaign_name_mappings",
field=models.JSONField(
blank=True,
db_column="campaign_name_mappings",
default=dict,
help_text="Maps campaign names to lists of raw UTM values per data source",
null=False,
),
),
]

View File

@@ -1 +1 @@
0889_add_schema_models
0890_add_campaign_name_mappings

View File

@@ -56,6 +56,42 @@ def validate_attribution_mode(mode: str) -> None:
raise ValidationError(f"attribution_mode must be one of {valid_modes}")
def validate_campaign_name_mappings(mappings: dict) -> None:
"""
Validate campaign_name_mappings structure: dict of source_id -> dict of clean_name -> list of raw utm values.
Structure: {
"GoogleAds": {
"Spring Sale 2024": ["spring_sale_2024", "spring-sale-2024"],
"Black Friday": ["bf_2024", "blackfriday"]
},
"MetaAds": {...}
}
"""
if not isinstance(mappings, dict):
raise ValidationError("campaign_name_mappings must be a dictionary")
for source_id, campaign_mappings in mappings.items():
if not isinstance(source_id, str):
raise ValidationError(f"Source ID '{source_id}' must be a string")
if not isinstance(campaign_mappings, dict):
raise ValidationError(f"Campaign mappings for source '{source_id}' must be a dictionary")
for clean_name, raw_values in campaign_mappings.items():
if not isinstance(clean_name, str):
raise ValidationError(f"Clean campaign name '{clean_name}' in source '{source_id}' must be a string")
if not isinstance(raw_values, list):
raise ValidationError(f"Raw values for campaign '{clean_name}' in source '{source_id}' must be a list")
for raw_value in raw_values:
if not isinstance(raw_value, str):
raise ValidationError(
f"Raw value '{raw_value}' for campaign '{clean_name}' in source '{source_id}' must be a string"
)
def validate_conversion_goals(conversion_goals: list) -> None:
"""Validate conversion goals structure: list of dicts with name, event, and properties."""
if not isinstance(conversion_goals, list):
@@ -143,6 +179,13 @@ class TeamMarketingAnalyticsConfig(models.Model):
# that are then wrapped by schema-validation getters/setters
_sources_map = models.JSONField(default=dict, db_column="sources_map", null=False, blank=True)
_conversion_goals = models.JSONField(default=list, db_column="conversion_goals", null=True, blank=True)
_campaign_name_mappings = models.JSONField(
default=dict,
db_column="campaign_name_mappings",
null=False,
blank=True,
help_text="Maps campaign names to lists of raw UTM values per data source",
)
def clean(self):
"""Validate model fields"""
@@ -192,6 +235,19 @@ class TeamMarketingAnalyticsConfig(models.Model):
except ValidationError as e:
raise ValidationError(f"Invalid conversion goals: {str(e)}")
@property
def campaign_name_mappings(self) -> dict[str, dict[str, list[str]]]:
return self._campaign_name_mappings or {}
@campaign_name_mappings.setter
def campaign_name_mappings(self, value: dict) -> None:
value = value or {}
try:
validate_campaign_name_mappings(value)
self._campaign_name_mappings = value
except ValidationError as e:
raise ValidationError(f"Invalid campaign name mappings: {str(e)}")
def update_source_mapping(self, source_id: str, field_mapping: dict) -> None:
"""Update or add a single source mapping while preserving existing sources."""
@@ -234,6 +290,7 @@ class TeamMarketingAnalyticsConfig(models.Model):
"sources_map": self.sources_map,
"attribution_window_days": self.attribution_window_days,
"attribution_mode": self.attribution_mode,
"campaign_name_mappings": self.campaign_name_mappings,
}

View File

@@ -9471,6 +9471,7 @@ class MarketingAnalyticsConfig(BaseModel):
)
attribution_mode: Optional[AttributionMode] = None
attribution_window_days: Optional[float] = None
campaign_name_mappings: Optional[dict[str, dict[str, list[str]]]] = None
conversion_goals: Optional[list[Union[ConversionGoalFilter1, ConversionGoalFilter2, ConversionGoalFilter3]]] = None
sources_map: Optional[dict[str, SourceMap]] = None

View File

@@ -1,189 +1,510 @@
# serializer version: 1
# name: TestConversionGoalsAggregator.test_multiple_goals_sql_snapshot
# name: TestConversionGoalsAggregator.test_campaign_name_mapping_integration_with_events
'''
SELECT
campaign,
multiIf(and(in(lower(source), ['google', 'youtube', 'display', 'gmail', 'google_maps', 'google_play', 'google_discover', 'admob', 'waze']), in(lower(campaign), ['spring_sale_2024', 'spring-sale-2024'])), 'Spring Sale 2024', and(in(lower(source), ['google', 'youtube', 'display', 'gmail', 'google_maps', 'google_play', 'google_discover', 'admob', 'waze']), in(lower(campaign), ['holiday_campaign', 'holiday_promo'])), 'Holiday Promo', campaign) AS campaign,
source,
sum(conversion_0) AS conversion_0,
sum(conversion_1) AS conversion_1,
sum(conversion_2) AS conversion_2
conversion_0
FROM
(
SELECT
if(notEmpty(campaign_name), campaign_name, 'organic') AS campaign,
if(in(lower(if(notEmpty(source_name), source_name, 'organic')), ['facebook', 'instagram', 'messenger', 'fb', 'whatsapp', 'audience_network', 'facebook_marketplace', 'threads']), 'meta', if(in(lower(if(notEmpty(source_name), source_name, 'organic')), ['li']), 'linkedin', if(in(lower(if(notEmpty(source_name), source_name, 'organic')), ['youtube', 'display', 'gmail', 'google_maps', 'google_play', 'google_discover', 'admob', 'waze']), 'google', if(notEmpty(source_name), source_name, 'organic')))) AS source,
count() AS conversion_0,
0 AS conversion_1,
0 AS conversion_2
FROM
(
SELECT
person_id,
if(notEmpty(conversion_campaign), conversion_campaign, if(notEmpty(fallback_campaign), fallback_campaign, '')) AS campaign_name,
if(notEmpty(conversion_source), conversion_source, if(notEmpty(fallback_source), fallback_source, '')) AS source_name,
1 AS conversion_value
FROM
(
SELECT
person_id,
conversion_timestamps[i] AS conversion_time,
conversion_math_values[i] AS conversion_math_value,
conversion_campaigns[i] AS conversion_campaign,
conversion_sources[i] AS conversion_source,
arrayMax(arrayFilter(x -> and(lessOrEquals(x, conversion_timestamps[i]), greaterOrEquals(x, minus(conversion_timestamps[i], 7776000))), utm_timestamps)) AS last_utm_timestamp,
if(isNotNull(last_utm_timestamp), utm_campaigns[indexOf(utm_timestamps, last_utm_timestamp)], '') AS fallback_campaign,
if(isNotNull(last_utm_timestamp), utm_sources[indexOf(utm_timestamps, last_utm_timestamp)], '') AS fallback_source
FROM
(
SELECT
events.person_id,
arrayFilter(x -> greater(x, 0), groupArray(if(equals(events.event, 'purchase'), toUnixTimestamp(events.timestamp), 0))) AS conversion_timestamps,
arrayFilter(x -> greater(x, 0), groupArray(if(equals(events.event, 'purchase'), toFloat(1), 0))) AS conversion_math_values,
arrayFilter(x -> notEmpty(toString(x)), groupArray(if(equals(events.event, 'purchase'), toString(ifNull(events.properties.utm_campaign, '')), ''))) AS conversion_campaigns,
arrayFilter(x -> notEmpty(toString(x)), groupArray(if(equals(events.event, 'purchase'), toString(ifNull(events.properties.utm_source, '')), ''))) AS conversion_sources,
arrayFilter(x -> greater(x, 0), groupArray(if(and(equals(events.event, '$pageview'), notEmpty(toString(ifNull(events.properties.utm_campaign, ''))), notEmpty(toString(ifNull(events.properties.utm_source, '')))), toUnixTimestamp(events.timestamp), 0))) AS utm_timestamps,
arrayFilter(x -> notEmpty(x), groupArray(if(and(equals(events.event, '$pageview'), notEmpty(toString(ifNull(events.properties.utm_campaign, ''))), notEmpty(toString(ifNull(events.properties.utm_source, '')))), toString(ifNull(events.properties.utm_campaign, '')), ''))) AS utm_campaigns,
arrayFilter(x -> notEmpty(x), groupArray(if(and(equals(events.event, '$pageview'), notEmpty(toString(ifNull(events.properties.utm_campaign, ''))), notEmpty(toString(ifNull(events.properties.utm_source, '')))), toString(ifNull(events.properties.utm_source, '')), ''))) AS utm_sources
FROM
events
WHERE
or(and(equals(events.event, 'purchase'), greaterOrEquals(events.timestamp, toDateTime('2023-01-01')), less(events.timestamp, toDateTime('2023-02-01'))), and(equals(events.event, '$pageview'), notEmpty(toString(ifNull(events.properties.utm_campaign, ''))), notEmpty(toString(ifNull(events.properties.utm_source, ''))), greaterOrEquals(events.timestamp, minus(toDateTime('2023-01-01'), toIntervalSecond(7776000)))))
GROUP BY
events.person_id
HAVING
greater(length(conversion_timestamps), 0))
ARRAY JOIN arrayEnumerate(conversion_timestamps) AS i)) AS attributed_conversions
GROUP BY
campaign,
source
UNION ALL
SELECT
if(notEmpty(campaign_name), campaign_name, 'organic') AS campaign,
if(in(lower(if(notEmpty(source_name), source_name, 'organic')), ['facebook', 'instagram', 'messenger', 'fb', 'whatsapp', 'audience_network', 'facebook_marketplace', 'threads']), 'meta', if(in(lower(if(notEmpty(source_name), source_name, 'organic')), ['li']), 'linkedin', if(in(lower(if(notEmpty(source_name), source_name, 'organic')), ['youtube', 'display', 'gmail', 'google_maps', 'google_play', 'google_discover', 'admob', 'waze']), 'google', if(notEmpty(source_name), source_name, 'organic')))) AS source,
0 AS conversion_0,
count() AS conversion_1,
0 AS conversion_2
source,
sum(conversion_0) AS conversion_0
FROM
(
SELECT
person_id,
if(notEmpty(conversion_campaign), conversion_campaign, if(notEmpty(fallback_campaign), fallback_campaign, '')) AS campaign_name,
if(notEmpty(conversion_source), conversion_source, if(notEmpty(fallback_source), fallback_source, '')) AS source_name,
1 AS conversion_value
if(notEmpty(campaign_name), campaign_name, 'organic') AS campaign,
if(in(lower(if(notEmpty(source_name), source_name, 'organic')), ['facebook', 'instagram', 'messenger', 'fb', 'whatsapp', 'audience_network', 'facebook_marketplace', 'threads']), 'meta', if(in(lower(if(notEmpty(source_name), source_name, 'organic')), ['li']), 'linkedin', if(in(lower(if(notEmpty(source_name), source_name, 'organic')), ['youtube', 'display', 'gmail', 'google_maps', 'google_play', 'google_discover', 'admob', 'waze']), 'google', if(notEmpty(source_name), source_name, 'organic')))) AS source,
count() AS conversion_0
FROM
(
SELECT
person_id,
conversion_timestamps[i] AS conversion_time,
conversion_math_values[i] AS conversion_math_value,
conversion_campaigns[i] AS conversion_campaign,
conversion_sources[i] AS conversion_source,
arrayMax(arrayFilter(x -> and(lessOrEquals(x, conversion_timestamps[i]), greaterOrEquals(x, minus(conversion_timestamps[i], 7776000))), utm_timestamps)) AS last_utm_timestamp,
if(isNotNull(last_utm_timestamp), utm_campaigns[indexOf(utm_timestamps, last_utm_timestamp)], '') AS fallback_campaign,
if(isNotNull(last_utm_timestamp), utm_sources[indexOf(utm_timestamps, last_utm_timestamp)], '') AS fallback_source
if(notEmpty(conversion_campaign), conversion_campaign, if(notEmpty(fallback_campaign), fallback_campaign, '')) AS campaign_name,
if(notEmpty(conversion_source), conversion_source, if(notEmpty(fallback_source), fallback_source, '')) AS source_name,
1 AS conversion_value
FROM
(
SELECT
events.person_id,
arrayFilter(x -> greater(x, 0), groupArray(if(equals(events.event, 'sign_up'), toUnixTimestamp(events.timestamp), 0))) AS conversion_timestamps,
arrayFilter(x -> greater(x, 0), groupArray(if(equals(events.event, 'sign_up'), toFloat(1), 0))) AS conversion_math_values,
arrayFilter(x -> notEmpty(toString(x)), groupArray(if(equals(events.event, 'sign_up'), toString(ifNull(events.properties.utm_campaign, '')), ''))) AS conversion_campaigns,
arrayFilter(x -> notEmpty(toString(x)), groupArray(if(equals(events.event, 'sign_up'), toString(ifNull(events.properties.utm_source, '')), ''))) AS conversion_sources,
arrayFilter(x -> greater(x, 0), groupArray(if(and(equals(events.event, '$pageview'), notEmpty(toString(ifNull(events.properties.utm_campaign, ''))), notEmpty(toString(ifNull(events.properties.utm_source, '')))), toUnixTimestamp(events.timestamp), 0))) AS utm_timestamps,
arrayFilter(x -> notEmpty(x), groupArray(if(and(equals(events.event, '$pageview'), notEmpty(toString(ifNull(events.properties.utm_campaign, ''))), notEmpty(toString(ifNull(events.properties.utm_source, '')))), toString(ifNull(events.properties.utm_campaign, '')), ''))) AS utm_campaigns,
arrayFilter(x -> notEmpty(x), groupArray(if(and(equals(events.event, '$pageview'), notEmpty(toString(ifNull(events.properties.utm_campaign, ''))), notEmpty(toString(ifNull(events.properties.utm_source, '')))), toString(ifNull(events.properties.utm_source, '')), ''))) AS utm_sources
person_id,
conversion_timestamps[i] AS conversion_time,
conversion_math_values[i] AS conversion_math_value,
conversion_campaigns[i] AS conversion_campaign,
conversion_sources[i] AS conversion_source,
arrayMax(arrayFilter(x -> and(lessOrEquals(x, conversion_timestamps[i]), greaterOrEquals(x, minus(conversion_timestamps[i], 7776000))), utm_timestamps)) AS last_utm_timestamp,
if(isNotNull(last_utm_timestamp), utm_campaigns[indexOf(utm_timestamps, last_utm_timestamp)], '') AS fallback_campaign,
if(isNotNull(last_utm_timestamp), utm_sources[indexOf(utm_timestamps, last_utm_timestamp)], '') AS fallback_source
FROM
events
(
SELECT
events.person_id,
arrayFilter(x -> greater(x, 0), groupArray(if(equals(events.event, 'purchase'), toUnixTimestamp(events.timestamp), 0))) AS conversion_timestamps,
arrayFilter(x -> greater(x, 0), groupArray(if(equals(events.event, 'purchase'), toFloat(1), 0))) AS conversion_math_values,
arrayFilter(x -> notEmpty(toString(x)), groupArray(if(equals(events.event, 'purchase'), toString(ifNull(events.properties.utm_campaign, '')), ''))) AS conversion_campaigns,
arrayFilter(x -> notEmpty(toString(x)), groupArray(if(equals(events.event, 'purchase'), toString(ifNull(events.properties.utm_source, '')), ''))) AS conversion_sources,
arrayFilter(x -> greater(x, 0), groupArray(if(and(equals(events.event, '$pageview'), notEmpty(toString(ifNull(events.properties.utm_campaign, ''))), notEmpty(toString(ifNull(events.properties.utm_source, '')))), toUnixTimestamp(events.timestamp), 0))) AS utm_timestamps,
arrayFilter(x -> notEmpty(x), groupArray(if(and(equals(events.event, '$pageview'), notEmpty(toString(ifNull(events.properties.utm_campaign, ''))), notEmpty(toString(ifNull(events.properties.utm_source, '')))), toString(ifNull(events.properties.utm_campaign, '')), ''))) AS utm_campaigns,
arrayFilter(x -> notEmpty(x), groupArray(if(and(equals(events.event, '$pageview'), notEmpty(toString(ifNull(events.properties.utm_campaign, ''))), notEmpty(toString(ifNull(events.properties.utm_source, '')))), toString(ifNull(events.properties.utm_source, '')), ''))) AS utm_sources
FROM
events
WHERE
or(and(equals(events.event, 'sign_up'), greaterOrEquals(events.timestamp, toDateTime('2023-01-01')), less(events.timestamp, toDateTime('2023-02-01'))), and(equals(events.event, '$pageview'), notEmpty(toString(ifNull(events.properties.utm_campaign, ''))), notEmpty(toString(ifNull(events.properties.utm_source, ''))), greaterOrEquals(events.timestamp, minus(toDateTime('2023-01-01'), toIntervalSecond(7776000)))))
or(and(equals(events.event, 'purchase'), greaterOrEquals(events.timestamp, toDateTime('2023-01-01')), less(events.timestamp, toDateTime('2023-02-01'))), and(equals(events.event, '$pageview'), notEmpty(toString(ifNull(events.properties.utm_campaign, ''))), notEmpty(toString(ifNull(events.properties.utm_source, ''))), greaterOrEquals(events.timestamp, minus(toDateTime('2023-01-01'), toIntervalSecond(7776000)))))
GROUP BY
events.person_id
events.person_id
HAVING
greater(length(conversion_timestamps), 0))
ARRAY JOIN arrayEnumerate(conversion_timestamps) AS i)) AS attributed_conversions
GROUP BY
campaign,
source
UNION ALL
SELECT
if(notEmpty(campaign_name), campaign_name, 'organic') AS campaign,
if(in(lower(if(notEmpty(source_name), source_name, 'organic')), ['facebook', 'instagram', 'messenger', 'fb', 'whatsapp', 'audience_network', 'facebook_marketplace', 'threads']), 'meta', if(in(lower(if(notEmpty(source_name), source_name, 'organic')), ['li']), 'linkedin', if(in(lower(if(notEmpty(source_name), source_name, 'organic')), ['youtube', 'display', 'gmail', 'google_maps', 'google_play', 'google_discover', 'admob', 'waze']), 'google', if(notEmpty(source_name), source_name, 'organic')))) AS source,
0 AS conversion_0,
0 AS conversion_1,
count() AS conversion_2
FROM
(
SELECT
person_id,
if(notEmpty(conversion_campaign), conversion_campaign, if(notEmpty(fallback_campaign), fallback_campaign, '')) AS campaign_name,
if(notEmpty(conversion_source), conversion_source, if(notEmpty(fallback_source), fallback_source, '')) AS source_name,
1 AS conversion_value
greater(length(conversion_timestamps), 0))
ARRAY JOIN arrayEnumerate(conversion_timestamps) AS i)) AS attributed_conversions
FROM
(
SELECT
person_id,
conversion_timestamps[i] AS conversion_time,
conversion_math_values[i] AS conversion_math_value,
conversion_campaigns[i] AS conversion_campaign,
conversion_sources[i] AS conversion_source,
arrayMax(arrayFilter(x -> and(lessOrEquals(x, conversion_timestamps[i]), greaterOrEquals(x, minus(conversion_timestamps[i], 7776000))), utm_timestamps)) AS last_utm_timestamp,
if(isNotNull(last_utm_timestamp), utm_campaigns[indexOf(utm_timestamps, last_utm_timestamp)], '') AS fallback_campaign,
if(isNotNull(last_utm_timestamp), utm_sources[indexOf(utm_timestamps, last_utm_timestamp)], '') AS fallback_source
FROM
(
SELECT
events.person_id,
arrayFilter(x -> greater(x, 0), groupArray(if(equals(events.event, 'login'), toUnixTimestamp(events.timestamp), 0))) AS conversion_timestamps,
arrayFilter(x -> greater(x, 0), groupArray(if(equals(events.event, 'login'), toFloat(1), 0))) AS conversion_math_values,
arrayFilter(x -> notEmpty(toString(x)), groupArray(if(equals(events.event, 'login'), toString(ifNull(events.properties.utm_campaign, '')), ''))) AS conversion_campaigns,
arrayFilter(x -> notEmpty(toString(x)), groupArray(if(equals(events.event, 'login'), toString(ifNull(events.properties.utm_source, '')), ''))) AS conversion_sources,
arrayFilter(x -> greater(x, 0), groupArray(if(and(equals(events.event, '$pageview'), notEmpty(toString(ifNull(events.properties.utm_campaign, ''))), notEmpty(toString(ifNull(events.properties.utm_source, '')))), toUnixTimestamp(events.timestamp), 0))) AS utm_timestamps,
arrayFilter(x -> notEmpty(x), groupArray(if(and(equals(events.event, '$pageview'), notEmpty(toString(ifNull(events.properties.utm_campaign, ''))), notEmpty(toString(ifNull(events.properties.utm_source, '')))), toString(ifNull(events.properties.utm_campaign, '')), ''))) AS utm_campaigns,
arrayFilter(x -> notEmpty(x), groupArray(if(and(equals(events.event, '$pageview'), notEmpty(toString(ifNull(events.properties.utm_campaign, ''))), notEmpty(toString(ifNull(events.properties.utm_source, '')))), toString(ifNull(events.properties.utm_source, '')), ''))) AS utm_sources
FROM
events
WHERE
or(and(equals(events.event, 'login'), greaterOrEquals(events.timestamp, toDateTime('2023-01-01')), less(events.timestamp, toDateTime('2023-02-01'))), and(equals(events.event, '$pageview'), notEmpty(toString(ifNull(events.properties.utm_campaign, ''))), notEmpty(toString(ifNull(events.properties.utm_source, ''))), greaterOrEquals(events.timestamp, minus(toDateTime('2023-01-01'), toIntervalSecond(7776000)))))
GROUP BY
events.person_id
HAVING
greater(length(conversion_timestamps), 0))
ARRAY JOIN arrayEnumerate(conversion_timestamps) AS i)) AS attributed_conversions
campaign,
source) AS conv
GROUP BY
campaign,
source)
LIMIT 100
'''
# ---
# name: TestConversionGoalsAggregator.test_campaign_name_mapping_no_mappings
'''
SELECT
campaign AS campaign,
source,
conversion_0
FROM
(
SELECT
campaign,
source,
sum(conversion_0) AS conversion_0
FROM
(
SELECT
if(notEmpty(campaign_name), campaign_name, 'organic') AS campaign,
if(in(lower(if(notEmpty(source_name), source_name, 'organic')), ['facebook', 'instagram', 'messenger', 'fb', 'whatsapp', 'audience_network', 'facebook_marketplace', 'threads']), 'meta', if(in(lower(if(notEmpty(source_name), source_name, 'organic')), ['li']), 'linkedin', if(in(lower(if(notEmpty(source_name), source_name, 'organic')), ['youtube', 'display', 'gmail', 'google_maps', 'google_play', 'google_discover', 'admob', 'waze']), 'google', if(notEmpty(source_name), source_name, 'organic')))) AS source,
count() AS conversion_0
FROM
(
SELECT
person_id,
if(notEmpty(conversion_campaign), conversion_campaign, if(notEmpty(fallback_campaign), fallback_campaign, '')) AS campaign_name,
if(notEmpty(conversion_source), conversion_source, if(notEmpty(fallback_source), fallback_source, '')) AS source_name,
1 AS conversion_value
FROM
(
SELECT
person_id,
conversion_timestamps[i] AS conversion_time,
conversion_math_values[i] AS conversion_math_value,
conversion_campaigns[i] AS conversion_campaign,
conversion_sources[i] AS conversion_source,
arrayMax(arrayFilter(x -> and(lessOrEquals(x, conversion_timestamps[i]), greaterOrEquals(x, minus(conversion_timestamps[i], 7776000))), utm_timestamps)) AS last_utm_timestamp,
if(isNotNull(last_utm_timestamp), utm_campaigns[indexOf(utm_timestamps, last_utm_timestamp)], '') AS fallback_campaign,
if(isNotNull(last_utm_timestamp), utm_sources[indexOf(utm_timestamps, last_utm_timestamp)], '') AS fallback_source
FROM
(
SELECT
events.person_id,
arrayFilter(x -> greater(x, 0), groupArray(if(equals(events.event, 'purchase'), toUnixTimestamp(events.timestamp), 0))) AS conversion_timestamps,
arrayFilter(x -> greater(x, 0), groupArray(if(equals(events.event, 'purchase'), toFloat(1), 0))) AS conversion_math_values,
arrayFilter(x -> notEmpty(toString(x)), groupArray(if(equals(events.event, 'purchase'), toString(ifNull(events.properties.utm_campaign, '')), ''))) AS conversion_campaigns,
arrayFilter(x -> notEmpty(toString(x)), groupArray(if(equals(events.event, 'purchase'), toString(ifNull(events.properties.utm_source, '')), ''))) AS conversion_sources,
arrayFilter(x -> greater(x, 0), groupArray(if(and(equals(events.event, '$pageview'), notEmpty(toString(ifNull(events.properties.utm_campaign, ''))), notEmpty(toString(ifNull(events.properties.utm_source, '')))), toUnixTimestamp(events.timestamp), 0))) AS utm_timestamps,
arrayFilter(x -> notEmpty(x), groupArray(if(and(equals(events.event, '$pageview'), notEmpty(toString(ifNull(events.properties.utm_campaign, ''))), notEmpty(toString(ifNull(events.properties.utm_source, '')))), toString(ifNull(events.properties.utm_campaign, '')), ''))) AS utm_campaigns,
arrayFilter(x -> notEmpty(x), groupArray(if(and(equals(events.event, '$pageview'), notEmpty(toString(ifNull(events.properties.utm_campaign, ''))), notEmpty(toString(ifNull(events.properties.utm_source, '')))), toString(ifNull(events.properties.utm_source, '')), ''))) AS utm_sources
FROM
events
WHERE
or(and(equals(events.event, 'purchase'), greaterOrEquals(events.timestamp, toDateTime('2023-01-01')), less(events.timestamp, toDateTime('2023-02-01'))), and(equals(events.event, '$pageview'), notEmpty(toString(ifNull(events.properties.utm_campaign, ''))), notEmpty(toString(ifNull(events.properties.utm_source, ''))), greaterOrEquals(events.timestamp, minus(toDateTime('2023-01-01'), toIntervalSecond(7776000)))))
GROUP BY
campaign,
source
events.person_id
HAVING
greater(length(conversion_timestamps), 0))
ARRAY JOIN arrayEnumerate(conversion_timestamps) AS i)) AS attributed_conversions
GROUP BY
campaign,
source) AS conv
GROUP BY
campaign,
source)
LIMIT 100
'''
# ---
# name: TestConversionGoalsAggregator.test_campaign_name_mapping_single_source_sql
'''
SELECT
multiIf(and(in(lower(source), ['google', 'youtube', 'display', 'gmail', 'google_maps', 'google_play', 'google_discover', 'admob', 'waze']), in(lower(campaign), ['messy_campaign_1', 'messy_campaign_2', 'messy_campaign_3'])), 'Clean Campaign Name', campaign) AS campaign,
source,
conversion_0
FROM
(
SELECT
campaign,
source,
sum(conversion_0) AS conversion_0
FROM
(
SELECT
if(notEmpty(campaign_name), campaign_name, 'organic') AS campaign,
if(in(lower(if(notEmpty(source_name), source_name, 'organic')), ['facebook', 'instagram', 'messenger', 'fb', 'whatsapp', 'audience_network', 'facebook_marketplace', 'threads']), 'meta', if(in(lower(if(notEmpty(source_name), source_name, 'organic')), ['li']), 'linkedin', if(in(lower(if(notEmpty(source_name), source_name, 'organic')), ['youtube', 'display', 'gmail', 'google_maps', 'google_play', 'google_discover', 'admob', 'waze']), 'google', if(notEmpty(source_name), source_name, 'organic')))) AS source,
count() AS conversion_0
FROM
(
SELECT
person_id,
if(notEmpty(conversion_campaign), conversion_campaign, if(notEmpty(fallback_campaign), fallback_campaign, '')) AS campaign_name,
if(notEmpty(conversion_source), conversion_source, if(notEmpty(fallback_source), fallback_source, '')) AS source_name,
1 AS conversion_value
FROM
(
SELECT
person_id,
conversion_timestamps[i] AS conversion_time,
conversion_math_values[i] AS conversion_math_value,
conversion_campaigns[i] AS conversion_campaign,
conversion_sources[i] AS conversion_source,
arrayMax(arrayFilter(x -> and(lessOrEquals(x, conversion_timestamps[i]), greaterOrEquals(x, minus(conversion_timestamps[i], 7776000))), utm_timestamps)) AS last_utm_timestamp,
if(isNotNull(last_utm_timestamp), utm_campaigns[indexOf(utm_timestamps, last_utm_timestamp)], '') AS fallback_campaign,
if(isNotNull(last_utm_timestamp), utm_sources[indexOf(utm_timestamps, last_utm_timestamp)], '') AS fallback_source
FROM
(
SELECT
events.person_id,
arrayFilter(x -> greater(x, 0), groupArray(if(equals(events.event, 'conversion'), toUnixTimestamp(events.timestamp), 0))) AS conversion_timestamps,
arrayFilter(x -> greater(x, 0), groupArray(if(equals(events.event, 'conversion'), toFloat(1), 0))) AS conversion_math_values,
arrayFilter(x -> notEmpty(toString(x)), groupArray(if(equals(events.event, 'conversion'), toString(ifNull(events.properties.utm_campaign, '')), ''))) AS conversion_campaigns,
arrayFilter(x -> notEmpty(toString(x)), groupArray(if(equals(events.event, 'conversion'), toString(ifNull(events.properties.utm_source, '')), ''))) AS conversion_sources,
arrayFilter(x -> greater(x, 0), groupArray(if(and(equals(events.event, '$pageview'), notEmpty(toString(ifNull(events.properties.utm_campaign, ''))), notEmpty(toString(ifNull(events.properties.utm_source, '')))), toUnixTimestamp(events.timestamp), 0))) AS utm_timestamps,
arrayFilter(x -> notEmpty(x), groupArray(if(and(equals(events.event, '$pageview'), notEmpty(toString(ifNull(events.properties.utm_campaign, ''))), notEmpty(toString(ifNull(events.properties.utm_source, '')))), toString(ifNull(events.properties.utm_campaign, '')), ''))) AS utm_campaigns,
arrayFilter(x -> notEmpty(x), groupArray(if(and(equals(events.event, '$pageview'), notEmpty(toString(ifNull(events.properties.utm_campaign, ''))), notEmpty(toString(ifNull(events.properties.utm_source, '')))), toString(ifNull(events.properties.utm_source, '')), ''))) AS utm_sources
FROM
events
WHERE
or(and(equals(events.event, 'conversion'), greaterOrEquals(events.timestamp, toDateTime('2023-01-01')), less(events.timestamp, toDateTime('2023-02-01'))), and(equals(events.event, '$pageview'), notEmpty(toString(ifNull(events.properties.utm_campaign, ''))), notEmpty(toString(ifNull(events.properties.utm_source, ''))), greaterOrEquals(events.timestamp, minus(toDateTime('2023-01-01'), toIntervalSecond(7776000)))))
GROUP BY
events.person_id
HAVING
greater(length(conversion_timestamps), 0))
ARRAY JOIN arrayEnumerate(conversion_timestamps) AS i)) AS attributed_conversions
GROUP BY
campaign,
source) AS conv
GROUP BY
campaign,
source)
LIMIT 100
'''
# ---
# name: TestConversionGoalsAggregator.test_campaign_name_mapping_sql_generation
'''
SELECT
multiIf(and(in(lower(source), ['google', 'youtube', 'display', 'gmail', 'google_maps', 'google_play', 'google_discover', 'admob', 'waze']), in(lower(campaign), ['spring_sale_2024', 'spring-sale-2024', 'spring_sale'])), 'Spring Sale 2024', and(in(lower(source), ['google', 'youtube', 'display', 'gmail', 'google_maps', 'google_play', 'google_discover', 'admob', 'waze']), in(lower(campaign), ['bf_2024', 'blackfriday', 'bf-promo'])), 'Black Friday', and(in(lower(source), ['meta', 'facebook', 'instagram', 'messenger', 'fb', 'whatsapp', 'audience_network', 'facebook_marketplace', 'threads']), in(lower(campaign), ['spring_sale_fb', 'springsalefb'])), 'Spring Sale 2024', and(in(lower(source), ['meta', 'facebook', 'instagram', 'messenger', 'fb', 'whatsapp', 'audience_network', 'facebook_marketplace', 'threads']), in(lower(campaign), ['summer_2024', 'summer_promo'])), 'Summer Campaign', campaign) AS campaign,
source,
conversion_0
FROM
(
SELECT
campaign,
source,
sum(conversion_0) AS conversion_0
FROM
(
SELECT
if(notEmpty(campaign_name), campaign_name, 'organic') AS campaign,
if(in(lower(if(notEmpty(source_name), source_name, 'organic')), ['facebook', 'instagram', 'messenger', 'fb', 'whatsapp', 'audience_network', 'facebook_marketplace', 'threads']), 'meta', if(in(lower(if(notEmpty(source_name), source_name, 'organic')), ['li']), 'linkedin', if(in(lower(if(notEmpty(source_name), source_name, 'organic')), ['youtube', 'display', 'gmail', 'google_maps', 'google_play', 'google_discover', 'admob', 'waze']), 'google', if(notEmpty(source_name), source_name, 'organic')))) AS source,
count() AS conversion_0
FROM
(
SELECT
person_id,
if(notEmpty(conversion_campaign), conversion_campaign, if(notEmpty(fallback_campaign), fallback_campaign, '')) AS campaign_name,
if(notEmpty(conversion_source), conversion_source, if(notEmpty(fallback_source), fallback_source, '')) AS source_name,
1 AS conversion_value
FROM
(
SELECT
person_id,
conversion_timestamps[i] AS conversion_time,
conversion_math_values[i] AS conversion_math_value,
conversion_campaigns[i] AS conversion_campaign,
conversion_sources[i] AS conversion_source,
arrayMax(arrayFilter(x -> and(lessOrEquals(x, conversion_timestamps[i]), greaterOrEquals(x, minus(conversion_timestamps[i], 7776000))), utm_timestamps)) AS last_utm_timestamp,
if(isNotNull(last_utm_timestamp), utm_campaigns[indexOf(utm_timestamps, last_utm_timestamp)], '') AS fallback_campaign,
if(isNotNull(last_utm_timestamp), utm_sources[indexOf(utm_timestamps, last_utm_timestamp)], '') AS fallback_source
FROM
(
SELECT
events.person_id,
arrayFilter(x -> greater(x, 0), groupArray(if(equals(events.event, 'purchase'), toUnixTimestamp(events.timestamp), 0))) AS conversion_timestamps,
arrayFilter(x -> greater(x, 0), groupArray(if(equals(events.event, 'purchase'), toFloat(1), 0))) AS conversion_math_values,
arrayFilter(x -> notEmpty(toString(x)), groupArray(if(equals(events.event, 'purchase'), toString(ifNull(events.properties.utm_campaign, '')), ''))) AS conversion_campaigns,
arrayFilter(x -> notEmpty(toString(x)), groupArray(if(equals(events.event, 'purchase'), toString(ifNull(events.properties.utm_source, '')), ''))) AS conversion_sources,
arrayFilter(x -> greater(x, 0), groupArray(if(and(equals(events.event, '$pageview'), notEmpty(toString(ifNull(events.properties.utm_campaign, ''))), notEmpty(toString(ifNull(events.properties.utm_source, '')))), toUnixTimestamp(events.timestamp), 0))) AS utm_timestamps,
arrayFilter(x -> notEmpty(x), groupArray(if(and(equals(events.event, '$pageview'), notEmpty(toString(ifNull(events.properties.utm_campaign, ''))), notEmpty(toString(ifNull(events.properties.utm_source, '')))), toString(ifNull(events.properties.utm_campaign, '')), ''))) AS utm_campaigns,
arrayFilter(x -> notEmpty(x), groupArray(if(and(equals(events.event, '$pageview'), notEmpty(toString(ifNull(events.properties.utm_campaign, ''))), notEmpty(toString(ifNull(events.properties.utm_source, '')))), toString(ifNull(events.properties.utm_source, '')), ''))) AS utm_sources
FROM
events
WHERE
or(and(equals(events.event, 'purchase'), greaterOrEquals(events.timestamp, toDateTime('2023-01-01')), less(events.timestamp, toDateTime('2023-02-01'))), and(equals(events.event, '$pageview'), notEmpty(toString(ifNull(events.properties.utm_campaign, ''))), notEmpty(toString(ifNull(events.properties.utm_source, ''))), greaterOrEquals(events.timestamp, minus(toDateTime('2023-01-01'), toIntervalSecond(7776000)))))
GROUP BY
events.person_id
HAVING
greater(length(conversion_timestamps), 0))
ARRAY JOIN arrayEnumerate(conversion_timestamps) AS i)) AS attributed_conversions
GROUP BY
campaign,
source) AS conv
GROUP BY
campaign,
source)
LIMIT 100
'''
# ---
# name: TestConversionGoalsAggregator.test_multiple_goals_sql_snapshot
'''
SELECT
campaign AS campaign,
source,
conversion_0,
conversion_1,
conversion_2
FROM
(
SELECT
campaign,
source,
sum(conversion_0) AS conversion_0,
sum(conversion_1) AS conversion_1,
sum(conversion_2) AS conversion_2
FROM
(
SELECT
if(notEmpty(campaign_name), campaign_name, 'organic') AS campaign,
if(in(lower(if(notEmpty(source_name), source_name, 'organic')), ['facebook', 'instagram', 'messenger', 'fb', 'whatsapp', 'audience_network', 'facebook_marketplace', 'threads']), 'meta', if(in(lower(if(notEmpty(source_name), source_name, 'organic')), ['li']), 'linkedin', if(in(lower(if(notEmpty(source_name), source_name, 'organic')), ['youtube', 'display', 'gmail', 'google_maps', 'google_play', 'google_discover', 'admob', 'waze']), 'google', if(notEmpty(source_name), source_name, 'organic')))) AS source,
count() AS conversion_0,
0 AS conversion_1,
0 AS conversion_2
FROM
(
SELECT
person_id,
if(notEmpty(conversion_campaign), conversion_campaign, if(notEmpty(fallback_campaign), fallback_campaign, '')) AS campaign_name,
if(notEmpty(conversion_source), conversion_source, if(notEmpty(fallback_source), fallback_source, '')) AS source_name,
1 AS conversion_value
FROM
(
SELECT
person_id,
conversion_timestamps[i] AS conversion_time,
conversion_math_values[i] AS conversion_math_value,
conversion_campaigns[i] AS conversion_campaign,
conversion_sources[i] AS conversion_source,
arrayMax(arrayFilter(x -> and(lessOrEquals(x, conversion_timestamps[i]), greaterOrEquals(x, minus(conversion_timestamps[i], 7776000))), utm_timestamps)) AS last_utm_timestamp,
if(isNotNull(last_utm_timestamp), utm_campaigns[indexOf(utm_timestamps, last_utm_timestamp)], '') AS fallback_campaign,
if(isNotNull(last_utm_timestamp), utm_sources[indexOf(utm_timestamps, last_utm_timestamp)], '') AS fallback_source
FROM
(
SELECT
events.person_id,
arrayFilter(x -> greater(x, 0), groupArray(if(equals(events.event, 'purchase'), toUnixTimestamp(events.timestamp), 0))) AS conversion_timestamps,
arrayFilter(x -> greater(x, 0), groupArray(if(equals(events.event, 'purchase'), toFloat(1), 0))) AS conversion_math_values,
arrayFilter(x -> notEmpty(toString(x)), groupArray(if(equals(events.event, 'purchase'), toString(ifNull(events.properties.utm_campaign, '')), ''))) AS conversion_campaigns,
arrayFilter(x -> notEmpty(toString(x)), groupArray(if(equals(events.event, 'purchase'), toString(ifNull(events.properties.utm_source, '')), ''))) AS conversion_sources,
arrayFilter(x -> greater(x, 0), groupArray(if(and(equals(events.event, '$pageview'), notEmpty(toString(ifNull(events.properties.utm_campaign, ''))), notEmpty(toString(ifNull(events.properties.utm_source, '')))), toUnixTimestamp(events.timestamp), 0))) AS utm_timestamps,
arrayFilter(x -> notEmpty(x), groupArray(if(and(equals(events.event, '$pageview'), notEmpty(toString(ifNull(events.properties.utm_campaign, ''))), notEmpty(toString(ifNull(events.properties.utm_source, '')))), toString(ifNull(events.properties.utm_campaign, '')), ''))) AS utm_campaigns,
arrayFilter(x -> notEmpty(x), groupArray(if(and(equals(events.event, '$pageview'), notEmpty(toString(ifNull(events.properties.utm_campaign, ''))), notEmpty(toString(ifNull(events.properties.utm_source, '')))), toString(ifNull(events.properties.utm_source, '')), ''))) AS utm_sources
FROM
events
WHERE
or(and(equals(events.event, 'purchase'), greaterOrEquals(events.timestamp, toDateTime('2023-01-01')), less(events.timestamp, toDateTime('2023-02-01'))), and(equals(events.event, '$pageview'), notEmpty(toString(ifNull(events.properties.utm_campaign, ''))), notEmpty(toString(ifNull(events.properties.utm_source, ''))), greaterOrEquals(events.timestamp, minus(toDateTime('2023-01-01'), toIntervalSecond(7776000)))))
GROUP BY
events.person_id
HAVING
greater(length(conversion_timestamps), 0))
ARRAY JOIN arrayEnumerate(conversion_timestamps) AS i)) AS attributed_conversions
GROUP BY
campaign,
source
UNION ALL
SELECT
if(notEmpty(campaign_name), campaign_name, 'organic') AS campaign,
if(in(lower(if(notEmpty(source_name), source_name, 'organic')), ['facebook', 'instagram', 'messenger', 'fb', 'whatsapp', 'audience_network', 'facebook_marketplace', 'threads']), 'meta', if(in(lower(if(notEmpty(source_name), source_name, 'organic')), ['li']), 'linkedin', if(in(lower(if(notEmpty(source_name), source_name, 'organic')), ['youtube', 'display', 'gmail', 'google_maps', 'google_play', 'google_discover', 'admob', 'waze']), 'google', if(notEmpty(source_name), source_name, 'organic')))) AS source,
0 AS conversion_0,
count() AS conversion_1,
0 AS conversion_2
FROM
(
SELECT
person_id,
if(notEmpty(conversion_campaign), conversion_campaign, if(notEmpty(fallback_campaign), fallback_campaign, '')) AS campaign_name,
if(notEmpty(conversion_source), conversion_source, if(notEmpty(fallback_source), fallback_source, '')) AS source_name,
1 AS conversion_value
FROM
(
SELECT
person_id,
conversion_timestamps[i] AS conversion_time,
conversion_math_values[i] AS conversion_math_value,
conversion_campaigns[i] AS conversion_campaign,
conversion_sources[i] AS conversion_source,
arrayMax(arrayFilter(x -> and(lessOrEquals(x, conversion_timestamps[i]), greaterOrEquals(x, minus(conversion_timestamps[i], 7776000))), utm_timestamps)) AS last_utm_timestamp,
if(isNotNull(last_utm_timestamp), utm_campaigns[indexOf(utm_timestamps, last_utm_timestamp)], '') AS fallback_campaign,
if(isNotNull(last_utm_timestamp), utm_sources[indexOf(utm_timestamps, last_utm_timestamp)], '') AS fallback_source
FROM
(
SELECT
events.person_id,
arrayFilter(x -> greater(x, 0), groupArray(if(equals(events.event, 'sign_up'), toUnixTimestamp(events.timestamp), 0))) AS conversion_timestamps,
arrayFilter(x -> greater(x, 0), groupArray(if(equals(events.event, 'sign_up'), toFloat(1), 0))) AS conversion_math_values,
arrayFilter(x -> notEmpty(toString(x)), groupArray(if(equals(events.event, 'sign_up'), toString(ifNull(events.properties.utm_campaign, '')), ''))) AS conversion_campaigns,
arrayFilter(x -> notEmpty(toString(x)), groupArray(if(equals(events.event, 'sign_up'), toString(ifNull(events.properties.utm_source, '')), ''))) AS conversion_sources,
arrayFilter(x -> greater(x, 0), groupArray(if(and(equals(events.event, '$pageview'), notEmpty(toString(ifNull(events.properties.utm_campaign, ''))), notEmpty(toString(ifNull(events.properties.utm_source, '')))), toUnixTimestamp(events.timestamp), 0))) AS utm_timestamps,
arrayFilter(x -> notEmpty(x), groupArray(if(and(equals(events.event, '$pageview'), notEmpty(toString(ifNull(events.properties.utm_campaign, ''))), notEmpty(toString(ifNull(events.properties.utm_source, '')))), toString(ifNull(events.properties.utm_campaign, '')), ''))) AS utm_campaigns,
arrayFilter(x -> notEmpty(x), groupArray(if(and(equals(events.event, '$pageview'), notEmpty(toString(ifNull(events.properties.utm_campaign, ''))), notEmpty(toString(ifNull(events.properties.utm_source, '')))), toString(ifNull(events.properties.utm_source, '')), ''))) AS utm_sources
FROM
events
WHERE
or(and(equals(events.event, 'sign_up'), greaterOrEquals(events.timestamp, toDateTime('2023-01-01')), less(events.timestamp, toDateTime('2023-02-01'))), and(equals(events.event, '$pageview'), notEmpty(toString(ifNull(events.properties.utm_campaign, ''))), notEmpty(toString(ifNull(events.properties.utm_source, ''))), greaterOrEquals(events.timestamp, minus(toDateTime('2023-01-01'), toIntervalSecond(7776000)))))
GROUP BY
events.person_id
HAVING
greater(length(conversion_timestamps), 0))
ARRAY JOIN arrayEnumerate(conversion_timestamps) AS i)) AS attributed_conversions
GROUP BY
campaign,
source
UNION ALL
SELECT
if(notEmpty(campaign_name), campaign_name, 'organic') AS campaign,
if(in(lower(if(notEmpty(source_name), source_name, 'organic')), ['facebook', 'instagram', 'messenger', 'fb', 'whatsapp', 'audience_network', 'facebook_marketplace', 'threads']), 'meta', if(in(lower(if(notEmpty(source_name), source_name, 'organic')), ['li']), 'linkedin', if(in(lower(if(notEmpty(source_name), source_name, 'organic')), ['youtube', 'display', 'gmail', 'google_maps', 'google_play', 'google_discover', 'admob', 'waze']), 'google', if(notEmpty(source_name), source_name, 'organic')))) AS source,
0 AS conversion_0,
0 AS conversion_1,
count() AS conversion_2
FROM
(
SELECT
person_id,
if(notEmpty(conversion_campaign), conversion_campaign, if(notEmpty(fallback_campaign), fallback_campaign, '')) AS campaign_name,
if(notEmpty(conversion_source), conversion_source, if(notEmpty(fallback_source), fallback_source, '')) AS source_name,
1 AS conversion_value
FROM
(
SELECT
person_id,
conversion_timestamps[i] AS conversion_time,
conversion_math_values[i] AS conversion_math_value,
conversion_campaigns[i] AS conversion_campaign,
conversion_sources[i] AS conversion_source,
arrayMax(arrayFilter(x -> and(lessOrEquals(x, conversion_timestamps[i]), greaterOrEquals(x, minus(conversion_timestamps[i], 7776000))), utm_timestamps)) AS last_utm_timestamp,
if(isNotNull(last_utm_timestamp), utm_campaigns[indexOf(utm_timestamps, last_utm_timestamp)], '') AS fallback_campaign,
if(isNotNull(last_utm_timestamp), utm_sources[indexOf(utm_timestamps, last_utm_timestamp)], '') AS fallback_source
FROM
(
SELECT
events.person_id,
arrayFilter(x -> greater(x, 0), groupArray(if(equals(events.event, 'login'), toUnixTimestamp(events.timestamp), 0))) AS conversion_timestamps,
arrayFilter(x -> greater(x, 0), groupArray(if(equals(events.event, 'login'), toFloat(1), 0))) AS conversion_math_values,
arrayFilter(x -> notEmpty(toString(x)), groupArray(if(equals(events.event, 'login'), toString(ifNull(events.properties.utm_campaign, '')), ''))) AS conversion_campaigns,
arrayFilter(x -> notEmpty(toString(x)), groupArray(if(equals(events.event, 'login'), toString(ifNull(events.properties.utm_source, '')), ''))) AS conversion_sources,
arrayFilter(x -> greater(x, 0), groupArray(if(and(equals(events.event, '$pageview'), notEmpty(toString(ifNull(events.properties.utm_campaign, ''))), notEmpty(toString(ifNull(events.properties.utm_source, '')))), toUnixTimestamp(events.timestamp), 0))) AS utm_timestamps,
arrayFilter(x -> notEmpty(x), groupArray(if(and(equals(events.event, '$pageview'), notEmpty(toString(ifNull(events.properties.utm_campaign, ''))), notEmpty(toString(ifNull(events.properties.utm_source, '')))), toString(ifNull(events.properties.utm_campaign, '')), ''))) AS utm_campaigns,
arrayFilter(x -> notEmpty(x), groupArray(if(and(equals(events.event, '$pageview'), notEmpty(toString(ifNull(events.properties.utm_campaign, ''))), notEmpty(toString(ifNull(events.properties.utm_source, '')))), toString(ifNull(events.properties.utm_source, '')), ''))) AS utm_sources
FROM
events
WHERE
or(and(equals(events.event, 'login'), greaterOrEquals(events.timestamp, toDateTime('2023-01-01')), less(events.timestamp, toDateTime('2023-02-01'))), and(equals(events.event, '$pageview'), notEmpty(toString(ifNull(events.properties.utm_campaign, ''))), notEmpty(toString(ifNull(events.properties.utm_source, ''))), greaterOrEquals(events.timestamp, minus(toDateTime('2023-01-01'), toIntervalSecond(7776000)))))
GROUP BY
events.person_id
HAVING
greater(length(conversion_timestamps), 0))
ARRAY JOIN arrayEnumerate(conversion_timestamps) AS i)) AS attributed_conversions
GROUP BY
campaign,
source) AS conv
GROUP BY
campaign,
source)
LIMIT 100
'''
@@ -192,128 +513,136 @@
'''
SELECT
campaign,
campaign AS campaign,
source,
sum(conversion_0) AS conversion_0,
sum(conversion_1) AS conversion_1
conversion_0,
conversion_1
FROM
(
SELECT
if(notEmpty(campaign_name), campaign_name, 'organic') AS campaign,
if(in(lower(if(notEmpty(source_name), source_name, 'organic')), ['facebook', 'instagram', 'messenger', 'fb', 'whatsapp', 'audience_network', 'facebook_marketplace', 'threads']), 'meta', if(in(lower(if(notEmpty(source_name), source_name, 'organic')), ['li']), 'linkedin', if(in(lower(if(notEmpty(source_name), source_name, 'organic')), ['youtube', 'display', 'gmail', 'google_maps', 'google_play', 'google_discover', 'admob', 'waze']), 'google', if(notEmpty(source_name), source_name, 'organic')))) AS source,
count() AS conversion_0,
0 AS conversion_1
FROM
(
SELECT
person_id,
if(notEmpty(conversion_campaign), conversion_campaign, if(notEmpty(fallback_campaign), fallback_campaign, '')) AS campaign_name,
if(notEmpty(conversion_source), conversion_source, if(notEmpty(fallback_source), fallback_source, '')) AS source_name,
1 AS conversion_value
FROM
(
SELECT
person_id,
conversion_timestamps[i] AS conversion_time,
conversion_math_values[i] AS conversion_math_value,
conversion_campaigns[i] AS conversion_campaign,
conversion_sources[i] AS conversion_source,
arrayMax(arrayFilter(x -> and(lessOrEquals(x, conversion_timestamps[i]), greaterOrEquals(x, minus(conversion_timestamps[i], 7776000))), utm_timestamps)) AS last_utm_timestamp,
if(isNotNull(last_utm_timestamp), utm_campaigns[indexOf(utm_timestamps, last_utm_timestamp)], '') AS fallback_campaign,
if(isNotNull(last_utm_timestamp), utm_sources[indexOf(utm_timestamps, last_utm_timestamp)], '') AS fallback_source
FROM
(
SELECT
events.person_id,
arrayFilter(x -> greater(x, 0), groupArray(if(equals(events.event, 'sign_up'), toUnixTimestamp(events.timestamp), 0))) AS conversion_timestamps,
arrayFilter(x -> greater(x, 0), groupArray(if(equals(events.event, 'sign_up'), toFloat(1), 0))) AS conversion_math_values,
arrayFilter(x -> notEmpty(toString(x)), groupArray(if(equals(events.event, 'sign_up'), toString(ifNull(events.properties.utm_campaign, '')), ''))) AS conversion_campaigns,
arrayFilter(x -> notEmpty(toString(x)), groupArray(if(equals(events.event, 'sign_up'), toString(ifNull(events.properties.utm_source, '')), ''))) AS conversion_sources,
arrayFilter(x -> greater(x, 0), groupArray(if(and(equals(events.event, '$pageview'), notEmpty(toString(ifNull(events.properties.utm_campaign, ''))), notEmpty(toString(ifNull(events.properties.utm_source, '')))), toUnixTimestamp(events.timestamp), 0))) AS utm_timestamps,
arrayFilter(x -> notEmpty(x), groupArray(if(and(equals(events.event, '$pageview'), notEmpty(toString(ifNull(events.properties.utm_campaign, ''))), notEmpty(toString(ifNull(events.properties.utm_source, '')))), toString(ifNull(events.properties.utm_campaign, '')), ''))) AS utm_campaigns,
arrayFilter(x -> notEmpty(x), groupArray(if(and(equals(events.event, '$pageview'), notEmpty(toString(ifNull(events.properties.utm_campaign, ''))), notEmpty(toString(ifNull(events.properties.utm_source, '')))), toString(ifNull(events.properties.utm_source, '')), ''))) AS utm_sources
FROM
events
WHERE
or(and(equals(events.event, 'sign_up'), greaterOrEquals(events.timestamp, toDateTime('2023-01-01')), less(events.timestamp, toDateTime('2023-02-01'))), and(equals(events.event, '$pageview'), notEmpty(toString(ifNull(events.properties.utm_campaign, ''))), notEmpty(toString(ifNull(events.properties.utm_source, ''))), greaterOrEquals(events.timestamp, minus(toDateTime('2023-01-01'), toIntervalSecond(7776000)))))
GROUP BY
events.person_id
HAVING
greater(length(conversion_timestamps), 0))
ARRAY JOIN arrayEnumerate(conversion_timestamps) AS i)) AS attributed_conversions
GROUP BY
campaign,
source
UNION ALL
SELECT
if(notEmpty(campaign_name), campaign_name, 'organic') AS campaign,
if(in(lower(if(notEmpty(source_name), source_name, 'organic')), ['facebook', 'instagram', 'messenger', 'fb', 'whatsapp', 'audience_network', 'facebook_marketplace', 'threads']), 'meta', if(in(lower(if(notEmpty(source_name), source_name, 'organic')), ['li']), 'linkedin', if(in(lower(if(notEmpty(source_name), source_name, 'organic')), ['youtube', 'display', 'gmail', 'google_maps', 'google_play', 'google_discover', 'admob', 'waze']), 'google', if(notEmpty(source_name), source_name, 'organic')))) AS source,
0 AS conversion_0,
count() AS conversion_1
source,
sum(conversion_0) AS conversion_0,
sum(conversion_1) AS conversion_1
FROM
(
SELECT
person_id,
if(notEmpty(conversion_campaign), conversion_campaign, if(notEmpty(fallback_campaign), fallback_campaign, '')) AS campaign_name,
if(notEmpty(conversion_source), conversion_source, if(notEmpty(fallback_source), fallback_source, '')) AS source_name,
1 AS conversion_value
if(notEmpty(campaign_name), campaign_name, 'organic') AS campaign,
if(in(lower(if(notEmpty(source_name), source_name, 'organic')), ['facebook', 'instagram', 'messenger', 'fb', 'whatsapp', 'audience_network', 'facebook_marketplace', 'threads']), 'meta', if(in(lower(if(notEmpty(source_name), source_name, 'organic')), ['li']), 'linkedin', if(in(lower(if(notEmpty(source_name), source_name, 'organic')), ['youtube', 'display', 'gmail', 'google_maps', 'google_play', 'google_discover', 'admob', 'waze']), 'google', if(notEmpty(source_name), source_name, 'organic')))) AS source,
count() AS conversion_0,
0 AS conversion_1
FROM
(
SELECT
person_id,
conversion_timestamps[i] AS conversion_time,
conversion_math_values[i] AS conversion_math_value,
conversion_campaigns[i] AS conversion_campaign,
conversion_sources[i] AS conversion_source,
arrayMax(arrayFilter(x -> and(lessOrEquals(x, conversion_timestamps[i]), greaterOrEquals(x, minus(conversion_timestamps[i], 7776000))), utm_timestamps)) AS last_utm_timestamp,
if(isNotNull(last_utm_timestamp), utm_campaigns[indexOf(utm_timestamps, last_utm_timestamp)], '') AS fallback_campaign,
if(isNotNull(last_utm_timestamp), utm_sources[indexOf(utm_timestamps, last_utm_timestamp)], '') AS fallback_source
if(notEmpty(conversion_campaign), conversion_campaign, if(notEmpty(fallback_campaign), fallback_campaign, '')) AS campaign_name,
if(notEmpty(conversion_source), conversion_source, if(notEmpty(fallback_source), fallback_source, '')) AS source_name,
1 AS conversion_value
FROM
(
SELECT
events.person_id,
arrayFilter(x -> greater(x, 0), groupArray(if(equals(events.event, 'purchase'), toUnixTimestamp(events.timestamp), 0))) AS conversion_timestamps,
arrayFilter(x -> greater(x, 0), groupArray(if(equals(events.event, 'purchase'), toFloat(1), 0))) AS conversion_math_values,
arrayFilter(x -> notEmpty(toString(x)), groupArray(if(equals(events.event, 'purchase'), toString(ifNull(events.properties.utm_campaign, '')), ''))) AS conversion_campaigns,
arrayFilter(x -> notEmpty(toString(x)), groupArray(if(equals(events.event, 'purchase'), toString(ifNull(events.properties.utm_source, '')), ''))) AS conversion_sources,
arrayFilter(x -> greater(x, 0), groupArray(if(and(equals(events.event, '$pageview'), notEmpty(toString(ifNull(events.properties.utm_campaign, ''))), notEmpty(toString(ifNull(events.properties.utm_source, '')))), toUnixTimestamp(events.timestamp), 0))) AS utm_timestamps,
arrayFilter(x -> notEmpty(x), groupArray(if(and(equals(events.event, '$pageview'), notEmpty(toString(ifNull(events.properties.utm_campaign, ''))), notEmpty(toString(ifNull(events.properties.utm_source, '')))), toString(ifNull(events.properties.utm_campaign, '')), ''))) AS utm_campaigns,
arrayFilter(x -> notEmpty(x), groupArray(if(and(equals(events.event, '$pageview'), notEmpty(toString(ifNull(events.properties.utm_campaign, ''))), notEmpty(toString(ifNull(events.properties.utm_source, '')))), toString(ifNull(events.properties.utm_source, '')), ''))) AS utm_sources
person_id,
conversion_timestamps[i] AS conversion_time,
conversion_math_values[i] AS conversion_math_value,
conversion_campaigns[i] AS conversion_campaign,
conversion_sources[i] AS conversion_source,
arrayMax(arrayFilter(x -> and(lessOrEquals(x, conversion_timestamps[i]), greaterOrEquals(x, minus(conversion_timestamps[i], 7776000))), utm_timestamps)) AS last_utm_timestamp,
if(isNotNull(last_utm_timestamp), utm_campaigns[indexOf(utm_timestamps, last_utm_timestamp)], '') AS fallback_campaign,
if(isNotNull(last_utm_timestamp), utm_sources[indexOf(utm_timestamps, last_utm_timestamp)], '') AS fallback_source
FROM
events
(
SELECT
events.person_id,
arrayFilter(x -> greater(x, 0), groupArray(if(equals(events.event, 'sign_up'), toUnixTimestamp(events.timestamp), 0))) AS conversion_timestamps,
arrayFilter(x -> greater(x, 0), groupArray(if(equals(events.event, 'sign_up'), toFloat(1), 0))) AS conversion_math_values,
arrayFilter(x -> notEmpty(toString(x)), groupArray(if(equals(events.event, 'sign_up'), toString(ifNull(events.properties.utm_campaign, '')), ''))) AS conversion_campaigns,
arrayFilter(x -> notEmpty(toString(x)), groupArray(if(equals(events.event, 'sign_up'), toString(ifNull(events.properties.utm_source, '')), ''))) AS conversion_sources,
arrayFilter(x -> greater(x, 0), groupArray(if(and(equals(events.event, '$pageview'), notEmpty(toString(ifNull(events.properties.utm_campaign, ''))), notEmpty(toString(ifNull(events.properties.utm_source, '')))), toUnixTimestamp(events.timestamp), 0))) AS utm_timestamps,
arrayFilter(x -> notEmpty(x), groupArray(if(and(equals(events.event, '$pageview'), notEmpty(toString(ifNull(events.properties.utm_campaign, ''))), notEmpty(toString(ifNull(events.properties.utm_source, '')))), toString(ifNull(events.properties.utm_campaign, '')), ''))) AS utm_campaigns,
arrayFilter(x -> notEmpty(x), groupArray(if(and(equals(events.event, '$pageview'), notEmpty(toString(ifNull(events.properties.utm_campaign, ''))), notEmpty(toString(ifNull(events.properties.utm_source, '')))), toString(ifNull(events.properties.utm_source, '')), ''))) AS utm_sources
FROM
events
WHERE
or(and(equals(events.event, 'purchase'), greaterOrEquals(events.timestamp, toDateTime('2023-01-01')), less(events.timestamp, toDateTime('2023-02-01'))), and(equals(events.event, '$pageview'), notEmpty(toString(ifNull(events.properties.utm_campaign, ''))), notEmpty(toString(ifNull(events.properties.utm_source, ''))), greaterOrEquals(events.timestamp, minus(toDateTime('2023-01-01'), toIntervalSecond(7776000)))))
or(and(equals(events.event, 'sign_up'), greaterOrEquals(events.timestamp, toDateTime('2023-01-01')), less(events.timestamp, toDateTime('2023-02-01'))), and(equals(events.event, '$pageview'), notEmpty(toString(ifNull(events.properties.utm_campaign, ''))), notEmpty(toString(ifNull(events.properties.utm_source, ''))), greaterOrEquals(events.timestamp, minus(toDateTime('2023-01-01'), toIntervalSecond(7776000)))))
GROUP BY
events.person_id
events.person_id
HAVING
greater(length(conversion_timestamps), 0))
ARRAY JOIN arrayEnumerate(conversion_timestamps) AS i)) AS attributed_conversions
greater(length(conversion_timestamps), 0))
ARRAY JOIN arrayEnumerate(conversion_timestamps) AS i)) AS attributed_conversions
GROUP BY
campaign,
source
UNION ALL
SELECT
if(notEmpty(campaign_name), campaign_name, 'organic') AS campaign,
if(in(lower(if(notEmpty(source_name), source_name, 'organic')), ['facebook', 'instagram', 'messenger', 'fb', 'whatsapp', 'audience_network', 'facebook_marketplace', 'threads']), 'meta', if(in(lower(if(notEmpty(source_name), source_name, 'organic')), ['li']), 'linkedin', if(in(lower(if(notEmpty(source_name), source_name, 'organic')), ['youtube', 'display', 'gmail', 'google_maps', 'google_play', 'google_discover', 'admob', 'waze']), 'google', if(notEmpty(source_name), source_name, 'organic')))) AS source,
0 AS conversion_0,
count() AS conversion_1
FROM
(
SELECT
person_id,
if(notEmpty(conversion_campaign), conversion_campaign, if(notEmpty(fallback_campaign), fallback_campaign, '')) AS campaign_name,
if(notEmpty(conversion_source), conversion_source, if(notEmpty(fallback_source), fallback_source, '')) AS source_name,
1 AS conversion_value
FROM
(
SELECT
person_id,
conversion_timestamps[i] AS conversion_time,
conversion_math_values[i] AS conversion_math_value,
conversion_campaigns[i] AS conversion_campaign,
conversion_sources[i] AS conversion_source,
arrayMax(arrayFilter(x -> and(lessOrEquals(x, conversion_timestamps[i]), greaterOrEquals(x, minus(conversion_timestamps[i], 7776000))), utm_timestamps)) AS last_utm_timestamp,
if(isNotNull(last_utm_timestamp), utm_campaigns[indexOf(utm_timestamps, last_utm_timestamp)], '') AS fallback_campaign,
if(isNotNull(last_utm_timestamp), utm_sources[indexOf(utm_timestamps, last_utm_timestamp)], '') AS fallback_source
FROM
(
SELECT
events.person_id,
arrayFilter(x -> greater(x, 0), groupArray(if(equals(events.event, 'purchase'), toUnixTimestamp(events.timestamp), 0))) AS conversion_timestamps,
arrayFilter(x -> greater(x, 0), groupArray(if(equals(events.event, 'purchase'), toFloat(1), 0))) AS conversion_math_values,
arrayFilter(x -> notEmpty(toString(x)), groupArray(if(equals(events.event, 'purchase'), toString(ifNull(events.properties.utm_campaign, '')), ''))) AS conversion_campaigns,
arrayFilter(x -> notEmpty(toString(x)), groupArray(if(equals(events.event, 'purchase'), toString(ifNull(events.properties.utm_source, '')), ''))) AS conversion_sources,
arrayFilter(x -> greater(x, 0), groupArray(if(and(equals(events.event, '$pageview'), notEmpty(toString(ifNull(events.properties.utm_campaign, ''))), notEmpty(toString(ifNull(events.properties.utm_source, '')))), toUnixTimestamp(events.timestamp), 0))) AS utm_timestamps,
arrayFilter(x -> notEmpty(x), groupArray(if(and(equals(events.event, '$pageview'), notEmpty(toString(ifNull(events.properties.utm_campaign, ''))), notEmpty(toString(ifNull(events.properties.utm_source, '')))), toString(ifNull(events.properties.utm_campaign, '')), ''))) AS utm_campaigns,
arrayFilter(x -> notEmpty(x), groupArray(if(and(equals(events.event, '$pageview'), notEmpty(toString(ifNull(events.properties.utm_campaign, ''))), notEmpty(toString(ifNull(events.properties.utm_source, '')))), toString(ifNull(events.properties.utm_source, '')), ''))) AS utm_sources
FROM
events
WHERE
or(and(equals(events.event, 'purchase'), greaterOrEquals(events.timestamp, toDateTime('2023-01-01')), less(events.timestamp, toDateTime('2023-02-01'))), and(equals(events.event, '$pageview'), notEmpty(toString(ifNull(events.properties.utm_campaign, ''))), notEmpty(toString(ifNull(events.properties.utm_source, ''))), greaterOrEquals(events.timestamp, minus(toDateTime('2023-01-01'), toIntervalSecond(7776000)))))
GROUP BY
events.person_id
HAVING
greater(length(conversion_timestamps), 0))
ARRAY JOIN arrayEnumerate(conversion_timestamps) AS i)) AS attributed_conversions
GROUP BY
campaign,
source) AS conv
GROUP BY
campaign,
source)
GROUP BY
campaign,
source
LIMIT 100
'''
# ---

View File

@@ -92,69 +92,76 @@
source) AS campaign_costs
LEFT JOIN (
SELECT
campaign,
campaign AS campaign,
source,
sum(conversion_0) AS conversion_0
conversion_0
FROM
(
SELECT
if(notEmpty(campaign_name), campaign_name, 'organic') AS campaign,
if(in(lower(if(notEmpty(source_name), source_name, 'organic')), ['facebook', 'instagram', 'messenger', 'fb', 'whatsapp', 'audience_network', 'facebook_marketplace', 'threads']), 'meta', if(in(lower(if(notEmpty(source_name), source_name, 'organic')), ['li']), 'linkedin', if(in(lower(if(notEmpty(source_name), source_name, 'organic')), ['youtube', 'display', 'gmail', 'google_maps', 'google_play', 'google_discover', 'admob', 'waze']), 'google', if(notEmpty(source_name), source_name, 'organic')))) AS source,
count() AS conversion_0
campaign,
source,
sum(conversion_0) AS conversion_0
FROM
(
SELECT
person_id,
if(notEmpty(conversion_campaign), conversion_campaign, if(notEmpty(fallback_campaign), fallback_campaign, '')) AS campaign_name,
if(notEmpty(conversion_source), conversion_source, if(notEmpty(fallback_source), fallback_source, '')) AS source_name,
1 AS conversion_value
if(notEmpty(campaign_name), campaign_name, 'organic') AS campaign,
if(in(lower(if(notEmpty(source_name), source_name, 'organic')), ['facebook', 'instagram', 'messenger', 'fb', 'whatsapp', 'audience_network', 'facebook_marketplace', 'threads']), 'meta', if(in(lower(if(notEmpty(source_name), source_name, 'organic')), ['li']), 'linkedin', if(in(lower(if(notEmpty(source_name), source_name, 'organic')), ['youtube', 'display', 'gmail', 'google_maps', 'google_play', 'google_discover', 'admob', 'waze']), 'google', if(notEmpty(source_name), source_name, 'organic')))) AS source,
count() AS conversion_0
FROM
(
SELECT
person_id,
conversion_timestamps[i] AS conversion_time,
conversion_math_values[i] AS conversion_math_value,
conversion_campaigns[i] AS conversion_campaign,
conversion_sources[i] AS conversion_source,
arrayMax(arrayFilter(x -> and(lessOrEquals(x, conversion_timestamps[i]), greaterOrEquals(x, minus(conversion_timestamps[i], 7776000))), utm_timestamps)) AS last_utm_timestamp,
if(isNotNull(last_utm_timestamp), utm_campaigns[indexOf(utm_timestamps, last_utm_timestamp)], '') AS fallback_campaign,
if(isNotNull(last_utm_timestamp), utm_sources[indexOf(utm_timestamps, last_utm_timestamp)], '') AS fallback_source
if(notEmpty(conversion_campaign), conversion_campaign, if(notEmpty(fallback_campaign), fallback_campaign, '')) AS campaign_name,
if(notEmpty(conversion_source), conversion_source, if(notEmpty(fallback_source), fallback_source, '')) AS source_name,
1 AS conversion_value
FROM
(
SELECT
events.person_id,
arrayFilter(x -> greater(x, 0), groupArray(if(equals(event, 'test_event'), toUnixTimestamp(events.timestamp), 0))) AS conversion_timestamps,
arrayFilter(x -> greater(x, 0), groupArray(if(equals(event, 'test_event'), toFloat(1), 0))) AS conversion_math_values,
arrayFilter(x -> notEmpty(toString(x)), groupArray(if(equals(event, 'test_event'), toString(ifNull(events.properties.utm_campaign, '')), ''))) AS conversion_campaigns,
arrayFilter(x -> notEmpty(toString(x)), groupArray(if(equals(event, 'test_event'), toString(ifNull(events.properties.utm_source, '')), ''))) AS conversion_sources,
arrayFilter(x -> greater(x, 0), groupArray(if(and(equals(events.event, '$pageview'), notEmpty(toString(ifNull(events.properties.utm_campaign, ''))), notEmpty(toString(ifNull(events.properties.utm_source, '')))), toUnixTimestamp(events.timestamp), 0))) AS utm_timestamps,
arrayFilter(x -> notEmpty(x), groupArray(if(and(equals(events.event, '$pageview'), notEmpty(toString(ifNull(events.properties.utm_campaign, ''))), notEmpty(toString(ifNull(events.properties.utm_source, '')))), toString(ifNull(events.properties.utm_campaign, '')), ''))) AS utm_campaigns,
arrayFilter(x -> notEmpty(x), groupArray(if(and(equals(events.event, '$pageview'), notEmpty(toString(ifNull(events.properties.utm_campaign, ''))), notEmpty(toString(ifNull(events.properties.utm_source, '')))), toString(ifNull(events.properties.utm_source, '')), ''))) AS utm_sources
person_id,
conversion_timestamps[i] AS conversion_time,
conversion_math_values[i] AS conversion_math_value,
conversion_campaigns[i] AS conversion_campaign,
conversion_sources[i] AS conversion_source,
arrayMax(arrayFilter(x -> and(lessOrEquals(x, conversion_timestamps[i]), greaterOrEquals(x, minus(conversion_timestamps[i], 7776000))), utm_timestamps)) AS last_utm_timestamp,
if(isNotNull(last_utm_timestamp), utm_campaigns[indexOf(utm_timestamps, last_utm_timestamp)], '') AS fallback_campaign,
if(isNotNull(last_utm_timestamp), utm_sources[indexOf(utm_timestamps, last_utm_timestamp)], '') AS fallback_source
FROM
events
(
SELECT
events.person_id,
arrayFilter(x -> greater(x, 0), groupArray(if(equals(event, 'test_event'), toUnixTimestamp(events.timestamp), 0))) AS conversion_timestamps,
arrayFilter(x -> greater(x, 0), groupArray(if(equals(event, 'test_event'), toFloat(1), 0))) AS conversion_math_values,
arrayFilter(x -> notEmpty(toString(x)), groupArray(if(equals(event, 'test_event'), toString(ifNull(events.properties.utm_campaign, '')), ''))) AS conversion_campaigns,
arrayFilter(x -> notEmpty(toString(x)), groupArray(if(equals(event, 'test_event'), toString(ifNull(events.properties.utm_source, '')), ''))) AS conversion_sources,
arrayFilter(x -> greater(x, 0), groupArray(if(and(equals(events.event, '$pageview'), notEmpty(toString(ifNull(events.properties.utm_campaign, ''))), notEmpty(toString(ifNull(events.properties.utm_source, '')))), toUnixTimestamp(events.timestamp), 0))) AS utm_timestamps,
arrayFilter(x -> notEmpty(x), groupArray(if(and(equals(events.event, '$pageview'), notEmpty(toString(ifNull(events.properties.utm_campaign, ''))), notEmpty(toString(ifNull(events.properties.utm_source, '')))), toString(ifNull(events.properties.utm_campaign, '')), ''))) AS utm_campaigns,
arrayFilter(x -> notEmpty(x), groupArray(if(and(equals(events.event, '$pageview'), notEmpty(toString(ifNull(events.properties.utm_campaign, ''))), notEmpty(toString(ifNull(events.properties.utm_source, '')))), toString(ifNull(events.properties.utm_source, '')), ''))) AS utm_sources
FROM
events
WHERE
or(and(equals(event, 'test_event'), greaterOrEquals(events.timestamp, toDateTime('2024-01-01 00:00:00')), lessOrEquals(events.timestamp, toDateTime('2024-12-31 23:59:59'))), and(equals(events.event, '$pageview'), notEmpty(toString(ifNull(events.properties.utm_campaign, ''))), notEmpty(toString(ifNull(events.properties.utm_source, ''))), greaterOrEquals(events.timestamp, minus(toDateTime('2024-01-01 00:00:00'), toIntervalSecond(7776000))), lessOrEquals(events.timestamp, toDateTime('2024-12-31 23:59:59'))))
or(and(equals(event, 'test_event'), greaterOrEquals(events.timestamp, toDateTime('2024-01-01 00:00:00')), lessOrEquals(events.timestamp, toDateTime('2024-12-31 23:59:59'))), and(equals(events.event, '$pageview'), notEmpty(toString(ifNull(events.properties.utm_campaign, ''))), notEmpty(toString(ifNull(events.properties.utm_source, ''))), greaterOrEquals(events.timestamp, minus(toDateTime('2024-01-01 00:00:00'), toIntervalSecond(7776000))), lessOrEquals(events.timestamp, toDateTime('2024-12-31 23:59:59'))))
GROUP BY
events.person_id
events.person_id
HAVING
greater(length(conversion_timestamps), 0))
ARRAY JOIN arrayEnumerate(conversion_timestamps) AS i)) AS attributed_conversions
greater(length(conversion_timestamps), 0))
ARRAY JOIN arrayEnumerate(conversion_timestamps) AS i)) AS attributed_conversions
GROUP BY
campaign,
source) AS conv
GROUP BY
campaign,
source)
GROUP BY
campaign,
source) AS ucg ON and(equals(campaign_costs.campaign, ucg.campaign), equals(campaign_costs.source, ucg.source))
source)) AS ucg ON and(equals(campaign_costs.campaign, ucg.campaign), equals(campaign_costs.source, ucg.source))
ORDER BY
Cost DESC

View File

@@ -106,69 +106,76 @@
source) AS campaign_costs
LEFT JOIN (
SELECT
campaign,
campaign AS campaign,
source,
sum(conversion_0) AS conversion_0
conversion_0
FROM
(
SELECT
if(notEmpty(campaign_name), campaign_name, 'organic') AS campaign,
if(in(lower(if(notEmpty(source_name), source_name, 'organic')), ['facebook', 'instagram', 'messenger', 'fb', 'whatsapp', 'audience_network', 'facebook_marketplace', 'threads']), 'meta', if(in(lower(if(notEmpty(source_name), source_name, 'organic')), ['li']), 'linkedin', if(in(lower(if(notEmpty(source_name), source_name, 'organic')), ['youtube', 'display', 'gmail', 'google_maps', 'google_play', 'google_discover', 'admob', 'waze']), 'google', if(notEmpty(source_name), source_name, 'organic')))) AS source,
count() AS conversion_0
campaign,
source,
sum(conversion_0) AS conversion_0
FROM
(
SELECT
person_id,
if(notEmpty(conversion_campaign), conversion_campaign, if(notEmpty(fallback_campaign), fallback_campaign, '')) AS campaign_name,
if(notEmpty(conversion_source), conversion_source, if(notEmpty(fallback_source), fallback_source, '')) AS source_name,
1 AS conversion_value
if(notEmpty(campaign_name), campaign_name, 'organic') AS campaign,
if(in(lower(if(notEmpty(source_name), source_name, 'organic')), ['facebook', 'instagram', 'messenger', 'fb', 'whatsapp', 'audience_network', 'facebook_marketplace', 'threads']), 'meta', if(in(lower(if(notEmpty(source_name), source_name, 'organic')), ['li']), 'linkedin', if(in(lower(if(notEmpty(source_name), source_name, 'organic')), ['youtube', 'display', 'gmail', 'google_maps', 'google_play', 'google_discover', 'admob', 'waze']), 'google', if(notEmpty(source_name), source_name, 'organic')))) AS source,
count() AS conversion_0
FROM
(
SELECT
person_id,
conversion_timestamps[i] AS conversion_time,
conversion_math_values[i] AS conversion_math_value,
conversion_campaigns[i] AS conversion_campaign,
conversion_sources[i] AS conversion_source,
arrayMax(arrayFilter(x -> and(lessOrEquals(x, conversion_timestamps[i]), greaterOrEquals(x, minus(conversion_timestamps[i], 7776000))), utm_timestamps)) AS last_utm_timestamp,
if(isNotNull(last_utm_timestamp), utm_campaigns[indexOf(utm_timestamps, last_utm_timestamp)], '') AS fallback_campaign,
if(isNotNull(last_utm_timestamp), utm_sources[indexOf(utm_timestamps, last_utm_timestamp)], '') AS fallback_source
if(notEmpty(conversion_campaign), conversion_campaign, if(notEmpty(fallback_campaign), fallback_campaign, '')) AS campaign_name,
if(notEmpty(conversion_source), conversion_source, if(notEmpty(fallback_source), fallback_source, '')) AS source_name,
1 AS conversion_value
FROM
(
SELECT
events.person_id,
arrayFilter(x -> greater(x, 0), groupArray(if(equals(event, 'test_event'), toUnixTimestamp(events.timestamp), 0))) AS conversion_timestamps,
arrayFilter(x -> greater(x, 0), groupArray(if(equals(event, 'test_event'), toFloat(1), 0))) AS conversion_math_values,
arrayFilter(x -> notEmpty(toString(x)), groupArray(if(equals(event, 'test_event'), toString(ifNull(events.properties.utm_campaign, '')), ''))) AS conversion_campaigns,
arrayFilter(x -> notEmpty(toString(x)), groupArray(if(equals(event, 'test_event'), toString(ifNull(events.properties.utm_source, '')), ''))) AS conversion_sources,
arrayFilter(x -> greater(x, 0), groupArray(if(and(equals(events.event, '$pageview'), notEmpty(toString(ifNull(events.properties.utm_campaign, ''))), notEmpty(toString(ifNull(events.properties.utm_source, '')))), toUnixTimestamp(events.timestamp), 0))) AS utm_timestamps,
arrayFilter(x -> notEmpty(x), groupArray(if(and(equals(events.event, '$pageview'), notEmpty(toString(ifNull(events.properties.utm_campaign, ''))), notEmpty(toString(ifNull(events.properties.utm_source, '')))), toString(ifNull(events.properties.utm_campaign, '')), ''))) AS utm_campaigns,
arrayFilter(x -> notEmpty(x), groupArray(if(and(equals(events.event, '$pageview'), notEmpty(toString(ifNull(events.properties.utm_campaign, ''))), notEmpty(toString(ifNull(events.properties.utm_source, '')))), toString(ifNull(events.properties.utm_source, '')), ''))) AS utm_sources
person_id,
conversion_timestamps[i] AS conversion_time,
conversion_math_values[i] AS conversion_math_value,
conversion_campaigns[i] AS conversion_campaign,
conversion_sources[i] AS conversion_source,
arrayMax(arrayFilter(x -> and(lessOrEquals(x, conversion_timestamps[i]), greaterOrEquals(x, minus(conversion_timestamps[i], 7776000))), utm_timestamps)) AS last_utm_timestamp,
if(isNotNull(last_utm_timestamp), utm_campaigns[indexOf(utm_timestamps, last_utm_timestamp)], '') AS fallback_campaign,
if(isNotNull(last_utm_timestamp), utm_sources[indexOf(utm_timestamps, last_utm_timestamp)], '') AS fallback_source
FROM
events
(
SELECT
events.person_id,
arrayFilter(x -> greater(x, 0), groupArray(if(equals(event, 'test_event'), toUnixTimestamp(events.timestamp), 0))) AS conversion_timestamps,
arrayFilter(x -> greater(x, 0), groupArray(if(equals(event, 'test_event'), toFloat(1), 0))) AS conversion_math_values,
arrayFilter(x -> notEmpty(toString(x)), groupArray(if(equals(event, 'test_event'), toString(ifNull(events.properties.utm_campaign, '')), ''))) AS conversion_campaigns,
arrayFilter(x -> notEmpty(toString(x)), groupArray(if(equals(event, 'test_event'), toString(ifNull(events.properties.utm_source, '')), ''))) AS conversion_sources,
arrayFilter(x -> greater(x, 0), groupArray(if(and(equals(events.event, '$pageview'), notEmpty(toString(ifNull(events.properties.utm_campaign, ''))), notEmpty(toString(ifNull(events.properties.utm_source, '')))), toUnixTimestamp(events.timestamp), 0))) AS utm_timestamps,
arrayFilter(x -> notEmpty(x), groupArray(if(and(equals(events.event, '$pageview'), notEmpty(toString(ifNull(events.properties.utm_campaign, ''))), notEmpty(toString(ifNull(events.properties.utm_source, '')))), toString(ifNull(events.properties.utm_campaign, '')), ''))) AS utm_campaigns,
arrayFilter(x -> notEmpty(x), groupArray(if(and(equals(events.event, '$pageview'), notEmpty(toString(ifNull(events.properties.utm_campaign, ''))), notEmpty(toString(ifNull(events.properties.utm_source, '')))), toString(ifNull(events.properties.utm_source, '')), ''))) AS utm_sources
FROM
events
WHERE
or(and(equals(event, 'test_event'), greaterOrEquals(events.timestamp, toDateTime('2024-11-01 00:00:00')), lessOrEquals(events.timestamp, toDateTime('2024-12-31 23:59:59'))), and(equals(events.event, '$pageview'), notEmpty(toString(ifNull(events.properties.utm_campaign, ''))), notEmpty(toString(ifNull(events.properties.utm_source, ''))), greaterOrEquals(events.timestamp, minus(toDateTime('2024-11-01 00:00:00'), toIntervalSecond(7776000))), lessOrEquals(events.timestamp, toDateTime('2024-12-31 23:59:59'))))
or(and(equals(event, 'test_event'), greaterOrEquals(events.timestamp, toDateTime('2024-11-01 00:00:00')), lessOrEquals(events.timestamp, toDateTime('2024-12-31 23:59:59'))), and(equals(events.event, '$pageview'), notEmpty(toString(ifNull(events.properties.utm_campaign, ''))), notEmpty(toString(ifNull(events.properties.utm_source, ''))), greaterOrEquals(events.timestamp, minus(toDateTime('2024-11-01 00:00:00'), toIntervalSecond(7776000))), lessOrEquals(events.timestamp, toDateTime('2024-12-31 23:59:59'))))
GROUP BY
events.person_id
events.person_id
HAVING
greater(length(conversion_timestamps), 0))
ARRAY JOIN arrayEnumerate(conversion_timestamps) AS i)) AS attributed_conversions
greater(length(conversion_timestamps), 0))
ARRAY JOIN arrayEnumerate(conversion_timestamps) AS i)) AS attributed_conversions
GROUP BY
campaign,
source) AS conv
GROUP BY
campaign,
source)
GROUP BY
campaign,
source) AS ucg ON and(equals(campaign_costs.campaign, ucg.campaign), equals(campaign_costs.source, ucg.source))) AS current_period
source)) AS ucg ON and(equals(campaign_costs.campaign, ucg.campaign), equals(campaign_costs.source, ucg.source))) AS current_period
LEFT JOIN (
SELECT
campaign_costs.campaign AS Campaign,
@@ -213,69 +220,76 @@
source) AS campaign_costs
LEFT JOIN (
SELECT
campaign,
campaign AS campaign,
source,
sum(conversion_0) AS conversion_0
conversion_0
FROM
(
SELECT
if(notEmpty(campaign_name), campaign_name, 'organic') AS campaign,
if(in(lower(if(notEmpty(source_name), source_name, 'organic')), ['facebook', 'instagram', 'messenger', 'fb', 'whatsapp', 'audience_network', 'facebook_marketplace', 'threads']), 'meta', if(in(lower(if(notEmpty(source_name), source_name, 'organic')), ['li']), 'linkedin', if(in(lower(if(notEmpty(source_name), source_name, 'organic')), ['youtube', 'display', 'gmail', 'google_maps', 'google_play', 'google_discover', 'admob', 'waze']), 'google', if(notEmpty(source_name), source_name, 'organic')))) AS source,
count() AS conversion_0
campaign,
source,
sum(conversion_0) AS conversion_0
FROM
(
SELECT
person_id,
if(notEmpty(conversion_campaign), conversion_campaign, if(notEmpty(fallback_campaign), fallback_campaign, '')) AS campaign_name,
if(notEmpty(conversion_source), conversion_source, if(notEmpty(fallback_source), fallback_source, '')) AS source_name,
1 AS conversion_value
if(notEmpty(campaign_name), campaign_name, 'organic') AS campaign,
if(in(lower(if(notEmpty(source_name), source_name, 'organic')), ['facebook', 'instagram', 'messenger', 'fb', 'whatsapp', 'audience_network', 'facebook_marketplace', 'threads']), 'meta', if(in(lower(if(notEmpty(source_name), source_name, 'organic')), ['li']), 'linkedin', if(in(lower(if(notEmpty(source_name), source_name, 'organic')), ['youtube', 'display', 'gmail', 'google_maps', 'google_play', 'google_discover', 'admob', 'waze']), 'google', if(notEmpty(source_name), source_name, 'organic')))) AS source,
count() AS conversion_0
FROM
(
SELECT
person_id,
conversion_timestamps[i] AS conversion_time,
conversion_math_values[i] AS conversion_math_value,
conversion_campaigns[i] AS conversion_campaign,
conversion_sources[i] AS conversion_source,
arrayMax(arrayFilter(x -> and(lessOrEquals(x, conversion_timestamps[i]), greaterOrEquals(x, minus(conversion_timestamps[i], 7776000))), utm_timestamps)) AS last_utm_timestamp,
if(isNotNull(last_utm_timestamp), utm_campaigns[indexOf(utm_timestamps, last_utm_timestamp)], '') AS fallback_campaign,
if(isNotNull(last_utm_timestamp), utm_sources[indexOf(utm_timestamps, last_utm_timestamp)], '') AS fallback_source
if(notEmpty(conversion_campaign), conversion_campaign, if(notEmpty(fallback_campaign), fallback_campaign, '')) AS campaign_name,
if(notEmpty(conversion_source), conversion_source, if(notEmpty(fallback_source), fallback_source, '')) AS source_name,
1 AS conversion_value
FROM
(
SELECT
events.person_id,
arrayFilter(x -> greater(x, 0), groupArray(if(equals(event, 'test_event'), toUnixTimestamp(events.timestamp), 0))) AS conversion_timestamps,
arrayFilter(x -> greater(x, 0), groupArray(if(equals(event, 'test_event'), toFloat(1), 0))) AS conversion_math_values,
arrayFilter(x -> notEmpty(toString(x)), groupArray(if(equals(event, 'test_event'), toString(ifNull(events.properties.utm_campaign, '')), ''))) AS conversion_campaigns,
arrayFilter(x -> notEmpty(toString(x)), groupArray(if(equals(event, 'test_event'), toString(ifNull(events.properties.utm_source, '')), ''))) AS conversion_sources,
arrayFilter(x -> greater(x, 0), groupArray(if(and(equals(events.event, '$pageview'), notEmpty(toString(ifNull(events.properties.utm_campaign, ''))), notEmpty(toString(ifNull(events.properties.utm_source, '')))), toUnixTimestamp(events.timestamp), 0))) AS utm_timestamps,
arrayFilter(x -> notEmpty(x), groupArray(if(and(equals(events.event, '$pageview'), notEmpty(toString(ifNull(events.properties.utm_campaign, ''))), notEmpty(toString(ifNull(events.properties.utm_source, '')))), toString(ifNull(events.properties.utm_campaign, '')), ''))) AS utm_campaigns,
arrayFilter(x -> notEmpty(x), groupArray(if(and(equals(events.event, '$pageview'), notEmpty(toString(ifNull(events.properties.utm_campaign, ''))), notEmpty(toString(ifNull(events.properties.utm_source, '')))), toString(ifNull(events.properties.utm_source, '')), ''))) AS utm_sources
person_id,
conversion_timestamps[i] AS conversion_time,
conversion_math_values[i] AS conversion_math_value,
conversion_campaigns[i] AS conversion_campaign,
conversion_sources[i] AS conversion_source,
arrayMax(arrayFilter(x -> and(lessOrEquals(x, conversion_timestamps[i]), greaterOrEquals(x, minus(conversion_timestamps[i], 7776000))), utm_timestamps)) AS last_utm_timestamp,
if(isNotNull(last_utm_timestamp), utm_campaigns[indexOf(utm_timestamps, last_utm_timestamp)], '') AS fallback_campaign,
if(isNotNull(last_utm_timestamp), utm_sources[indexOf(utm_timestamps, last_utm_timestamp)], '') AS fallback_source
FROM
events
(
SELECT
events.person_id,
arrayFilter(x -> greater(x, 0), groupArray(if(equals(event, 'test_event'), toUnixTimestamp(events.timestamp), 0))) AS conversion_timestamps,
arrayFilter(x -> greater(x, 0), groupArray(if(equals(event, 'test_event'), toFloat(1), 0))) AS conversion_math_values,
arrayFilter(x -> notEmpty(toString(x)), groupArray(if(equals(event, 'test_event'), toString(ifNull(events.properties.utm_campaign, '')), ''))) AS conversion_campaigns,
arrayFilter(x -> notEmpty(toString(x)), groupArray(if(equals(event, 'test_event'), toString(ifNull(events.properties.utm_source, '')), ''))) AS conversion_sources,
arrayFilter(x -> greater(x, 0), groupArray(if(and(equals(events.event, '$pageview'), notEmpty(toString(ifNull(events.properties.utm_campaign, ''))), notEmpty(toString(ifNull(events.properties.utm_source, '')))), toUnixTimestamp(events.timestamp), 0))) AS utm_timestamps,
arrayFilter(x -> notEmpty(x), groupArray(if(and(equals(events.event, '$pageview'), notEmpty(toString(ifNull(events.properties.utm_campaign, ''))), notEmpty(toString(ifNull(events.properties.utm_source, '')))), toString(ifNull(events.properties.utm_campaign, '')), ''))) AS utm_campaigns,
arrayFilter(x -> notEmpty(x), groupArray(if(and(equals(events.event, '$pageview'), notEmpty(toString(ifNull(events.properties.utm_campaign, ''))), notEmpty(toString(ifNull(events.properties.utm_source, '')))), toString(ifNull(events.properties.utm_source, '')), ''))) AS utm_sources
FROM
events
WHERE
or(and(equals(event, 'test_event'), greaterOrEquals(events.timestamp, toDateTime('2024-09-01 00:00:00')), lessOrEquals(events.timestamp, toDateTime('2024-10-31 23:59:59'))), and(equals(events.event, '$pageview'), notEmpty(toString(ifNull(events.properties.utm_campaign, ''))), notEmpty(toString(ifNull(events.properties.utm_source, ''))), greaterOrEquals(events.timestamp, minus(toDateTime('2024-09-01 00:00:00'), toIntervalSecond(7776000))), lessOrEquals(events.timestamp, toDateTime('2024-10-31 23:59:59'))))
or(and(equals(event, 'test_event'), greaterOrEquals(events.timestamp, toDateTime('2024-09-01 00:00:00')), lessOrEquals(events.timestamp, toDateTime('2024-10-31 23:59:59'))), and(equals(events.event, '$pageview'), notEmpty(toString(ifNull(events.properties.utm_campaign, ''))), notEmpty(toString(ifNull(events.properties.utm_source, ''))), greaterOrEquals(events.timestamp, minus(toDateTime('2024-09-01 00:00:00'), toIntervalSecond(7776000))), lessOrEquals(events.timestamp, toDateTime('2024-10-31 23:59:59'))))
GROUP BY
events.person_id
events.person_id
HAVING
greater(length(conversion_timestamps), 0))
ARRAY JOIN arrayEnumerate(conversion_timestamps) AS i)) AS attributed_conversions
greater(length(conversion_timestamps), 0))
ARRAY JOIN arrayEnumerate(conversion_timestamps) AS i)) AS attributed_conversions
GROUP BY
campaign,
source) AS conv
GROUP BY
campaign,
source)
GROUP BY
campaign,
source) AS ucg ON and(equals(campaign_costs.campaign, ucg.campaign), equals(campaign_costs.source, ucg.source))) AS previous_period ON and(equals(current_period.Campaign, previous_period.Campaign), equals(current_period.Source, previous_period.Source))
source)) AS ucg ON and(equals(campaign_costs.campaign, ucg.campaign), equals(campaign_costs.source, ucg.source))) AS previous_period ON and(equals(current_period.Campaign, previous_period.Campaign), equals(current_period.Source, previous_period.Source))
ORDER BY
Cost DESC

View File

@@ -879,12 +879,6 @@ class ConversionGoalProcessor:
group_by=[ast.Field(chain=[field]) for field in self.config.group_by_fields],
)
def generate_cte_query_expr(self, additional_conditions: list[ast.Expr]) -> ast.Expr:
"""Generate CTE query expression"""
cte_name = self.get_cte_name()
select_query = self.generate_cte_query(additional_conditions)
return ast.Alias(alias=cte_name, expr=select_query)
def generate_join_clause(self, use_full_outer_join: bool = False) -> ast.JoinExpr:
"""Generate JOIN clause for this conversion goal"""
cte_name = self.get_cte_name()

View File

@@ -4,6 +4,7 @@ from posthog.hogql_queries.utils.query_date_range import QueryDateRange
from products.marketing_analytics.backend.hogql_queries.constants import UNIFIED_CONVERSION_GOALS_CTE_ALIAS
from .adapters.factory import MarketingSourceFactory
from .conversion_goal_processor import ConversionGoalProcessor
from .marketing_analytics_config import MarketingAnalyticsConfig
@@ -88,7 +89,13 @@ class ConversionGoalsAggregator:
union_query = ast.SelectSetQuery.create_from_queries(conversion_subqueries, "UNION ALL")
# Step 3: Create final aggregation query that sums all conversion goals by campaign/source
final_select: list[ast.Expr] = [ast.Field(chain=[field]) for field in self.config.group_by_fields]
# First, wrap the union in a subquery to materialize campaign/source fields
subquery_alias = "conv"
final_select: list[ast.Expr] = [
ast.Field(chain=[self.config.campaign_field]),
ast.Field(chain=[self.config.source_field]),
]
# Add each conversion goal as a summed column
for processor in self.processors:
@@ -104,11 +111,108 @@ class ConversionGoalsAggregator:
final_query = ast.SelectQuery(
select=final_select,
select_from=ast.JoinExpr(table=union_query),
group_by=[ast.Field(chain=[field]) for field in self.config.group_by_fields],
select_from=ast.JoinExpr(table=union_query, alias=subquery_alias),
group_by=[
ast.Field(chain=[self.config.campaign_field]),
ast.Field(chain=[self.config.source_field]),
],
)
return ast.CTE(name=UNIFIED_CONVERSION_GOALS_CTE_ALIAS, expr=final_query, cte_type="subquery")
# Now apply campaign name mappings by wrapping in another SELECT
campaign_field_expr = ast.Field(chain=[self.config.campaign_field])
source_field_expr = ast.Field(chain=[self.config.source_field])
mapped_campaign_expr = self._apply_campaign_name_mappings(campaign_field_expr, source_field_expr)
outer_select: list[ast.Expr] = [
ast.Alias(alias=self.config.campaign_field, expr=mapped_campaign_expr),
ast.Field(chain=[self.config.source_field]),
]
# Add conversion goal columns
for processor in self.processors:
outer_select.append(ast.Field(chain=[self.config.get_conversion_goal_column_name(processor.index)]))
wrapped_query = ast.SelectQuery(
select=outer_select,
select_from=ast.JoinExpr(table=final_query),
)
return ast.CTE(name=UNIFIED_CONVERSION_GOALS_CTE_ALIAS, expr=wrapped_query, cte_type="subquery")
def _apply_campaign_name_mappings(self, campaign_expr: ast.Expr, source_expr: ast.Expr) -> ast.Expr:
"""Apply campaign name mappings from team config"""
# Get team from first processor (all processors have the same team)
if not self.processors or not self.processors[0].team:
return campaign_expr
team = self.processors[0].team
try:
campaign_mappings = team.marketing_analytics_config.campaign_name_mappings
except Exception:
return campaign_expr
if not campaign_mappings:
return campaign_expr
conditions_and_results: list[ast.Expr] = []
lowercase_campaign = ast.Call(name="lower", args=[campaign_expr])
lowercase_source = ast.Call(name="lower", args=[source_expr])
for external_source, source_mappings in campaign_mappings.items():
if not source_mappings:
continue
# Get utm_source values for this adapter
adapter_class = MarketingSourceFactory._adapter_registry.get(external_source)
if not adapter_class:
continue
source_mapping = adapter_class.get_source_identifier_mapping()
utm_sources = []
for alternatives in source_mapping.values():
utm_sources.extend(alternatives)
if not utm_sources:
continue
# Build source condition once for this adapter
source_condition = ast.Call(
name="in",
args=[
lowercase_source,
ast.Array(exprs=[ast.Constant(value=s.lower()) for s in utm_sources]),
],
)
# Add condition/result pairs for each campaign mapping
for clean_name, raw_values in source_mappings.items():
if not raw_values:
continue
campaign_condition = ast.Call(
name="in",
args=[
lowercase_campaign,
ast.Array(exprs=[ast.Constant(value=val.lower()) for val in raw_values]),
],
)
# Combine source and campaign conditions
combined_condition = ast.Call(name="and", args=[source_condition, campaign_condition])
conditions_and_results.append(combined_condition)
conditions_and_results.append(ast.Constant(value=clean_name))
# If no mappings were added, return original campaign
if not conditions_and_results:
return campaign_expr
# Add default case (original campaign)
conditions_and_results.append(campaign_expr)
# Build multiIf with all conditions
return ast.Call(name="multiIf", args=conditions_and_results)
def get_conversion_goal_columns(self) -> dict[str, ast.Alias]:
"""Get the column mappings for accessing conversion goals from the unified CTE"""

View File

@@ -125,12 +125,10 @@ class TestConversionGoalsAggregator(ClickhouseTestMixin, BaseTest):
final_query = cte.expr
assert isinstance(final_query, ast.SelectQuery)
assert len(final_query.select) == 3
assert final_query.group_by is not None
assert len(final_query.group_by) == 2
conversion_column = final_query.select[2]
assert isinstance(conversion_column, ast.Alias)
assert conversion_column.alias == self.config.get_conversion_goal_column_name(0)
assert isinstance(conversion_column, ast.Field)
assert conversion_column.chain == [self.config.get_conversion_goal_column_name(0)]
def test_unified_cte_multiple_processors(self):
goal1 = self._create_test_conversion_goal("multi_goal1", "Goal 1")
@@ -150,17 +148,13 @@ class TestConversionGoalsAggregator(ClickhouseTestMixin, BaseTest):
final_query = cte.expr
assert isinstance(final_query, ast.SelectQuery)
assert len(final_query.select) == 5
assert final_query.group_by is not None
assert len(final_query.group_by) == 2
conversion_columns = final_query.select[2:]
assert len(conversion_columns) == 3
for i, column in enumerate(conversion_columns):
assert isinstance(column, ast.Alias)
assert column.alias == self.config.get_conversion_goal_column_name(i)
assert isinstance(column.expr, ast.Call)
assert column.expr.name == "sum"
assert isinstance(column, ast.Field)
assert column.chain == [self.config.get_conversion_goal_column_name(i)]
@pytest.mark.usefixtures("unittest_snapshot")
def test_unified_cte_sql_snapshot(self):
@@ -193,10 +187,8 @@ class TestConversionGoalsAggregator(ClickhouseTestMixin, BaseTest):
final_query = cte.expr
assert isinstance(final_query, ast.SelectQuery)
assert len(final_query.select) == 4
assert final_query.group_by is not None
assert len(final_query.group_by) == 2
assert isinstance(final_query.select_from, ast.JoinExpr)
assert isinstance(final_query.select_from.table, ast.SelectSetQuery)
assert isinstance(final_query.select_from.table, ast.SelectQuery)
def test_conversion_goal_columns_single(self):
goal = self._create_test_conversion_goal("columns_test", "Columns Test")
@@ -373,8 +365,6 @@ class TestConversionGoalsAggregator(ClickhouseTestMixin, BaseTest):
final_query = cte.expr
assert isinstance(final_query, ast.SelectQuery)
assert len(final_query.select) == 5
assert final_query.group_by is not None
assert len(final_query.group_by) == 2
conversion_columns = final_query.select[2:]
expected_names = [
@@ -382,7 +372,7 @@ class TestConversionGoalsAggregator(ClickhouseTestMixin, BaseTest):
self.config.get_conversion_goal_column_name(1),
self.config.get_conversion_goal_column_name(2),
]
actual_names = [col.alias for col in conversion_columns if isinstance(col, ast.Alias)]
actual_names = [col.chain[0] for col in conversion_columns if isinstance(col, ast.Field)]
assert actual_names == expected_names
def test_empty_goal_name(self):
@@ -413,8 +403,8 @@ class TestConversionGoalsAggregator(ClickhouseTestMixin, BaseTest):
final_query = cte.expr
assert isinstance(final_query, ast.SelectQuery)
conversion_column = final_query.select[2]
assert isinstance(conversion_column, ast.Alias)
assert conversion_column.alias == self.config.get_conversion_goal_column_name(999)
assert isinstance(conversion_column, ast.Field)
assert conversion_column.chain == [self.config.get_conversion_goal_column_name(999)]
def test_duplicate_goal_names(self):
goal1 = self._create_test_conversion_goal("dup1", "Duplicate Goal")
@@ -459,3 +449,105 @@ class TestConversionGoalsAggregator(ClickhouseTestMixin, BaseTest):
assert isinstance(cte, ast.CTE)
assert cte.name == "ucg"
@pytest.mark.usefixtures("unittest_snapshot")
def test_campaign_name_mapping_sql_generation(self):
"""Test that campaign name mappings generate correct SQL with nested CASE statements"""
# Configure campaign name mappings for multiple sources
self.team.marketing_analytics_config.campaign_name_mappings = {
"GoogleAds": {
"Spring Sale 2024": ["spring_sale_2024", "spring-sale-2024", "SPRING_SALE"],
"Black Friday": ["bf_2024", "blackfriday", "BF-PROMO"],
},
"MetaAds": {
"Spring Sale 2024": ["spring_sale_fb", "SpringSaleFB"],
"Summer Campaign": ["summer_2024", "SUMMER_PROMO"],
},
}
self.team.marketing_analytics_config.save()
goal = self._create_test_conversion_goal("mapping_sql_test", "Mapping SQL Test", "purchase")
processor = self._create_test_processor(goal, 0)
aggregator = ConversionGoalsAggregator(processors=[processor], config=self.config)
additional_conditions_getter = self._create_mock_additional_conditions_getter()
cte = aggregator.generate_unified_cte(self.date_range, additional_conditions_getter)
# Execute to get the SQL string
response = execute_hogql_query(query=cte.expr, team=self.team)
assert pretty_print_in_tests(response.hogql, self.team.pk) == self.snapshot
@pytest.mark.usefixtures("unittest_snapshot")
def test_campaign_name_mapping_single_source_sql(self):
"""Test campaign name mapping for a single data source"""
self.team.marketing_analytics_config.campaign_name_mappings = {
"GoogleAds": {
"Clean Campaign Name": ["messy_campaign_1", "messy_campaign_2", "MESSY_CAMPAIGN_3"],
}
}
self.team.marketing_analytics_config.save()
goal = self._create_test_conversion_goal("single_source_mapping", "Single Source Mapping", "conversion")
processor = self._create_test_processor(goal, 0)
aggregator = ConversionGoalsAggregator(processors=[processor], config=self.config)
additional_conditions_getter = self._create_mock_additional_conditions_getter()
cte = aggregator.generate_unified_cte(self.date_range, additional_conditions_getter)
response = execute_hogql_query(query=cte.expr, team=self.team)
assert pretty_print_in_tests(response.hogql, self.team.pk) == self.snapshot
@pytest.mark.usefixtures("unittest_snapshot")
def test_campaign_name_mapping_no_mappings(self):
"""Test that when no mappings are configured, the query uses original campaign names"""
self.team.marketing_analytics_config.campaign_name_mappings = {}
self.team.marketing_analytics_config.save()
goal = self._create_test_conversion_goal("no_mapping_test", "No Mapping Test", "purchase")
processor = self._create_test_processor(goal, 0)
aggregator = ConversionGoalsAggregator(processors=[processor], config=self.config)
additional_conditions_getter = self._create_mock_additional_conditions_getter()
cte = aggregator.generate_unified_cte(self.date_range, additional_conditions_getter)
response = execute_hogql_query(query=cte.expr, team=self.team)
assert pretty_print_in_tests(response.hogql, self.team.pk) == self.snapshot
@pytest.mark.usefixtures("unittest_snapshot")
def test_campaign_name_mapping_integration_with_events(self):
"""Test campaign name mapping SQL generation with actual events to verify mapping works end-to-end"""
# Configure campaign name mappings for GoogleAds
self.team.marketing_analytics_config.campaign_name_mappings = {
"GoogleAds": {
"Spring Sale 2024": ["spring_sale_2024", "spring-sale-2024"],
"Holiday Promo": ["holiday_campaign", "holiday_promo"],
}
}
self.team.marketing_analytics_config.save()
# Create conversion goal
goal = self._create_test_conversion_goal("integration_test", "Integration Test", "purchase")
processor = self._create_test_processor(goal, 0)
aggregator = ConversionGoalsAggregator(processors=[processor], config=self.config)
additional_conditions_getter = self._create_mock_additional_conditions_getter()
cte = aggregator.generate_unified_cte(self.date_range, additional_conditions_getter)
# Execute the query to get the SQL (but don't worry about results due to memory limits)
response = execute_hogql_query(query=cte.expr, team=self.team)
# Verify the SQL contains campaign name mapping logic
assert pretty_print_in_tests(response.hogql, self.team.pk) == self.snapshot
# Verify that the SQL contains the expected campaign name mapping logic
sql_string = pretty_print_in_tests(response.hogql, self.team.pk)
# Check that the SQL contains multiIf with campaign name mapping
assert "multiIf" in sql_string, "SQL should contain multiIf for campaign name mapping"
assert "Spring Sale 2024" in sql_string, "SQL should contain mapped campaign name"
assert "Holiday Promo" in sql_string, "SQL should contain mapped campaign name"
assert "spring_sale_2024" in sql_string, "SQL should contain original campaign name in mapping"
assert "holiday_campaign" in sql_string, "SQL should contain original campaign name in mapping"