mirror of
https://github.com/BillyOutlast/posthog.git
synced 2026-02-04 03:01:23 +01:00
feat: fix queries and tests before upgrading to ClickHouse v25.8.11.66 (#40927)
This commit is contained in:
@@ -132,6 +132,10 @@
|
||||
prop
|
||||
HAVING steps = max(max_steps))
|
||||
GROUP BY prop
|
||||
ORDER BY step_3 DESC,
|
||||
step_2 DESC,
|
||||
step_1 DESC
|
||||
LIMIT 100
|
||||
'''
|
||||
# ---
|
||||
# name: TestFunnelGroupBreakdown.test_funnel_aggregate_by_groups_breakdown_group_person_on_events_poe_v2
|
||||
@@ -281,6 +285,10 @@
|
||||
prop
|
||||
HAVING steps = max(max_steps))
|
||||
GROUP BY prop
|
||||
ORDER BY step_3 DESC,
|
||||
step_2 DESC,
|
||||
step_1 DESC
|
||||
LIMIT 100
|
||||
'''
|
||||
# ---
|
||||
# name: TestFunnelGroupBreakdown.test_funnel_breakdown_group
|
||||
@@ -426,6 +434,10 @@
|
||||
prop
|
||||
HAVING steps = max(max_steps))
|
||||
GROUP BY prop
|
||||
ORDER BY step_3 DESC,
|
||||
step_2 DESC,
|
||||
step_1 DESC
|
||||
LIMIT 100
|
||||
'''
|
||||
# ---
|
||||
# name: TestFunnelGroupBreakdown.test_funnel_breakdown_group.2
|
||||
@@ -1133,6 +1145,10 @@
|
||||
prop
|
||||
HAVING steps = max(max_steps))
|
||||
GROUP BY prop
|
||||
ORDER BY step_3 DESC,
|
||||
step_2 DESC,
|
||||
step_1 DESC
|
||||
LIMIT 100
|
||||
'''
|
||||
# ---
|
||||
# name: TestStrictFunnelGroupBreakdown.test_funnel_aggregate_by_groups_breakdown_group_person_on_events_poe_v2
|
||||
@@ -1282,6 +1298,10 @@
|
||||
prop
|
||||
HAVING steps = max(max_steps))
|
||||
GROUP BY prop
|
||||
ORDER BY step_3 DESC,
|
||||
step_2 DESC,
|
||||
step_1 DESC
|
||||
LIMIT 100
|
||||
'''
|
||||
# ---
|
||||
# name: TestStrictFunnelGroupBreakdown.test_funnel_breakdown_group
|
||||
@@ -1427,6 +1447,10 @@
|
||||
prop
|
||||
HAVING steps = max(max_steps))
|
||||
GROUP BY prop
|
||||
ORDER BY step_3 DESC,
|
||||
step_2 DESC,
|
||||
step_1 DESC
|
||||
LIMIT 100
|
||||
'''
|
||||
# ---
|
||||
# name: TestStrictFunnelGroupBreakdown.test_funnel_breakdown_group.2
|
||||
@@ -2042,6 +2066,10 @@
|
||||
prop
|
||||
HAVING steps = max(max_steps))
|
||||
GROUP BY prop
|
||||
ORDER BY step_3 DESC,
|
||||
step_2 DESC,
|
||||
step_1 DESC
|
||||
LIMIT 100
|
||||
'''
|
||||
# ---
|
||||
# name: TestUnorderedFunnelGroupBreakdown.test_funnel_aggregate_by_groups_breakdown_group_person_on_events_poe_v2
|
||||
@@ -2191,6 +2219,10 @@
|
||||
prop
|
||||
HAVING steps = max(max_steps))
|
||||
GROUP BY prop
|
||||
ORDER BY step_3 DESC,
|
||||
step_2 DESC,
|
||||
step_1 DESC
|
||||
LIMIT 100
|
||||
'''
|
||||
# ---
|
||||
# name: TestUnorderedFunnelGroupBreakdown.test_funnel_breakdown_group
|
||||
@@ -2336,6 +2368,10 @@
|
||||
prop
|
||||
HAVING steps = max(max_steps))
|
||||
GROUP BY prop
|
||||
ORDER BY step_3 DESC,
|
||||
step_2 DESC,
|
||||
step_1 DESC
|
||||
LIMIT 100
|
||||
'''
|
||||
# ---
|
||||
# name: TestUnorderedFunnelGroupBreakdown.test_funnel_breakdown_group.10
|
||||
|
||||
@@ -90,6 +90,8 @@
|
||||
HAVING ifNull(equals(steps, max(max_steps)), isNull(steps)
|
||||
and isNull(max(max_steps))))
|
||||
GROUP BY prop
|
||||
ORDER BY step_2 DESC,
|
||||
step_1 DESC
|
||||
LIMIT 100 SETTINGS readonly=2,
|
||||
max_execution_time=60,
|
||||
allow_experimental_object_type=1,
|
||||
|
||||
@@ -95,6 +95,8 @@
|
||||
and isNull(max(max_steps))))
|
||||
GROUP BY prop)
|
||||
GROUP BY final_prop
|
||||
ORDER BY step_2 DESC,
|
||||
step_1 DESC
|
||||
LIMIT 26 SETTINGS readonly=2,
|
||||
max_execution_time=60,
|
||||
allow_experimental_object_type=1,
|
||||
@@ -204,6 +206,8 @@
|
||||
and isNull(max(max_steps))))
|
||||
GROUP BY prop)
|
||||
GROUP BY final_prop
|
||||
ORDER BY step_2 DESC,
|
||||
step_1 DESC
|
||||
LIMIT 26 SETTINGS readonly=2,
|
||||
max_execution_time=60,
|
||||
allow_experimental_object_type=1,
|
||||
@@ -313,6 +317,8 @@
|
||||
and isNull(max(max_steps))))
|
||||
GROUP BY prop)
|
||||
GROUP BY final_prop
|
||||
ORDER BY step_2 DESC,
|
||||
step_1 DESC
|
||||
LIMIT 26 SETTINGS readonly=2,
|
||||
max_execution_time=60,
|
||||
allow_experimental_object_type=1,
|
||||
@@ -432,6 +438,8 @@
|
||||
and isNull(max(max_steps))))
|
||||
GROUP BY prop)
|
||||
GROUP BY final_prop
|
||||
ORDER BY step_2 DESC,
|
||||
step_1 DESC
|
||||
LIMIT 26 SETTINGS readonly=2,
|
||||
max_execution_time=60,
|
||||
allow_experimental_object_type=1,
|
||||
@@ -551,6 +559,8 @@
|
||||
and isNull(max(max_steps))))
|
||||
GROUP BY prop)
|
||||
GROUP BY final_prop
|
||||
ORDER BY step_2 DESC,
|
||||
step_1 DESC
|
||||
LIMIT 26 SETTINGS readonly=2,
|
||||
max_execution_time=60,
|
||||
allow_experimental_object_type=1,
|
||||
@@ -670,6 +680,8 @@
|
||||
and isNull(max(max_steps))))
|
||||
GROUP BY prop)
|
||||
GROUP BY final_prop
|
||||
ORDER BY step_2 DESC,
|
||||
step_1 DESC
|
||||
LIMIT 26 SETTINGS readonly=2,
|
||||
max_execution_time=60,
|
||||
allow_experimental_object_type=1,
|
||||
@@ -772,6 +784,8 @@
|
||||
and isNull(max(max_steps))))
|
||||
GROUP BY prop)
|
||||
GROUP BY final_prop
|
||||
ORDER BY step_2 DESC,
|
||||
step_1 DESC
|
||||
LIMIT 26 SETTINGS readonly=2,
|
||||
max_execution_time=60,
|
||||
allow_experimental_object_type=1,
|
||||
@@ -874,6 +888,8 @@
|
||||
and isNull(max(max_steps))))
|
||||
GROUP BY prop)
|
||||
GROUP BY final_prop
|
||||
ORDER BY step_2 DESC,
|
||||
step_1 DESC
|
||||
LIMIT 26 SETTINGS readonly=2,
|
||||
max_execution_time=60,
|
||||
allow_experimental_object_type=1,
|
||||
@@ -976,6 +992,8 @@
|
||||
and isNull(max(max_steps))))
|
||||
GROUP BY prop)
|
||||
GROUP BY final_prop
|
||||
ORDER BY step_2 DESC,
|
||||
step_1 DESC
|
||||
LIMIT 26 SETTINGS readonly=2,
|
||||
max_execution_time=60,
|
||||
allow_experimental_object_type=1,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import datetime
|
||||
from datetime import timedelta
|
||||
|
||||
from freezegun import freeze_time
|
||||
@@ -133,18 +134,19 @@ class TestGroupsQueryRunner(ClickhouseTestMixin, APIBaseTest):
|
||||
team=self.team, project_id=self.team.project_id, group_type="organization", group_type_index=0
|
||||
)
|
||||
test_groups = [
|
||||
{"key": "prefix2", "name": "testable"},
|
||||
{"key": "contains", "name": "my_test_group"},
|
||||
{"key": "prefix", "name": "testing"},
|
||||
{"key": "exact", "name": "test"},
|
||||
{"key": "contains2", "name": "best_test_ever"},
|
||||
{"key": "prefix2", "name": "testable", "ts": datetime.datetime(2025, 11, 3, 17, 16)},
|
||||
{"key": "contains", "name": "my_test_group", "ts": datetime.datetime(2025, 11, 3, 17, 15)},
|
||||
{"key": "prefix", "name": "testing", "ts": datetime.datetime(2025, 11, 3, 17, 14)},
|
||||
{"key": "exact", "name": "test", "ts": datetime.datetime(2025, 11, 3, 17, 17)},
|
||||
{"key": "contains2", "name": "best_test_ever", "ts": datetime.datetime(2025, 11, 3, 17, 18)},
|
||||
]
|
||||
for group in test_groups:
|
||||
create_group(
|
||||
team_id=self.team.pk,
|
||||
group_type_index=0,
|
||||
group_key=group["key"],
|
||||
group_key=group["key"], # type: ignore[arg-type]
|
||||
properties={"name": group["name"]},
|
||||
timestamp=group["ts"], # type: ignore[arg-type]
|
||||
)
|
||||
|
||||
query = GroupsQuery(
|
||||
|
||||
@@ -636,6 +636,10 @@ class FunnelBase(ABC):
|
||||
self.context.limit_context
|
||||
)
|
||||
|
||||
def _order_by(self) -> list[ast.OrderExpr]:
|
||||
max_steps = self.context.max_steps
|
||||
return [ast.OrderExpr(expr=ast.Field(chain=[f"step_{i}"]), order="DESC") for i in range(max_steps, 0, -1)]
|
||||
|
||||
# Wrap funnel query in another query to determine the top X breakdowns, and bucket all others into "Other" bucket
|
||||
def _breakdown_other_subquery(self) -> ast.SelectQuery:
|
||||
max_steps = self.context.max_steps
|
||||
@@ -677,6 +681,7 @@ class FunnelBase(ABC):
|
||||
],
|
||||
select_from=ast.JoinExpr(table=select_query),
|
||||
group_by=[ast.Field(chain=["final_prop"])],
|
||||
order_by=self._order_by(),
|
||||
limit=ast.Constant(value=self.get_breakdown_limit() + 1),
|
||||
)
|
||||
|
||||
@@ -869,11 +874,11 @@ class FunnelBase(ABC):
|
||||
statement = None
|
||||
for i in range(max_steps - 1, -1, -1):
|
||||
if i == max_steps - 1:
|
||||
statement = f"if(isNull(latest_{i}),step_{i-1}_matching_event,step_{i}_matching_event)"
|
||||
statement = f"if(isNull(latest_{i}),step_{i - 1}_matching_event,step_{i}_matching_event)"
|
||||
elif i == 0:
|
||||
statement = f"if(isNull(latest_0),(null,null,null,null),{statement})"
|
||||
else:
|
||||
statement = f"if(isNull(latest_{i}),step_{i-1}_matching_event,{statement})"
|
||||
statement = f"if(isNull(latest_{i}),step_{i - 1}_matching_event,{statement})"
|
||||
return [parse_expr(f"{statement} as final_matching_event")] if statement else []
|
||||
|
||||
def _get_matching_events(self, max_steps: int) -> list[ast.Expr]:
|
||||
@@ -1009,7 +1014,7 @@ class FunnelBase(ABC):
|
||||
for i in range(1, max_steps):
|
||||
exprs.append(
|
||||
parse_expr(
|
||||
f"if(isNotNull(latest_{i}) AND latest_{i} <= toTimeZone(latest_{i-1}, 'UTC') + INTERVAL {windowInterval} {windowIntervalUnit}, dateDiff('second', latest_{i - 1}, latest_{i}), NULL) as step_{i}_conversion_time"
|
||||
f"if(isNotNull(latest_{i}) AND latest_{i} <= toTimeZone(latest_{i - 1}, 'UTC') + INTERVAL {windowInterval} {windowIntervalUnit}, dateDiff('second', latest_{i - 1}, latest_{i}), NULL) as step_{i}_conversion_time"
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
@@ -54,6 +54,7 @@ class Funnel(FunnelBase):
|
||||
select=select,
|
||||
select_from=ast.JoinExpr(table=self.get_step_counts_query()),
|
||||
group_by=[ast.Field(chain=["prop"])] if len(breakdown_exprs) > 0 else None,
|
||||
order_by=self._order_by() if len(breakdown_exprs) > 0 else None,
|
||||
)
|
||||
|
||||
def get_step_counts_query(self):
|
||||
|
||||
@@ -208,8 +208,6 @@ class FunnelUDF(FunnelUDFMixin, FunnelBase):
|
||||
]
|
||||
)
|
||||
|
||||
order_by = ",".join([f"step_{i+1} DESC" for i in reversed(range(self.context.max_steps))])
|
||||
|
||||
other_aggregation = "['Other']" if self._query_has_array_breakdown() else "'Other'"
|
||||
|
||||
use_breakdown_limit = self.context.breakdown and self.context.breakdownType in [
|
||||
@@ -223,6 +221,7 @@ class FunnelUDF(FunnelUDFMixin, FunnelBase):
|
||||
if use_breakdown_limit
|
||||
else "breakdown"
|
||||
)
|
||||
order_by = ",".join([f"step_{i + 1} DESC" for i in reversed(range(self.context.max_steps))])
|
||||
|
||||
s = parse_select(
|
||||
f"""
|
||||
@@ -252,6 +251,7 @@ class FunnelUDF(FunnelUDFMixin, FunnelBase):
|
||||
]
|
||||
)
|
||||
|
||||
order_by = ",".join([f"step_{i + 1} DESC" for i in reversed(range(self.context.max_steps))])
|
||||
# Weird: unless you reference row_number in this outer block, it doesn't work correctly
|
||||
s = cast(
|
||||
ast.SelectQuery,
|
||||
@@ -266,6 +266,7 @@ class FunnelUDF(FunnelUDFMixin, FunnelBase):
|
||||
FROM
|
||||
{{s}}
|
||||
GROUP BY final_prop
|
||||
ORDER BY {order_by}
|
||||
LIMIT {self.get_breakdown_limit() + 1 if use_breakdown_limit else DEFAULT_RETURNED_ROWS}
|
||||
""",
|
||||
{"s": s},
|
||||
|
||||
@@ -1229,6 +1229,8 @@
|
||||
and isNull(max(max_steps))))
|
||||
GROUP BY prop)
|
||||
GROUP BY final_prop
|
||||
ORDER BY step_2 DESC,
|
||||
step_1 DESC
|
||||
LIMIT 26 SETTINGS readonly=2,
|
||||
max_execution_time=60,
|
||||
allow_experimental_object_type=1,
|
||||
@@ -1336,6 +1338,8 @@
|
||||
and isNull(max(max_steps))))
|
||||
GROUP BY prop)
|
||||
GROUP BY final_prop
|
||||
ORDER BY step_2 DESC,
|
||||
step_1 DESC
|
||||
LIMIT 26 SETTINGS readonly=2,
|
||||
max_execution_time=60,
|
||||
allow_experimental_object_type=1,
|
||||
@@ -1436,6 +1440,8 @@
|
||||
and isNull(max(max_steps))))
|
||||
GROUP BY prop)
|
||||
GROUP BY final_prop
|
||||
ORDER BY step_2 DESC,
|
||||
step_1 DESC
|
||||
LIMIT 26 SETTINGS readonly=2,
|
||||
max_execution_time=60,
|
||||
allow_experimental_object_type=1,
|
||||
@@ -1577,6 +1583,9 @@
|
||||
and isNull(max(max_steps))))
|
||||
GROUP BY prop)
|
||||
GROUP BY final_prop
|
||||
ORDER BY step_3 DESC,
|
||||
step_2 DESC,
|
||||
step_1 DESC
|
||||
LIMIT 26 SETTINGS readonly=2,
|
||||
max_execution_time=60,
|
||||
allow_experimental_object_type=1,
|
||||
@@ -1718,6 +1727,9 @@
|
||||
and isNull(max(max_steps))))
|
||||
GROUP BY prop)
|
||||
GROUP BY final_prop
|
||||
ORDER BY step_3 DESC,
|
||||
step_2 DESC,
|
||||
step_1 DESC
|
||||
LIMIT 26 SETTINGS readonly=2,
|
||||
max_execution_time=60,
|
||||
allow_experimental_object_type=1,
|
||||
@@ -1866,6 +1878,9 @@
|
||||
and isNull(max(max_steps))))
|
||||
GROUP BY prop)
|
||||
GROUP BY final_prop
|
||||
ORDER BY step_3 DESC,
|
||||
step_2 DESC,
|
||||
step_1 DESC
|
||||
LIMIT 26 SETTINGS readonly=2,
|
||||
max_execution_time=60,
|
||||
allow_experimental_object_type=1,
|
||||
|
||||
@@ -86,6 +86,8 @@
|
||||
and isNull(max(max_steps))))
|
||||
GROUP BY prop)
|
||||
GROUP BY final_prop
|
||||
ORDER BY step_2 DESC,
|
||||
step_1 DESC
|
||||
LIMIT 101 SETTINGS readonly=2,
|
||||
max_execution_time=60,
|
||||
allow_experimental_object_type=1,
|
||||
@@ -186,6 +188,8 @@
|
||||
and isNull(max(max_steps))))
|
||||
GROUP BY prop)
|
||||
GROUP BY final_prop
|
||||
ORDER BY step_2 DESC,
|
||||
step_1 DESC
|
||||
LIMIT 101 SETTINGS readonly=2,
|
||||
max_execution_time=60,
|
||||
allow_experimental_object_type=1,
|
||||
|
||||
@@ -85,6 +85,8 @@
|
||||
and isNull(max(max_steps))))
|
||||
GROUP BY prop)
|
||||
GROUP BY final_prop
|
||||
ORDER BY step_2 DESC,
|
||||
step_1 DESC
|
||||
LIMIT 26 SETTINGS readonly=2,
|
||||
max_execution_time=60,
|
||||
allow_experimental_object_type=1,
|
||||
@@ -191,6 +193,8 @@
|
||||
and isNull(max(max_steps))))
|
||||
GROUP BY prop)
|
||||
GROUP BY final_prop
|
||||
ORDER BY step_2 DESC,
|
||||
step_1 DESC
|
||||
LIMIT 26 SETTINGS readonly=2,
|
||||
max_execution_time=60,
|
||||
allow_experimental_object_type=1,
|
||||
@@ -290,6 +294,8 @@
|
||||
and isNull(max(max_steps))))
|
||||
GROUP BY prop)
|
||||
GROUP BY final_prop
|
||||
ORDER BY step_2 DESC,
|
||||
step_1 DESC
|
||||
LIMIT 26 SETTINGS readonly=2,
|
||||
max_execution_time=60,
|
||||
allow_experimental_object_type=1,
|
||||
@@ -408,6 +414,9 @@
|
||||
and isNull(max(max_steps))))
|
||||
GROUP BY prop)
|
||||
GROUP BY final_prop
|
||||
ORDER BY step_3 DESC,
|
||||
step_2 DESC,
|
||||
step_1 DESC
|
||||
LIMIT 26 SETTINGS readonly=2,
|
||||
max_execution_time=60,
|
||||
allow_experimental_object_type=1,
|
||||
@@ -526,6 +535,9 @@
|
||||
and isNull(max(max_steps))))
|
||||
GROUP BY prop)
|
||||
GROUP BY final_prop
|
||||
ORDER BY step_3 DESC,
|
||||
step_2 DESC,
|
||||
step_1 DESC
|
||||
LIMIT 26 SETTINGS readonly=2,
|
||||
max_execution_time=60,
|
||||
allow_experimental_object_type=1,
|
||||
@@ -651,6 +663,9 @@
|
||||
and isNull(max(max_steps))))
|
||||
GROUP BY prop)
|
||||
GROUP BY final_prop
|
||||
ORDER BY step_3 DESC,
|
||||
step_2 DESC,
|
||||
step_1 DESC
|
||||
LIMIT 26 SETTINGS readonly=2,
|
||||
max_execution_time=60,
|
||||
allow_experimental_object_type=1,
|
||||
|
||||
@@ -51,6 +51,8 @@
|
||||
GROUP BY breakdown
|
||||
ORDER BY step_2 DESC, step_1 DESC)
|
||||
GROUP BY final_prop
|
||||
ORDER BY step_2 DESC,
|
||||
step_1 DESC
|
||||
LIMIT 26 SETTINGS join_algorithm='auto',
|
||||
readonly=2,
|
||||
max_execution_time=60,
|
||||
@@ -119,6 +121,8 @@
|
||||
GROUP BY breakdown
|
||||
ORDER BY step_2 DESC, step_1 DESC)
|
||||
GROUP BY final_prop
|
||||
ORDER BY step_2 DESC,
|
||||
step_1 DESC
|
||||
LIMIT 26 SETTINGS join_algorithm='auto',
|
||||
readonly=2,
|
||||
max_execution_time=60,
|
||||
@@ -185,6 +189,8 @@
|
||||
GROUP BY breakdown
|
||||
ORDER BY step_2 DESC, step_1 DESC)
|
||||
GROUP BY final_prop
|
||||
ORDER BY step_2 DESC,
|
||||
step_1 DESC
|
||||
LIMIT 26 SETTINGS join_algorithm='auto',
|
||||
readonly=2,
|
||||
max_execution_time=60,
|
||||
@@ -258,6 +264,9 @@
|
||||
GROUP BY breakdown
|
||||
ORDER BY step_3 DESC, step_2 DESC, step_1 DESC)
|
||||
GROUP BY final_prop
|
||||
ORDER BY step_3 DESC,
|
||||
step_2 DESC,
|
||||
step_1 DESC
|
||||
LIMIT 26 SETTINGS join_algorithm='auto',
|
||||
readonly=2,
|
||||
max_execution_time=60,
|
||||
@@ -331,6 +340,9 @@
|
||||
GROUP BY breakdown
|
||||
ORDER BY step_3 DESC, step_2 DESC, step_1 DESC)
|
||||
GROUP BY final_prop
|
||||
ORDER BY step_3 DESC,
|
||||
step_2 DESC,
|
||||
step_1 DESC
|
||||
LIMIT 26 SETTINGS join_algorithm='auto',
|
||||
readonly=2,
|
||||
max_execution_time=60,
|
||||
@@ -411,6 +423,9 @@
|
||||
GROUP BY breakdown
|
||||
ORDER BY step_3 DESC, step_2 DESC, step_1 DESC)
|
||||
GROUP BY final_prop
|
||||
ORDER BY step_3 DESC,
|
||||
step_2 DESC,
|
||||
step_1 DESC
|
||||
LIMIT 26 SETTINGS join_algorithm='auto',
|
||||
readonly=2,
|
||||
max_execution_time=60,
|
||||
|
||||
@@ -42,6 +42,8 @@
|
||||
GROUP BY breakdown
|
||||
ORDER BY step_2 DESC, step_1 DESC)
|
||||
GROUP BY final_prop
|
||||
ORDER BY step_2 DESC,
|
||||
step_1 DESC
|
||||
LIMIT 100 SETTINGS join_algorithm='auto',
|
||||
readonly=2,
|
||||
max_execution_time=60,
|
||||
@@ -107,6 +109,8 @@
|
||||
GROUP BY breakdown
|
||||
ORDER BY step_2 DESC, step_1 DESC)
|
||||
GROUP BY final_prop
|
||||
ORDER BY step_2 DESC,
|
||||
step_1 DESC
|
||||
LIMIT 100 SETTINGS join_algorithm='auto',
|
||||
readonly=2,
|
||||
max_execution_time=60,
|
||||
@@ -177,6 +181,9 @@
|
||||
GROUP BY breakdown
|
||||
ORDER BY step_3 DESC, step_2 DESC, step_1 DESC)
|
||||
GROUP BY final_prop
|
||||
ORDER BY step_3 DESC,
|
||||
step_2 DESC,
|
||||
step_1 DESC
|
||||
LIMIT 100 SETTINGS join_algorithm='auto',
|
||||
readonly=2,
|
||||
max_execution_time=60,
|
||||
@@ -317,6 +324,9 @@
|
||||
GROUP BY breakdown
|
||||
ORDER BY step_3 DESC, step_2 DESC, step_1 DESC)
|
||||
GROUP BY final_prop
|
||||
ORDER BY step_3 DESC,
|
||||
step_2 DESC,
|
||||
step_1 DESC
|
||||
LIMIT 100 SETTINGS join_algorithm='auto',
|
||||
readonly=2,
|
||||
max_execution_time=60,
|
||||
@@ -415,6 +425,8 @@
|
||||
GROUP BY breakdown
|
||||
ORDER BY step_2 DESC, step_1 DESC)
|
||||
GROUP BY final_prop
|
||||
ORDER BY step_2 DESC,
|
||||
step_1 DESC
|
||||
LIMIT 100 SETTINGS join_algorithm='auto',
|
||||
readonly=2,
|
||||
max_execution_time=60,
|
||||
@@ -496,6 +508,9 @@
|
||||
GROUP BY breakdown
|
||||
ORDER BY step_3 DESC, step_2 DESC, step_1 DESC)
|
||||
GROUP BY final_prop
|
||||
ORDER BY step_3 DESC,
|
||||
step_2 DESC,
|
||||
step_1 DESC
|
||||
LIMIT 100 SETTINGS join_algorithm='auto',
|
||||
readonly=2,
|
||||
max_execution_time=60,
|
||||
@@ -779,6 +794,8 @@
|
||||
GROUP BY breakdown
|
||||
ORDER BY step_2 DESC, step_1 DESC)
|
||||
GROUP BY final_prop
|
||||
ORDER BY step_2 DESC,
|
||||
step_1 DESC
|
||||
LIMIT 100 SETTINGS join_algorithm='auto',
|
||||
readonly=2,
|
||||
max_execution_time=60,
|
||||
@@ -843,6 +860,8 @@
|
||||
GROUP BY breakdown
|
||||
ORDER BY step_2 DESC, step_1 DESC)
|
||||
GROUP BY final_prop
|
||||
ORDER BY step_2 DESC,
|
||||
step_1 DESC
|
||||
LIMIT 100 SETTINGS join_algorithm='auto',
|
||||
readonly=2,
|
||||
max_execution_time=60,
|
||||
@@ -909,6 +928,8 @@
|
||||
GROUP BY breakdown
|
||||
ORDER BY step_2 DESC, step_1 DESC)
|
||||
GROUP BY final_prop
|
||||
ORDER BY step_2 DESC,
|
||||
step_1 DESC
|
||||
LIMIT 26 SETTINGS join_algorithm='auto',
|
||||
readonly=2,
|
||||
max_execution_time=60,
|
||||
@@ -977,6 +998,8 @@
|
||||
GROUP BY breakdown
|
||||
ORDER BY step_2 DESC, step_1 DESC)
|
||||
GROUP BY final_prop
|
||||
ORDER BY step_2 DESC,
|
||||
step_1 DESC
|
||||
LIMIT 26 SETTINGS join_algorithm='auto',
|
||||
readonly=2,
|
||||
max_execution_time=60,
|
||||
@@ -1043,6 +1066,8 @@
|
||||
GROUP BY breakdown
|
||||
ORDER BY step_2 DESC, step_1 DESC)
|
||||
GROUP BY final_prop
|
||||
ORDER BY step_2 DESC,
|
||||
step_1 DESC
|
||||
LIMIT 26 SETTINGS join_algorithm='auto',
|
||||
readonly=2,
|
||||
max_execution_time=60,
|
||||
@@ -1116,6 +1141,9 @@
|
||||
GROUP BY breakdown
|
||||
ORDER BY step_3 DESC, step_2 DESC, step_1 DESC)
|
||||
GROUP BY final_prop
|
||||
ORDER BY step_3 DESC,
|
||||
step_2 DESC,
|
||||
step_1 DESC
|
||||
LIMIT 26 SETTINGS join_algorithm='auto',
|
||||
readonly=2,
|
||||
max_execution_time=60,
|
||||
@@ -1189,6 +1217,9 @@
|
||||
GROUP BY breakdown
|
||||
ORDER BY step_3 DESC, step_2 DESC, step_1 DESC)
|
||||
GROUP BY final_prop
|
||||
ORDER BY step_3 DESC,
|
||||
step_2 DESC,
|
||||
step_1 DESC
|
||||
LIMIT 26 SETTINGS join_algorithm='auto',
|
||||
readonly=2,
|
||||
max_execution_time=60,
|
||||
@@ -1269,6 +1300,9 @@
|
||||
GROUP BY breakdown
|
||||
ORDER BY step_3 DESC, step_2 DESC, step_1 DESC)
|
||||
GROUP BY final_prop
|
||||
ORDER BY step_3 DESC,
|
||||
step_2 DESC,
|
||||
step_1 DESC
|
||||
LIMIT 26 SETTINGS join_algorithm='auto',
|
||||
readonly=2,
|
||||
max_execution_time=60,
|
||||
|
||||
@@ -139,6 +139,8 @@
|
||||
and isNull(max(max_steps))))
|
||||
GROUP BY prop)
|
||||
GROUP BY final_prop
|
||||
ORDER BY step_2 DESC,
|
||||
step_1 DESC
|
||||
LIMIT 26 SETTINGS readonly=2,
|
||||
max_execution_time=60,
|
||||
allow_experimental_object_type=1,
|
||||
@@ -306,6 +308,8 @@
|
||||
and isNull(max(max_steps))))
|
||||
GROUP BY prop)
|
||||
GROUP BY final_prop
|
||||
ORDER BY step_2 DESC,
|
||||
step_1 DESC
|
||||
LIMIT 26 SETTINGS readonly=2,
|
||||
max_execution_time=60,
|
||||
allow_experimental_object_type=1,
|
||||
@@ -459,6 +463,8 @@
|
||||
and isNull(max(max_steps))))
|
||||
GROUP BY prop)
|
||||
GROUP BY final_prop
|
||||
ORDER BY step_2 DESC,
|
||||
step_1 DESC
|
||||
LIMIT 26 SETTINGS readonly=2,
|
||||
max_execution_time=60,
|
||||
allow_experimental_object_type=1,
|
||||
@@ -707,6 +713,9 @@
|
||||
and isNull(max(max_steps))))
|
||||
GROUP BY prop)
|
||||
GROUP BY final_prop
|
||||
ORDER BY step_3 DESC,
|
||||
step_2 DESC,
|
||||
step_1 DESC
|
||||
LIMIT 26 SETTINGS readonly=2,
|
||||
max_execution_time=60,
|
||||
allow_experimental_object_type=1,
|
||||
@@ -955,6 +964,9 @@
|
||||
and isNull(max(max_steps))))
|
||||
GROUP BY prop)
|
||||
GROUP BY final_prop
|
||||
ORDER BY step_3 DESC,
|
||||
step_2 DESC,
|
||||
step_1 DESC
|
||||
LIMIT 26 SETTINGS readonly=2,
|
||||
max_execution_time=60,
|
||||
allow_experimental_object_type=1,
|
||||
@@ -1224,6 +1236,9 @@
|
||||
and isNull(max(max_steps))))
|
||||
GROUP BY prop)
|
||||
GROUP BY final_prop
|
||||
ORDER BY step_3 DESC,
|
||||
step_2 DESC,
|
||||
step_1 DESC
|
||||
LIMIT 26 SETTINGS readonly=2,
|
||||
max_execution_time=60,
|
||||
allow_experimental_object_type=1,
|
||||
|
||||
@@ -51,6 +51,8 @@
|
||||
GROUP BY breakdown
|
||||
ORDER BY step_2 DESC, step_1 DESC)
|
||||
GROUP BY final_prop
|
||||
ORDER BY step_2 DESC,
|
||||
step_1 DESC
|
||||
LIMIT 26 SETTINGS join_algorithm='auto',
|
||||
readonly=2,
|
||||
max_execution_time=60,
|
||||
@@ -117,6 +119,8 @@
|
||||
GROUP BY breakdown
|
||||
ORDER BY step_2 DESC, step_1 DESC)
|
||||
GROUP BY final_prop
|
||||
ORDER BY step_2 DESC,
|
||||
step_1 DESC
|
||||
LIMIT 26 SETTINGS join_algorithm='auto',
|
||||
readonly=2,
|
||||
max_execution_time=60,
|
||||
|
||||
@@ -170,7 +170,7 @@ def funnel_breakdown_test_factory(
|
||||
results = FunnelsQueryRunner(query=query, team=self.team).calculate().results
|
||||
|
||||
self._assert_funnel_breakdown_result_is_correct(
|
||||
results[0],
|
||||
results[2],
|
||||
[
|
||||
FunnelStepResult(name="sign up", breakdown=["Safari", "14"], count=1),
|
||||
FunnelStepResult(name="play movie", breakdown=["Safari", "14"], count=0),
|
||||
@@ -208,7 +208,7 @@ def funnel_breakdown_test_factory(
|
||||
)
|
||||
|
||||
self._assert_funnel_breakdown_result_is_correct(
|
||||
results[2],
|
||||
results[0],
|
||||
[
|
||||
FunnelStepResult(name="sign up", breakdown=["Chrome", "95"], count=1),
|
||||
FunnelStepResult(
|
||||
@@ -1115,20 +1115,6 @@ def funnel_breakdown_test_factory(
|
||||
|
||||
self._assert_funnel_breakdown_result_is_correct(
|
||||
results[0],
|
||||
[
|
||||
FunnelStepResult(name="sign up", count=1, breakdown=["Chrome"]),
|
||||
FunnelStepResult(name="play movie", count=0, breakdown=["Chrome"]),
|
||||
],
|
||||
)
|
||||
|
||||
self.assertCountEqual(
|
||||
self._get_actor_ids_at_step(filters, 1, "Chrome"),
|
||||
[people["person1"].uuid],
|
||||
)
|
||||
self.assertCountEqual(self._get_actor_ids_at_step(filters, 2, "Chrome"), [])
|
||||
|
||||
self._assert_funnel_breakdown_result_is_correct(
|
||||
results[1],
|
||||
[
|
||||
FunnelStepResult(name="sign up", count=1, breakdown=["Safari"]),
|
||||
FunnelStepResult(
|
||||
@@ -1150,6 +1136,20 @@ def funnel_breakdown_test_factory(
|
||||
[people["person1"].uuid],
|
||||
)
|
||||
|
||||
self._assert_funnel_breakdown_result_is_correct(
|
||||
results[1],
|
||||
[
|
||||
FunnelStepResult(name="sign up", count=1, breakdown=["Chrome"]),
|
||||
FunnelStepResult(name="play movie", count=0, breakdown=["Chrome"]),
|
||||
],
|
||||
)
|
||||
|
||||
self.assertCountEqual(
|
||||
self._get_actor_ids_at_step(filters, 1, "Chrome"),
|
||||
[people["person1"].uuid],
|
||||
)
|
||||
self.assertCountEqual(self._get_actor_ids_at_step(filters, 2, "Chrome"), [])
|
||||
|
||||
@also_test_with_materialized_columns(person_properties=["key"], verify_no_jsonextract=False)
|
||||
def test_funnel_cohort_breakdown(self):
|
||||
# This caused some issues with SQL parsing
|
||||
|
||||
@@ -168,12 +168,16 @@ class TestFunnelBreakdownsByCurrentURL(ClickhouseTestMixin, APIBaseTest):
|
||||
)
|
||||
)
|
||||
|
||||
assert actual == [
|
||||
("watched movie", 2, ["/"]),
|
||||
("terminate funnel", 2, ["/"]),
|
||||
("watched movie", 2, ["/home"]),
|
||||
("terminate funnel", 2, ["/home"]),
|
||||
]
|
||||
sk = lambda x: (x[1], x[2][0], x[0])
|
||||
assert sorted(actual, key=sk) == sorted(
|
||||
[
|
||||
("watched movie", 2, ["/"]),
|
||||
("terminate funnel", 2, ["/"]),
|
||||
("watched movie", 2, ["/home"]),
|
||||
("terminate funnel", 2, ["/home"]),
|
||||
],
|
||||
key=sk,
|
||||
)
|
||||
|
||||
@snapshot_clickhouse_queries
|
||||
def test_breakdown_by_current_url(self) -> None:
|
||||
@@ -196,9 +200,13 @@ class TestFunnelBreakdownsByCurrentURL(ClickhouseTestMixin, APIBaseTest):
|
||||
)
|
||||
)
|
||||
|
||||
assert actual == [
|
||||
("watched movie", 2, ["https://example.com/home"]),
|
||||
("terminate funnel", 2, ["https://example.com/home"]),
|
||||
("watched movie", 2, ["https://example.com"]),
|
||||
("terminate funnel", 2, ["https://example.com"]),
|
||||
]
|
||||
sk = lambda x: (x[1], x[2][0], x[0])
|
||||
assert sorted(actual, key=sk) == sorted(
|
||||
[
|
||||
("watched movie", 2, ["https://example.com/home"]),
|
||||
("terminate funnel", 2, ["https://example.com/home"]),
|
||||
("watched movie", 2, ["https://example.com"]),
|
||||
("terminate funnel", 2, ["https://example.com"]),
|
||||
],
|
||||
key=sk,
|
||||
)
|
||||
|
||||
@@ -106,40 +106,6 @@ class BaseTestFunnelStrictStepsBreakdown(
|
||||
|
||||
assert_funnel_results_equal(
|
||||
results[0],
|
||||
[
|
||||
{
|
||||
"action_id": "sign up",
|
||||
"name": "sign up",
|
||||
"custom_name": None,
|
||||
"order": 0,
|
||||
"people": [],
|
||||
"count": 1,
|
||||
"type": "events",
|
||||
"average_conversion_time": None,
|
||||
"median_conversion_time": None,
|
||||
"breakdown": ["Chrome"],
|
||||
"breakdown_value": ["Chrome"],
|
||||
},
|
||||
{
|
||||
"action_id": "play movie",
|
||||
"name": "play movie",
|
||||
"custom_name": None,
|
||||
"order": 1,
|
||||
"people": [],
|
||||
"count": 0,
|
||||
"type": "events",
|
||||
"average_conversion_time": None,
|
||||
"median_conversion_time": None,
|
||||
"breakdown": ["Chrome"],
|
||||
"breakdown_value": ["Chrome"],
|
||||
},
|
||||
],
|
||||
)
|
||||
self.assertCountEqual(self._get_actor_ids_at_step(filters, 1, ["Chrome"]), [people["person1"].uuid])
|
||||
self.assertCountEqual(self._get_actor_ids_at_step(filters, 2, ["Chrome"]), [])
|
||||
|
||||
assert_funnel_results_equal(
|
||||
results[1],
|
||||
[
|
||||
{
|
||||
"action_id": "sign up",
|
||||
@@ -172,6 +138,40 @@ class BaseTestFunnelStrictStepsBreakdown(
|
||||
self.assertCountEqual(self._get_actor_ids_at_step(filters, 1, ["Safari"]), [people["person2"].uuid])
|
||||
self.assertCountEqual(self._get_actor_ids_at_step(filters, 2, ["Safari"]), [people["person2"].uuid])
|
||||
|
||||
assert_funnel_results_equal(
|
||||
results[1],
|
||||
[
|
||||
{
|
||||
"action_id": "sign up",
|
||||
"name": "sign up",
|
||||
"custom_name": None,
|
||||
"order": 0,
|
||||
"people": [],
|
||||
"count": 1,
|
||||
"type": "events",
|
||||
"average_conversion_time": None,
|
||||
"median_conversion_time": None,
|
||||
"breakdown": ["Chrome"],
|
||||
"breakdown_value": ["Chrome"],
|
||||
},
|
||||
{
|
||||
"action_id": "play movie",
|
||||
"name": "play movie",
|
||||
"custom_name": None,
|
||||
"order": 1,
|
||||
"people": [],
|
||||
"count": 0,
|
||||
"type": "events",
|
||||
"average_conversion_time": None,
|
||||
"median_conversion_time": None,
|
||||
"breakdown": ["Chrome"],
|
||||
"breakdown_value": ["Chrome"],
|
||||
},
|
||||
],
|
||||
)
|
||||
self.assertCountEqual(self._get_actor_ids_at_step(filters, 1, ["Chrome"]), [people["person1"].uuid])
|
||||
self.assertCountEqual(self._get_actor_ids_at_step(filters, 2, ["Chrome"]), [])
|
||||
|
||||
|
||||
class BaseTestStrictFunnelGroupBreakdown(
|
||||
ClickhouseTestMixin,
|
||||
|
||||
@@ -97,40 +97,6 @@ class BaseTestFunnelUnorderedStepsBreakdown(
|
||||
|
||||
assert_funnel_results_equal(
|
||||
results[0],
|
||||
[
|
||||
{
|
||||
"action_id": None,
|
||||
"name": "Completed 1 step",
|
||||
"custom_name": None,
|
||||
"order": 0,
|
||||
"people": [],
|
||||
"count": 1,
|
||||
"type": "events",
|
||||
"average_conversion_time": None,
|
||||
"median_conversion_time": None,
|
||||
"breakdown": ["Chrome"],
|
||||
"breakdown_value": ["Chrome"],
|
||||
},
|
||||
{
|
||||
"action_id": None,
|
||||
"name": "Completed 2 steps",
|
||||
"custom_name": None,
|
||||
"order": 1,
|
||||
"people": [],
|
||||
"count": 0,
|
||||
"type": "events",
|
||||
"average_conversion_time": None,
|
||||
"median_conversion_time": None,
|
||||
"breakdown": ["Chrome"],
|
||||
"breakdown_value": ["Chrome"],
|
||||
},
|
||||
],
|
||||
)
|
||||
self.assertCountEqual(self._get_actor_ids_at_step(filters, 1, ["Chrome"]), [person1.uuid])
|
||||
self.assertCountEqual(self._get_actor_ids_at_step(filters, 2, ["Chrome"]), [])
|
||||
|
||||
assert_funnel_results_equal(
|
||||
results[1],
|
||||
[
|
||||
{
|
||||
"action_id": None,
|
||||
@@ -162,6 +128,39 @@ class BaseTestFunnelUnorderedStepsBreakdown(
|
||||
)
|
||||
self.assertCountEqual(self._get_actor_ids_at_step(filters, 1, ["Safari"]), [person1.uuid])
|
||||
self.assertCountEqual(self._get_actor_ids_at_step(filters, 2, ["Safari"]), [person1.uuid])
|
||||
assert_funnel_results_equal(
|
||||
results[1],
|
||||
[
|
||||
{
|
||||
"action_id": None,
|
||||
"name": "Completed 1 step",
|
||||
"custom_name": None,
|
||||
"order": 0,
|
||||
"people": [],
|
||||
"count": 1,
|
||||
"type": "events",
|
||||
"average_conversion_time": None,
|
||||
"median_conversion_time": None,
|
||||
"breakdown": ["Chrome"],
|
||||
"breakdown_value": ["Chrome"],
|
||||
},
|
||||
{
|
||||
"action_id": None,
|
||||
"name": "Completed 2 steps",
|
||||
"custom_name": None,
|
||||
"order": 1,
|
||||
"people": [],
|
||||
"count": 0,
|
||||
"type": "events",
|
||||
"average_conversion_time": None,
|
||||
"median_conversion_time": None,
|
||||
"breakdown": ["Chrome"],
|
||||
"breakdown_value": ["Chrome"],
|
||||
},
|
||||
],
|
||||
)
|
||||
self.assertCountEqual(self._get_actor_ids_at_step(filters, 1, ["Chrome"]), [person1.uuid])
|
||||
self.assertCountEqual(self._get_actor_ids_at_step(filters, 2, ["Chrome"]), [])
|
||||
|
||||
def test_funnel_step_breakdown_with_step_attribution(self):
|
||||
# overridden from factory, since with no order, step one is step zero, and vice versa
|
||||
|
||||
@@ -722,6 +722,16 @@ class ClickhouseFunnelBase(ABC):
|
||||
select_clause += f", groupArray(10)(final_matching_event) as final_matching_events"
|
||||
return select_clause
|
||||
|
||||
@staticmethod
|
||||
def _order_by(max_steps: int):
|
||||
return "ORDER BY " + ",".join([f"step_{i + 1} DESC" for i in reversed(range(max_steps))])
|
||||
|
||||
def _get_limit(self):
|
||||
if self._filter.limit:
|
||||
return f"LIMIT {self._filter.limit}"
|
||||
# TODO figure out some good default limit
|
||||
return "LIMIT 1000"
|
||||
|
||||
def get_query(self) -> str:
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
@@ -36,6 +36,8 @@ class ClickhouseFunnel(ClickhouseFunnelBase):
|
||||
SELECT {self._get_count_columns(max_steps)} {self._get_step_time_avgs(max_steps)} {self._get_step_time_median(max_steps)} {breakdown_clause} FROM (
|
||||
{self.get_step_counts_query()}
|
||||
) {'GROUP BY prop' if breakdown_clause != '' else ''}
|
||||
{self._order_by(max_steps) if breakdown_clause != '' else ''}
|
||||
{self._get_limit() if breakdown_clause != '' else ''}
|
||||
"""
|
||||
|
||||
def get_step_counts_query(self):
|
||||
|
||||
@@ -13,6 +13,8 @@ class ClickhouseFunnelStrict(ClickhouseFunnelBase):
|
||||
SELECT {self._get_count_columns(max_steps)} {self._get_step_time_avgs(max_steps)} {self._get_step_time_median(max_steps)} {breakdown_clause} FROM (
|
||||
{self.get_step_counts_query()}
|
||||
) {'GROUP BY prop' if breakdown_clause != '' else ''}
|
||||
{self._order_by(max_steps) if breakdown_clause != '' else ''}
|
||||
{self._get_limit() if breakdown_clause != '' else ''}
|
||||
"""
|
||||
|
||||
def get_step_counts_query(self):
|
||||
|
||||
@@ -66,6 +66,8 @@ class ClickhouseFunnelUnordered(ClickhouseFunnelBase):
|
||||
SELECT {self._get_count_columns(max_steps)} {self._get_step_time_avgs(max_steps)} {self._get_step_time_median(max_steps)} {breakdown_clause} FROM (
|
||||
{self.get_step_counts_query()}
|
||||
) {'GROUP BY prop' if breakdown_clause != '' else ''}
|
||||
{self._order_by(max_steps) if breakdown_clause != '' else ''}
|
||||
{self._get_limit() if breakdown_clause != '' else ''}
|
||||
"""
|
||||
|
||||
def get_step_counts_query(self):
|
||||
|
||||
@@ -96,6 +96,9 @@
|
||||
prop
|
||||
HAVING steps = max(max_steps))
|
||||
GROUP BY prop
|
||||
ORDER BY step_2 DESC,
|
||||
step_1 DESC
|
||||
LIMIT 100
|
||||
'''
|
||||
# ---
|
||||
# name: TestBreakdownsByCurrentURL.test_breakdown_by_pathname
|
||||
@@ -195,5 +198,8 @@
|
||||
prop
|
||||
HAVING steps = max(max_steps))
|
||||
GROUP BY prop
|
||||
ORDER BY step_2 DESC,
|
||||
step_1 DESC
|
||||
LIMIT 100
|
||||
'''
|
||||
# ---
|
||||
|
||||
@@ -1018,81 +1018,6 @@
|
||||
HAVING steps = max(max_steps))
|
||||
'''
|
||||
# ---
|
||||
# name: TestFOSSFunnel.test_funnel_with_static_cohort_step_filter.1
|
||||
'''
|
||||
|
||||
SELECT countIf(steps = 1) step_1,
|
||||
countIf(steps = 2) step_2,
|
||||
avg(step_1_average_conversion_time_inner) step_1_average_conversion_time,
|
||||
median(step_1_median_conversion_time_inner) step_1_median_conversion_time
|
||||
FROM
|
||||
(SELECT aggregation_target,
|
||||
steps,
|
||||
avg(step_1_conversion_time) step_1_average_conversion_time_inner,
|
||||
median(step_1_conversion_time) step_1_median_conversion_time_inner
|
||||
FROM
|
||||
(SELECT aggregation_target,
|
||||
steps,
|
||||
max(steps) over (PARTITION BY aggregation_target) as max_steps,
|
||||
step_1_conversion_time
|
||||
FROM
|
||||
(SELECT *,
|
||||
if(latest_0 <= latest_1
|
||||
AND latest_1 <= latest_0 + INTERVAL 14 DAY, 2, 1) AS steps ,
|
||||
if(isNotNull(latest_1)
|
||||
AND latest_1 <= latest_0 + INTERVAL 14 DAY, dateDiff('second', toDateTime(latest_0), toDateTime(latest_1)), NULL) step_1_conversion_time
|
||||
FROM
|
||||
(SELECT aggregation_target, timestamp, step_0,
|
||||
latest_0,
|
||||
step_1,
|
||||
min(latest_1) over (PARTITION by aggregation_target
|
||||
ORDER BY timestamp DESC ROWS BETWEEN UNBOUNDED PRECEDING AND 0 PRECEDING) latest_1
|
||||
FROM
|
||||
(SELECT e.timestamp as timestamp,
|
||||
if(notEmpty(pdi.distinct_id), pdi.person_id, e.person_id) as aggregation_target,
|
||||
if(notEmpty(pdi.distinct_id), pdi.person_id, e.person_id) as person_id,
|
||||
if(event = 'user signed up'
|
||||
AND (person_id IN
|
||||
(SELECT person_id as id
|
||||
FROM person_static_cohort
|
||||
WHERE cohort_id = 99999
|
||||
AND team_id = 99999)), 1, 0) as step_0,
|
||||
if(step_0 = 1, timestamp, null) as latest_0,
|
||||
if(event = 'paid', 1, 0) as step_1,
|
||||
if(step_1 = 1, timestamp, null) as latest_1
|
||||
FROM events e
|
||||
LEFT OUTER JOIN
|
||||
(SELECT distinct_id,
|
||||
argMax(person_id, version) as person_id
|
||||
FROM person_distinct_id2
|
||||
WHERE team_id = 99999
|
||||
AND distinct_id IN
|
||||
(SELECT distinct_id
|
||||
FROM events
|
||||
WHERE team_id = 99999
|
||||
AND event IN ['paid', 'user signed up']
|
||||
AND toTimeZone(timestamp, 'UTC') >= toDateTime('2020-01-01 00:00:00', 'UTC')
|
||||
AND toTimeZone(timestamp, 'UTC') <= toDateTime('2020-01-14 23:59:59', 'UTC') )
|
||||
GROUP BY distinct_id
|
||||
HAVING argMax(is_deleted, version) = 0) AS pdi ON e.distinct_id = pdi.distinct_id
|
||||
INNER JOIN
|
||||
(SELECT id
|
||||
FROM person
|
||||
WHERE team_id = 99999
|
||||
GROUP BY id
|
||||
HAVING max(is_deleted) = 0 SETTINGS optimize_aggregation_in_order = 1) person ON person.id = pdi.person_id
|
||||
WHERE team_id = 99999
|
||||
AND event IN ['paid', 'user signed up']
|
||||
AND toTimeZone(timestamp, 'UTC') >= toDateTime('2020-01-01 00:00:00', 'UTC')
|
||||
AND toTimeZone(timestamp, 'UTC') <= toDateTime('2020-01-14 23:59:59', 'UTC')
|
||||
AND (step_0 = 1
|
||||
OR step_1 = 1) ))
|
||||
WHERE step_0 = 1 ))
|
||||
GROUP BY aggregation_target,
|
||||
steps
|
||||
HAVING steps = max(max_steps))
|
||||
'''
|
||||
# ---
|
||||
# name: TestFOSSFunnel.test_timezones
|
||||
'''
|
||||
|
||||
@@ -1251,6 +1176,9 @@
|
||||
prop
|
||||
HAVING steps = max(max_steps))
|
||||
GROUP BY prop
|
||||
ORDER BY step_2 DESC,
|
||||
step_1 DESC
|
||||
LIMIT 100
|
||||
'''
|
||||
# ---
|
||||
# name: TestFunnelBreakdown.test_funnel_breakdown_correct_breakdown_props_are_chosen_for_step
|
||||
@@ -1352,6 +1280,9 @@
|
||||
prop
|
||||
HAVING steps = max(max_steps))
|
||||
GROUP BY prop
|
||||
ORDER BY step_2 DESC,
|
||||
step_1 DESC
|
||||
LIMIT 100
|
||||
'''
|
||||
# ---
|
||||
# name: TestFunnelBreakdown.test_funnel_step_multiple_breakdown_snapshot
|
||||
@@ -1447,5 +1378,8 @@
|
||||
prop
|
||||
HAVING steps = max(max_steps))
|
||||
GROUP BY prop
|
||||
ORDER BY step_2 DESC,
|
||||
step_1 DESC
|
||||
LIMIT 100
|
||||
'''
|
||||
# ---
|
||||
|
||||
@@ -89,6 +89,9 @@
|
||||
prop
|
||||
HAVING steps = max(max_steps))
|
||||
GROUP BY prop
|
||||
ORDER BY step_2 DESC,
|
||||
step_1 DESC
|
||||
LIMIT 100
|
||||
'''
|
||||
# ---
|
||||
# name: TestFunnelStrictStepsBreakdown.test_funnel_breakdown_correct_breakdown_props_are_chosen_for_step
|
||||
@@ -186,6 +189,9 @@
|
||||
prop
|
||||
HAVING steps = max(max_steps))
|
||||
GROUP BY prop
|
||||
ORDER BY step_2 DESC,
|
||||
step_1 DESC
|
||||
LIMIT 100
|
||||
'''
|
||||
# ---
|
||||
# name: TestFunnelStrictStepsBreakdown.test_funnel_step_multiple_breakdown_snapshot
|
||||
@@ -277,5 +283,8 @@
|
||||
prop
|
||||
HAVING steps = max(max_steps))
|
||||
GROUP BY prop
|
||||
ORDER BY step_2 DESC,
|
||||
step_1 DESC
|
||||
LIMIT 100
|
||||
'''
|
||||
# ---
|
||||
|
||||
@@ -160,6 +160,9 @@
|
||||
prop
|
||||
HAVING steps = max(max_steps))
|
||||
GROUP BY prop
|
||||
ORDER BY step_2 DESC,
|
||||
step_1 DESC
|
||||
LIMIT 100
|
||||
'''
|
||||
# ---
|
||||
# name: TestFunnelUnorderedStepsBreakdown.test_funnel_breakdown_correct_breakdown_props_are_chosen_for_step
|
||||
@@ -331,6 +334,9 @@
|
||||
prop
|
||||
HAVING steps = max(max_steps))
|
||||
GROUP BY prop
|
||||
ORDER BY step_2 DESC,
|
||||
step_1 DESC
|
||||
LIMIT 100
|
||||
'''
|
||||
# ---
|
||||
# name: TestFunnelUnorderedStepsBreakdown.test_funnel_step_multiple_breakdown_snapshot
|
||||
@@ -492,5 +498,8 @@
|
||||
prop
|
||||
HAVING steps = max(max_steps))
|
||||
GROUP BY prop
|
||||
ORDER BY step_2 DESC,
|
||||
step_1 DESC
|
||||
LIMIT 100
|
||||
'''
|
||||
# ---
|
||||
|
||||
@@ -150,7 +150,7 @@ def funnel_breakdown_test_factory(Funnel, FunnelPerson, _create_event, _create_a
|
||||
result = funnel.run()
|
||||
|
||||
self._assert_funnel_breakdown_result_is_correct(
|
||||
result[0],
|
||||
result[2],
|
||||
[
|
||||
FunnelStepResult(name="sign up", breakdown=["Safari", "14"], count=1),
|
||||
FunnelStepResult(name="play movie", breakdown=["Safari", "14"], count=0),
|
||||
@@ -188,7 +188,7 @@ def funnel_breakdown_test_factory(Funnel, FunnelPerson, _create_event, _create_a
|
||||
)
|
||||
|
||||
self._assert_funnel_breakdown_result_is_correct(
|
||||
result[2],
|
||||
result[0],
|
||||
[
|
||||
FunnelStepResult(name="sign up", breakdown=["Chrome", "95"], count=1),
|
||||
FunnelStepResult(
|
||||
@@ -306,6 +306,7 @@ def funnel_breakdown_test_factory(Funnel, FunnelPerson, _create_event, _create_a
|
||||
self._get_actor_ids_at_step(filter, 2, "Chrome"),
|
||||
[people["person1"].uuid],
|
||||
)
|
||||
|
||||
self._assert_funnel_breakdown_result_is_correct(
|
||||
result[1],
|
||||
[
|
||||
@@ -1082,7 +1083,7 @@ def funnel_breakdown_test_factory(Funnel, FunnelPerson, _create_event, _create_a
|
||||
self.assertEqual(len(result), 2)
|
||||
|
||||
self._assert_funnel_breakdown_result_is_correct(
|
||||
result[0],
|
||||
result[1],
|
||||
[
|
||||
FunnelStepResult(name="sign up", count=1, breakdown=["Chrome"]),
|
||||
FunnelStepResult(name="play movie", count=0, breakdown=["Chrome"]),
|
||||
@@ -1096,7 +1097,7 @@ def funnel_breakdown_test_factory(Funnel, FunnelPerson, _create_event, _create_a
|
||||
self.assertCountEqual(self._get_actor_ids_at_step(filter, 2, "Chrome"), [])
|
||||
|
||||
self._assert_funnel_breakdown_result_is_correct(
|
||||
result[1],
|
||||
result[0],
|
||||
[
|
||||
FunnelStepResult(name="sign up", count=1, breakdown=["Safari"]),
|
||||
FunnelStepResult(
|
||||
|
||||
@@ -168,12 +168,15 @@ class TestBreakdownsByCurrentURL(ClickhouseTestMixin, APIBaseTest):
|
||||
funnel_step["breakdown"],
|
||||
)
|
||||
)
|
||||
assert actual == [
|
||||
# The query to get results does not order them.
|
||||
want = [
|
||||
("watched movie", 2, ["/"]),
|
||||
("terminate funnel", 2, ["/"]),
|
||||
("watched movie", 2, ["/home"]),
|
||||
("terminate funnel", 2, ["/home"]),
|
||||
]
|
||||
key_func = lambda x: (x[2][0], x[0])
|
||||
assert sorted(want, key=key_func) == sorted(actual, key=key_func)
|
||||
|
||||
@snapshot_clickhouse_queries
|
||||
def test_breakdown_by_current_url(self) -> None:
|
||||
@@ -196,9 +199,11 @@ class TestBreakdownsByCurrentURL(ClickhouseTestMixin, APIBaseTest):
|
||||
)
|
||||
)
|
||||
|
||||
assert actual == [
|
||||
want = [
|
||||
("watched movie", 2, ["https://example.com/home"]),
|
||||
("terminate funnel", 2, ["https://example.com/home"]),
|
||||
("watched movie", 2, ["https://example.com"]),
|
||||
("terminate funnel", 2, ["https://example.com"]),
|
||||
]
|
||||
key_func = lambda x: (x[2][0], x[0])
|
||||
assert sorted(want, key=key_func) == sorted(actual, key=key_func)
|
||||
|
||||
@@ -95,42 +95,9 @@ class TestFunnelStrictStepsBreakdown(
|
||||
)
|
||||
|
||||
result = funnel.run()
|
||||
assert_funnel_results_equal(
|
||||
result[0],
|
||||
[
|
||||
{
|
||||
"action_id": "sign up",
|
||||
"name": "sign up",
|
||||
"custom_name": None,
|
||||
"order": 0,
|
||||
"people": [],
|
||||
"count": 1,
|
||||
"type": "events",
|
||||
"average_conversion_time": None,
|
||||
"median_conversion_time": None,
|
||||
"breakdown": ["Chrome"],
|
||||
"breakdown_value": ["Chrome"],
|
||||
},
|
||||
{
|
||||
"action_id": "play movie",
|
||||
"name": "play movie",
|
||||
"custom_name": None,
|
||||
"order": 1,
|
||||
"people": [],
|
||||
"count": 0,
|
||||
"type": "events",
|
||||
"average_conversion_time": None,
|
||||
"median_conversion_time": None,
|
||||
"breakdown": ["Chrome"],
|
||||
"breakdown_value": ["Chrome"],
|
||||
},
|
||||
],
|
||||
)
|
||||
self.assertCountEqual(self._get_actor_ids_at_step(filter, 1, ["Chrome"]), [people["person1"].uuid])
|
||||
self.assertCountEqual(self._get_actor_ids_at_step(filter, 2, ["Chrome"]), [])
|
||||
|
||||
assert_funnel_results_equal(
|
||||
result[1],
|
||||
result[0],
|
||||
[
|
||||
{
|
||||
"action_id": "sign up",
|
||||
@@ -163,6 +130,40 @@ class TestFunnelStrictStepsBreakdown(
|
||||
self.assertCountEqual(self._get_actor_ids_at_step(filter, 1, ["Safari"]), [people["person2"].uuid])
|
||||
self.assertCountEqual(self._get_actor_ids_at_step(filter, 2, ["Safari"]), [people["person2"].uuid])
|
||||
|
||||
assert_funnel_results_equal(
|
||||
result[1],
|
||||
[
|
||||
{
|
||||
"action_id": "sign up",
|
||||
"name": "sign up",
|
||||
"custom_name": None,
|
||||
"order": 0,
|
||||
"people": [],
|
||||
"count": 1,
|
||||
"type": "events",
|
||||
"average_conversion_time": None,
|
||||
"median_conversion_time": None,
|
||||
"breakdown": ["Chrome"],
|
||||
"breakdown_value": ["Chrome"],
|
||||
},
|
||||
{
|
||||
"action_id": "play movie",
|
||||
"name": "play movie",
|
||||
"custom_name": None,
|
||||
"order": 1,
|
||||
"people": [],
|
||||
"count": 0,
|
||||
"type": "events",
|
||||
"average_conversion_time": None,
|
||||
"median_conversion_time": None,
|
||||
"breakdown": ["Chrome"],
|
||||
"breakdown_value": ["Chrome"],
|
||||
},
|
||||
],
|
||||
)
|
||||
self.assertCountEqual(self._get_actor_ids_at_step(filter, 1, ["Chrome"]), [people["person1"].uuid])
|
||||
self.assertCountEqual(self._get_actor_ids_at_step(filter, 2, ["Chrome"]), [])
|
||||
|
||||
|
||||
class TestFunnelStrictStepsConversionTime(
|
||||
ClickhouseTestMixin,
|
||||
|
||||
@@ -88,42 +88,9 @@ class TestFunnelUnorderedStepsBreakdown(
|
||||
)
|
||||
|
||||
result = funnel.run()
|
||||
assert_funnel_results_equal(
|
||||
result[0],
|
||||
[
|
||||
{
|
||||
"action_id": None,
|
||||
"name": "Completed 1 step",
|
||||
"custom_name": None,
|
||||
"order": 0,
|
||||
"people": [],
|
||||
"count": 1,
|
||||
"type": "events",
|
||||
"average_conversion_time": None,
|
||||
"median_conversion_time": None,
|
||||
"breakdown": ["Chrome"],
|
||||
"breakdown_value": ["Chrome"],
|
||||
},
|
||||
{
|
||||
"action_id": None,
|
||||
"name": "Completed 2 steps",
|
||||
"custom_name": None,
|
||||
"order": 1,
|
||||
"people": [],
|
||||
"count": 0,
|
||||
"type": "events",
|
||||
"average_conversion_time": None,
|
||||
"median_conversion_time": None,
|
||||
"breakdown": ["Chrome"],
|
||||
"breakdown_value": ["Chrome"],
|
||||
},
|
||||
],
|
||||
)
|
||||
self.assertCountEqual(self._get_actor_ids_at_step(filter, 1, ["Chrome"]), [person1.uuid])
|
||||
self.assertCountEqual(self._get_actor_ids_at_step(filter, 2, ["Chrome"]), [])
|
||||
|
||||
assert_funnel_results_equal(
|
||||
result[1],
|
||||
result[0],
|
||||
[
|
||||
{
|
||||
"action_id": None,
|
||||
@@ -156,6 +123,40 @@ class TestFunnelUnorderedStepsBreakdown(
|
||||
self.assertCountEqual(self._get_actor_ids_at_step(filter, 1, ["Safari"]), [person1.uuid])
|
||||
self.assertCountEqual(self._get_actor_ids_at_step(filter, 2, ["Safari"]), [person1.uuid])
|
||||
|
||||
assert_funnel_results_equal(
|
||||
result[1],
|
||||
[
|
||||
{
|
||||
"action_id": None,
|
||||
"name": "Completed 1 step",
|
||||
"custom_name": None,
|
||||
"order": 0,
|
||||
"people": [],
|
||||
"count": 1,
|
||||
"type": "events",
|
||||
"average_conversion_time": None,
|
||||
"median_conversion_time": None,
|
||||
"breakdown": ["Chrome"],
|
||||
"breakdown_value": ["Chrome"],
|
||||
},
|
||||
{
|
||||
"action_id": None,
|
||||
"name": "Completed 2 steps",
|
||||
"custom_name": None,
|
||||
"order": 1,
|
||||
"people": [],
|
||||
"count": 0,
|
||||
"type": "events",
|
||||
"average_conversion_time": None,
|
||||
"median_conversion_time": None,
|
||||
"breakdown": ["Chrome"],
|
||||
"breakdown_value": ["Chrome"],
|
||||
},
|
||||
],
|
||||
)
|
||||
self.assertCountEqual(self._get_actor_ids_at_step(filter, 1, ["Chrome"]), [person1.uuid])
|
||||
self.assertCountEqual(self._get_actor_ids_at_step(filter, 2, ["Chrome"]), [])
|
||||
|
||||
def test_funnel_step_breakdown_with_step_attribution(self):
|
||||
# overridden from factory, since with no order, step one is step zero, and vice versa
|
||||
|
||||
|
||||
@@ -1102,108 +1102,6 @@
|
||||
# ---
|
||||
# name: TestRevenueAnalyticsTopCustomersQueryRunner.test_with_data_with_managed_viewsets_ff
|
||||
'''
|
||||
SELECT inner.customer_id AS customer_id,
|
||||
revenue_analytics_customer.name AS name,
|
||||
inner.amount AS amount,
|
||||
inner.month AS month
|
||||
FROM
|
||||
(SELECT revenue_analytics_revenue_item.customer_id AS customer_id,
|
||||
toStartOfMonth(toTimeZone(revenue_analytics_revenue_item.timestamp, 'UTC')) AS month,
|
||||
sum(revenue_analytics_revenue_item.amount) AS amount
|
||||
FROM
|
||||
(SELECT toString(events.uuid) AS id,
|
||||
toString(events.uuid) AS invoice_item_id,
|
||||
'revenue_analytics.events.purchase' AS source_label,
|
||||
toTimeZone(events.timestamp, 'UTC') AS timestamp,
|
||||
timestamp AS created_at,
|
||||
isNotNull(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, 'subscription'), ''), 'null'), '^"|"$', '')) AS is_recurring,
|
||||
replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, 'product'), ''), 'null'), '^"|"$', '') AS product_id,
|
||||
toString(if(not(empty(events__override.distinct_id)), events__override.person_id, events.person_id)) AS customer_id,
|
||||
events.`$group_0` AS group_0_key,
|
||||
events.`$group_1` AS group_1_key,
|
||||
events.`$group_2` AS group_2_key,
|
||||
events.`$group_3` AS group_3_key,
|
||||
events.`$group_4` AS group_4_key,
|
||||
NULL AS invoice_id,
|
||||
replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, 'subscription'), ''), 'null'), '^"|"$', '') AS subscription_id,
|
||||
toString(events.`$session_id`) AS session_id,
|
||||
events.event AS event_name,
|
||||
replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, 'coupon'), ''), 'null'), '^"|"$', '') AS coupon,
|
||||
coupon AS coupon_id,
|
||||
upper(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, 'currency'), ''), 'null'), '^"|"$', '')) AS original_currency,
|
||||
accurateCastOrNull(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, 'revenue'), ''), 'null'), '^"|"$', ''), 'Decimal64(10)') AS original_amount,
|
||||
1 AS enable_currency_aware_divider,
|
||||
if(enable_currency_aware_divider, accurateCastOrNull(1, 'Decimal64(10)'), accurateCastOrNull(100, 'Decimal64(10)')) AS currency_aware_divider,
|
||||
divideDecimal(original_amount, currency_aware_divider) AS currency_aware_amount,
|
||||
'GBP' AS currency,
|
||||
if(isNull(upper(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, 'currency'), ''), 'null'), '^"|"$', ''))), accurateCastOrNull(currency_aware_amount, 'Decimal64(10)'), if(equals(upper(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, 'currency'), ''), 'null'), '^"|"$', '')), 'GBP'), toDecimal64(currency_aware_amount, 10), if(dictGetOrDefault(`posthog_test`.`exchange_rate_dict`, 'rate', upper(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, 'currency'), ''), 'null'), '^"|"$', '')), toDate(toTimeZone(events.timestamp, 'UTC')), toDecimal64(0, 10)) = 0, toDecimal64(0, 10), multiplyDecimal(divideDecimal(toDecimal64(currency_aware_amount, 10), dictGetOrDefault(`posthog_test`.`exchange_rate_dict`, 'rate', upper(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, 'currency'), ''), 'null'), '^"|"$', '')), toDate(toTimeZone(events.timestamp, 'UTC')), toDecimal64(0, 10))), dictGetOrDefault(`posthog_test`.`exchange_rate_dict`, 'rate', 'GBP', toDate(toTimeZone(events.timestamp, 'UTC')), toDecimal64(0, 10)))))) AS amount
|
||||
FROM events
|
||||
LEFT OUTER JOIN
|
||||
(SELECT argMax(person_distinct_id_overrides.person_id, person_distinct_id_overrides.version) AS person_id,
|
||||
person_distinct_id_overrides.distinct_id AS distinct_id
|
||||
FROM person_distinct_id_overrides
|
||||
WHERE equals(person_distinct_id_overrides.team_id, 99999)
|
||||
GROUP BY person_distinct_id_overrides.distinct_id
|
||||
HAVING ifNull(equals(argMax(person_distinct_id_overrides.is_deleted, person_distinct_id_overrides.version), 0), 0) SETTINGS optimize_aggregation_in_order=1) AS events__override ON equals(events.distinct_id, events__override.distinct_id)
|
||||
WHERE and(equals(events.team_id, 99999), and(equals(events.event, 'purchase'), 1, isNotNull(amount)))
|
||||
ORDER BY timestamp DESC) AS revenue_analytics_revenue_item
|
||||
WHERE and(greaterOrEquals(toTimeZone(revenue_analytics_revenue_item.timestamp, 'UTC'), assumeNotNull(toDateTime('2015-01-01 00:00:00', 'UTC'))), lessOrEquals(toTimeZone(revenue_analytics_revenue_item.timestamp, 'UTC'), assumeNotNull(toDateTime('2025-04-21 23:59:59', 'UTC'))))
|
||||
GROUP BY customer_id,
|
||||
month
|
||||
ORDER BY amount DESC
|
||||
LIMIT 20 BY month) AS inner
|
||||
LEFT JOIN
|
||||
(SELECT toString(persons.id) AS id,
|
||||
'revenue_analytics.events.purchase' AS source_label,
|
||||
persons.created_at AS timestamp,
|
||||
persons.properties___name AS name,
|
||||
persons.properties___email AS email,
|
||||
persons.properties___phone AS phone,
|
||||
persons.properties___address AS address,
|
||||
persons.properties___metadata AS metadata,
|
||||
persons.`properties___$geoip_country_name` AS country,
|
||||
formatDateTime(toStartOfMonth(persons.created_at), '%Y-%m') AS cohort,
|
||||
NULL AS initial_coupon,
|
||||
NULL AS initial_coupon_id
|
||||
FROM
|
||||
(SELECT person.id AS id,
|
||||
replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(person.properties, 'name'), ''), 'null'), '^"|"$', '') AS properties___name,
|
||||
replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(person.properties, 'email'), ''), 'null'), '^"|"$', '') AS properties___email,
|
||||
replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(person.properties, 'phone'), ''), 'null'), '^"|"$', '') AS properties___phone,
|
||||
replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(person.properties, 'address'), ''), 'null'), '^"|"$', '') AS properties___address,
|
||||
replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(person.properties, 'metadata'), ''), 'null'), '^"|"$', '') AS properties___metadata,
|
||||
replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(person.properties, '$geoip_country_name'), ''), 'null'), '^"|"$', '') AS `properties___$geoip_country_name`,
|
||||
toTimeZone(person.created_at, 'UTC') AS created_at
|
||||
FROM person
|
||||
WHERE and(equals(person.team_id, 99999), in(tuple(person.id, person.version),
|
||||
(SELECT person.id AS id, max(person.version) AS version
|
||||
FROM person
|
||||
WHERE equals(person.team_id, 99999)
|
||||
GROUP BY person.id
|
||||
HAVING and(ifNull(equals(argMax(person.is_deleted, person.version), 0), 0), ifNull(less(argMax(toTimeZone(person.created_at, 'UTC'), person.version), plus(now64(6, 'UTC'), toIntervalDay(1))), 0))
|
||||
ORDER BY argMax(toTimeZone(person.created_at, 'UTC'), person.version) DESC))) SETTINGS optimize_aggregation_in_order=1) AS persons
|
||||
INNER JOIN
|
||||
(SELECT DISTINCT events__person.id AS person_id
|
||||
FROM events
|
||||
LEFT OUTER JOIN
|
||||
(SELECT argMax(person_distinct_id_overrides.person_id, person_distinct_id_overrides.version) AS person_id,
|
||||
person_distinct_id_overrides.distinct_id AS distinct_id
|
||||
FROM person_distinct_id_overrides
|
||||
WHERE equals(person_distinct_id_overrides.team_id, 99999)
|
||||
GROUP BY person_distinct_id_overrides.distinct_id
|
||||
HAVING ifNull(equals(argMax(person_distinct_id_overrides.is_deleted, person_distinct_id_overrides.version), 0), 0) SETTINGS optimize_aggregation_in_order=1) AS events__override ON equals(events.distinct_id, events__override.distinct_id)
|
||||
INNER JOIN
|
||||
(SELECT person.id AS id
|
||||
FROM person
|
||||
WHERE equals(person.team_id, 99999)
|
||||
GROUP BY person.id
|
||||
HAVING and(ifNull(equals(argMax(person.is_deleted, person.version), 0), 0), ifNull(less(argMax(toTimeZone(person.created_at, 'UTC'), person.version), plus(now64(6, 'UTC'), toIntervalDay(1))), 0)) SETTINGS optimize_aggregation_in_order=1) AS events__person ON equals(if(not(empty(events__override.distinct_id)), events__override.person_id, events.person_id), events__person.id)
|
||||
WHERE equals(events.team_id, 99999)) AS events ON equals(persons.id, events.person_id)
|
||||
ORDER BY persons.created_at DESC) AS revenue_analytics_customer ON equals(inner.customer_id, revenue_analytics_customer.id)
|
||||
ORDER BY amount DESC
|
||||
LIMIT 20 BY month
|
||||
LIMIT 100
|
||||
UNION ALL
|
||||
SELECT inner.customer_id AS customer_id,
|
||||
revenue_analytics_customer.name AS name,
|
||||
inner.amount AS amount,
|
||||
@@ -1319,6 +1217,108 @@
|
||||
GROUP BY invoice.customer) AS cohort_inner ON equals(cohort_inner.customer_id, outer.id)) AS revenue_analytics_customer ON equals(inner.customer_id, revenue_analytics_customer.id)
|
||||
ORDER BY amount DESC
|
||||
LIMIT 20 BY month
|
||||
LIMIT 100
|
||||
UNION ALL
|
||||
SELECT inner.customer_id AS customer_id,
|
||||
revenue_analytics_customer.name AS name,
|
||||
inner.amount AS amount,
|
||||
inner.month AS month
|
||||
FROM
|
||||
(SELECT revenue_analytics_revenue_item.customer_id AS customer_id,
|
||||
toStartOfMonth(toTimeZone(revenue_analytics_revenue_item.timestamp, 'UTC')) AS month,
|
||||
sum(revenue_analytics_revenue_item.amount) AS amount
|
||||
FROM
|
||||
(SELECT toString(events.uuid) AS id,
|
||||
toString(events.uuid) AS invoice_item_id,
|
||||
'revenue_analytics.events.purchase' AS source_label,
|
||||
toTimeZone(events.timestamp, 'UTC') AS timestamp,
|
||||
timestamp AS created_at,
|
||||
isNotNull(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, 'subscription'), ''), 'null'), '^"|"$', '')) AS is_recurring,
|
||||
replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, 'product'), ''), 'null'), '^"|"$', '') AS product_id,
|
||||
toString(if(not(empty(events__override.distinct_id)), events__override.person_id, events.person_id)) AS customer_id,
|
||||
events.`$group_0` AS group_0_key,
|
||||
events.`$group_1` AS group_1_key,
|
||||
events.`$group_2` AS group_2_key,
|
||||
events.`$group_3` AS group_3_key,
|
||||
events.`$group_4` AS group_4_key,
|
||||
NULL AS invoice_id,
|
||||
replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, 'subscription'), ''), 'null'), '^"|"$', '') AS subscription_id,
|
||||
toString(events.`$session_id`) AS session_id,
|
||||
events.event AS event_name,
|
||||
replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, 'coupon'), ''), 'null'), '^"|"$', '') AS coupon,
|
||||
coupon AS coupon_id,
|
||||
upper(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, 'currency'), ''), 'null'), '^"|"$', '')) AS original_currency,
|
||||
accurateCastOrNull(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, 'revenue'), ''), 'null'), '^"|"$', ''), 'Decimal64(10)') AS original_amount,
|
||||
1 AS enable_currency_aware_divider,
|
||||
if(enable_currency_aware_divider, accurateCastOrNull(1, 'Decimal64(10)'), accurateCastOrNull(100, 'Decimal64(10)')) AS currency_aware_divider,
|
||||
divideDecimal(original_amount, currency_aware_divider) AS currency_aware_amount,
|
||||
'GBP' AS currency,
|
||||
if(isNull(upper(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, 'currency'), ''), 'null'), '^"|"$', ''))), accurateCastOrNull(currency_aware_amount, 'Decimal64(10)'), if(equals(upper(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, 'currency'), ''), 'null'), '^"|"$', '')), 'GBP'), toDecimal64(currency_aware_amount, 10), if(dictGetOrDefault(`posthog_test`.`exchange_rate_dict`, 'rate', upper(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, 'currency'), ''), 'null'), '^"|"$', '')), toDate(toTimeZone(events.timestamp, 'UTC')), toDecimal64(0, 10)) = 0, toDecimal64(0, 10), multiplyDecimal(divideDecimal(toDecimal64(currency_aware_amount, 10), dictGetOrDefault(`posthog_test`.`exchange_rate_dict`, 'rate', upper(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, 'currency'), ''), 'null'), '^"|"$', '')), toDate(toTimeZone(events.timestamp, 'UTC')), toDecimal64(0, 10))), dictGetOrDefault(`posthog_test`.`exchange_rate_dict`, 'rate', 'GBP', toDate(toTimeZone(events.timestamp, 'UTC')), toDecimal64(0, 10)))))) AS amount
|
||||
FROM events
|
||||
LEFT OUTER JOIN
|
||||
(SELECT argMax(person_distinct_id_overrides.person_id, person_distinct_id_overrides.version) AS person_id,
|
||||
person_distinct_id_overrides.distinct_id AS distinct_id
|
||||
FROM person_distinct_id_overrides
|
||||
WHERE equals(person_distinct_id_overrides.team_id, 99999)
|
||||
GROUP BY person_distinct_id_overrides.distinct_id
|
||||
HAVING ifNull(equals(argMax(person_distinct_id_overrides.is_deleted, person_distinct_id_overrides.version), 0), 0) SETTINGS optimize_aggregation_in_order=1) AS events__override ON equals(events.distinct_id, events__override.distinct_id)
|
||||
WHERE and(equals(events.team_id, 99999), and(equals(events.event, 'purchase'), 1, isNotNull(amount)))
|
||||
ORDER BY timestamp DESC) AS revenue_analytics_revenue_item
|
||||
WHERE and(greaterOrEquals(toTimeZone(revenue_analytics_revenue_item.timestamp, 'UTC'), assumeNotNull(toDateTime('2015-01-01 00:00:00', 'UTC'))), lessOrEquals(toTimeZone(revenue_analytics_revenue_item.timestamp, 'UTC'), assumeNotNull(toDateTime('2025-04-21 23:59:59', 'UTC'))))
|
||||
GROUP BY customer_id,
|
||||
month
|
||||
ORDER BY amount DESC
|
||||
LIMIT 20 BY month) AS inner
|
||||
LEFT JOIN
|
||||
(SELECT toString(persons.id) AS id,
|
||||
'revenue_analytics.events.purchase' AS source_label,
|
||||
persons.created_at AS timestamp,
|
||||
persons.properties___name AS name,
|
||||
persons.properties___email AS email,
|
||||
persons.properties___phone AS phone,
|
||||
persons.properties___address AS address,
|
||||
persons.properties___metadata AS metadata,
|
||||
persons.`properties___$geoip_country_name` AS country,
|
||||
formatDateTime(toStartOfMonth(persons.created_at), '%Y-%m') AS cohort,
|
||||
NULL AS initial_coupon,
|
||||
NULL AS initial_coupon_id
|
||||
FROM
|
||||
(SELECT person.id AS id,
|
||||
replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(person.properties, 'name'), ''), 'null'), '^"|"$', '') AS properties___name,
|
||||
replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(person.properties, 'email'), ''), 'null'), '^"|"$', '') AS properties___email,
|
||||
replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(person.properties, 'phone'), ''), 'null'), '^"|"$', '') AS properties___phone,
|
||||
replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(person.properties, 'address'), ''), 'null'), '^"|"$', '') AS properties___address,
|
||||
replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(person.properties, 'metadata'), ''), 'null'), '^"|"$', '') AS properties___metadata,
|
||||
replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(person.properties, '$geoip_country_name'), ''), 'null'), '^"|"$', '') AS `properties___$geoip_country_name`,
|
||||
toTimeZone(person.created_at, 'UTC') AS created_at
|
||||
FROM person
|
||||
WHERE and(equals(person.team_id, 99999), in(tuple(person.id, person.version),
|
||||
(SELECT person.id AS id, max(person.version) AS version
|
||||
FROM person
|
||||
WHERE equals(person.team_id, 99999)
|
||||
GROUP BY person.id
|
||||
HAVING and(ifNull(equals(argMax(person.is_deleted, person.version), 0), 0), ifNull(less(argMax(toTimeZone(person.created_at, 'UTC'), person.version), plus(now64(6, 'UTC'), toIntervalDay(1))), 0))
|
||||
ORDER BY argMax(toTimeZone(person.created_at, 'UTC'), person.version) DESC))) SETTINGS optimize_aggregation_in_order=1) AS persons
|
||||
INNER JOIN
|
||||
(SELECT DISTINCT events__person.id AS person_id
|
||||
FROM events
|
||||
LEFT OUTER JOIN
|
||||
(SELECT argMax(person_distinct_id_overrides.person_id, person_distinct_id_overrides.version) AS person_id,
|
||||
person_distinct_id_overrides.distinct_id AS distinct_id
|
||||
FROM person_distinct_id_overrides
|
||||
WHERE equals(person_distinct_id_overrides.team_id, 99999)
|
||||
GROUP BY person_distinct_id_overrides.distinct_id
|
||||
HAVING ifNull(equals(argMax(person_distinct_id_overrides.is_deleted, person_distinct_id_overrides.version), 0), 0) SETTINGS optimize_aggregation_in_order=1) AS events__override ON equals(events.distinct_id, events__override.distinct_id)
|
||||
INNER JOIN
|
||||
(SELECT person.id AS id
|
||||
FROM person
|
||||
WHERE equals(person.team_id, 99999)
|
||||
GROUP BY person.id
|
||||
HAVING and(ifNull(equals(argMax(person.is_deleted, person.version), 0), 0), ifNull(less(argMax(toTimeZone(person.created_at, 'UTC'), person.version), plus(now64(6, 'UTC'), toIntervalDay(1))), 0)) SETTINGS optimize_aggregation_in_order=1) AS events__person ON equals(if(not(empty(events__override.distinct_id)), events__override.person_id, events.person_id), events__person.id)
|
||||
WHERE equals(events.team_id, 99999)) AS events ON equals(persons.id, events.person_id)
|
||||
ORDER BY persons.created_at DESC) AS revenue_analytics_customer ON equals(inner.customer_id, revenue_analytics_customer.id)
|
||||
ORDER BY amount DESC
|
||||
LIMIT 20 BY month
|
||||
LIMIT 100 SETTINGS readonly=2,
|
||||
max_execution_time=60,
|
||||
allow_experimental_object_type=1,
|
||||
@@ -1796,108 +1796,6 @@
|
||||
# ---
|
||||
# name: TestRevenueAnalyticsTopCustomersQueryRunner.test_with_events_data_with_managed_viewsets_ff
|
||||
'''
|
||||
SELECT inner.customer_id AS customer_id,
|
||||
revenue_analytics_customer.name AS name,
|
||||
inner.amount AS amount,
|
||||
inner.month AS month
|
||||
FROM
|
||||
(SELECT revenue_analytics_revenue_item.customer_id AS customer_id,
|
||||
toStartOfMonth(toTimeZone(revenue_analytics_revenue_item.timestamp, 'UTC')) AS month,
|
||||
sum(revenue_analytics_revenue_item.amount) AS amount
|
||||
FROM
|
||||
(SELECT toString(events.uuid) AS id,
|
||||
toString(events.uuid) AS invoice_item_id,
|
||||
'revenue_analytics.events.purchase' AS source_label,
|
||||
toTimeZone(events.timestamp, 'UTC') AS timestamp,
|
||||
timestamp AS created_at,
|
||||
isNotNull(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, 'subscription'), ''), 'null'), '^"|"$', '')) AS is_recurring,
|
||||
replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, 'product'), ''), 'null'), '^"|"$', '') AS product_id,
|
||||
toString(if(not(empty(events__override.distinct_id)), events__override.person_id, events.person_id)) AS customer_id,
|
||||
events.`$group_0` AS group_0_key,
|
||||
events.`$group_1` AS group_1_key,
|
||||
events.`$group_2` AS group_2_key,
|
||||
events.`$group_3` AS group_3_key,
|
||||
events.`$group_4` AS group_4_key,
|
||||
NULL AS invoice_id,
|
||||
replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, 'subscription'), ''), 'null'), '^"|"$', '') AS subscription_id,
|
||||
toString(events.`$session_id`) AS session_id,
|
||||
events.event AS event_name,
|
||||
replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, 'coupon'), ''), 'null'), '^"|"$', '') AS coupon,
|
||||
coupon AS coupon_id,
|
||||
upper(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, 'currency'), ''), 'null'), '^"|"$', '')) AS original_currency,
|
||||
accurateCastOrNull(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, 'revenue'), ''), 'null'), '^"|"$', ''), 'Decimal64(10)') AS original_amount,
|
||||
1 AS enable_currency_aware_divider,
|
||||
if(enable_currency_aware_divider, accurateCastOrNull(1, 'Decimal64(10)'), accurateCastOrNull(100, 'Decimal64(10)')) AS currency_aware_divider,
|
||||
divideDecimal(original_amount, currency_aware_divider) AS currency_aware_amount,
|
||||
'GBP' AS currency,
|
||||
if(isNull(upper(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, 'currency'), ''), 'null'), '^"|"$', ''))), accurateCastOrNull(currency_aware_amount, 'Decimal64(10)'), if(equals(upper(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, 'currency'), ''), 'null'), '^"|"$', '')), 'GBP'), toDecimal64(currency_aware_amount, 10), if(dictGetOrDefault(`posthog_test`.`exchange_rate_dict`, 'rate', upper(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, 'currency'), ''), 'null'), '^"|"$', '')), toDate(toTimeZone(events.timestamp, 'UTC')), toDecimal64(0, 10)) = 0, toDecimal64(0, 10), multiplyDecimal(divideDecimal(toDecimal64(currency_aware_amount, 10), dictGetOrDefault(`posthog_test`.`exchange_rate_dict`, 'rate', upper(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, 'currency'), ''), 'null'), '^"|"$', '')), toDate(toTimeZone(events.timestamp, 'UTC')), toDecimal64(0, 10))), dictGetOrDefault(`posthog_test`.`exchange_rate_dict`, 'rate', 'GBP', toDate(toTimeZone(events.timestamp, 'UTC')), toDecimal64(0, 10)))))) AS amount
|
||||
FROM events
|
||||
LEFT OUTER JOIN
|
||||
(SELECT argMax(person_distinct_id_overrides.person_id, person_distinct_id_overrides.version) AS person_id,
|
||||
person_distinct_id_overrides.distinct_id AS distinct_id
|
||||
FROM person_distinct_id_overrides
|
||||
WHERE equals(person_distinct_id_overrides.team_id, 99999)
|
||||
GROUP BY person_distinct_id_overrides.distinct_id
|
||||
HAVING ifNull(equals(argMax(person_distinct_id_overrides.is_deleted, person_distinct_id_overrides.version), 0), 0) SETTINGS optimize_aggregation_in_order=1) AS events__override ON equals(events.distinct_id, events__override.distinct_id)
|
||||
WHERE and(equals(events.team_id, 99999), and(equals(events.event, 'purchase'), 1, isNotNull(amount)))
|
||||
ORDER BY timestamp DESC) AS revenue_analytics_revenue_item
|
||||
WHERE and(and(greaterOrEquals(toTimeZone(revenue_analytics_revenue_item.timestamp, 'UTC'), assumeNotNull(toDateTime('2023-11-01 00:00:00', 'UTC'))), lessOrEquals(toTimeZone(revenue_analytics_revenue_item.timestamp, 'UTC'), assumeNotNull(toDateTime('2024-01-31 23:59:59', 'UTC')))), equals(revenue_analytics_revenue_item.source_label, 'revenue_analytics.events.purchase'))
|
||||
GROUP BY customer_id,
|
||||
month
|
||||
ORDER BY amount DESC
|
||||
LIMIT 20 BY month) AS inner
|
||||
LEFT JOIN
|
||||
(SELECT toString(persons.id) AS id,
|
||||
'revenue_analytics.events.purchase' AS source_label,
|
||||
persons.created_at AS timestamp,
|
||||
persons.properties___name AS name,
|
||||
persons.properties___email AS email,
|
||||
persons.properties___phone AS phone,
|
||||
persons.properties___address AS address,
|
||||
persons.properties___metadata AS metadata,
|
||||
persons.`properties___$geoip_country_name` AS country,
|
||||
formatDateTime(toStartOfMonth(persons.created_at), '%Y-%m') AS cohort,
|
||||
NULL AS initial_coupon,
|
||||
NULL AS initial_coupon_id
|
||||
FROM
|
||||
(SELECT person.id AS id,
|
||||
replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(person.properties, 'name'), ''), 'null'), '^"|"$', '') AS properties___name,
|
||||
replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(person.properties, 'email'), ''), 'null'), '^"|"$', '') AS properties___email,
|
||||
replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(person.properties, 'phone'), ''), 'null'), '^"|"$', '') AS properties___phone,
|
||||
replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(person.properties, 'address'), ''), 'null'), '^"|"$', '') AS properties___address,
|
||||
replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(person.properties, 'metadata'), ''), 'null'), '^"|"$', '') AS properties___metadata,
|
||||
replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(person.properties, '$geoip_country_name'), ''), 'null'), '^"|"$', '') AS `properties___$geoip_country_name`,
|
||||
toTimeZone(person.created_at, 'UTC') AS created_at
|
||||
FROM person
|
||||
WHERE and(equals(person.team_id, 99999), in(tuple(person.id, person.version),
|
||||
(SELECT person.id AS id, max(person.version) AS version
|
||||
FROM person
|
||||
WHERE equals(person.team_id, 99999)
|
||||
GROUP BY person.id
|
||||
HAVING and(ifNull(equals(argMax(person.is_deleted, person.version), 0), 0), ifNull(less(argMax(toTimeZone(person.created_at, 'UTC'), person.version), plus(now64(6, 'UTC'), toIntervalDay(1))), 0))
|
||||
ORDER BY argMax(toTimeZone(person.created_at, 'UTC'), person.version) DESC))) SETTINGS optimize_aggregation_in_order=1) AS persons
|
||||
INNER JOIN
|
||||
(SELECT DISTINCT events__person.id AS person_id
|
||||
FROM events
|
||||
LEFT OUTER JOIN
|
||||
(SELECT argMax(person_distinct_id_overrides.person_id, person_distinct_id_overrides.version) AS person_id,
|
||||
person_distinct_id_overrides.distinct_id AS distinct_id
|
||||
FROM person_distinct_id_overrides
|
||||
WHERE equals(person_distinct_id_overrides.team_id, 99999)
|
||||
GROUP BY person_distinct_id_overrides.distinct_id
|
||||
HAVING ifNull(equals(argMax(person_distinct_id_overrides.is_deleted, person_distinct_id_overrides.version), 0), 0) SETTINGS optimize_aggregation_in_order=1) AS events__override ON equals(events.distinct_id, events__override.distinct_id)
|
||||
INNER JOIN
|
||||
(SELECT person.id AS id
|
||||
FROM person
|
||||
WHERE equals(person.team_id, 99999)
|
||||
GROUP BY person.id
|
||||
HAVING and(ifNull(equals(argMax(person.is_deleted, person.version), 0), 0), ifNull(less(argMax(toTimeZone(person.created_at, 'UTC'), person.version), plus(now64(6, 'UTC'), toIntervalDay(1))), 0)) SETTINGS optimize_aggregation_in_order=1) AS events__person ON equals(if(not(empty(events__override.distinct_id)), events__override.person_id, events.person_id), events__person.id)
|
||||
WHERE equals(events.team_id, 99999)) AS events ON equals(persons.id, events.person_id)
|
||||
ORDER BY persons.created_at DESC) AS revenue_analytics_customer ON equals(inner.customer_id, revenue_analytics_customer.id)
|
||||
ORDER BY amount DESC
|
||||
LIMIT 20 BY month
|
||||
LIMIT 100
|
||||
UNION ALL
|
||||
SELECT inner.customer_id AS customer_id,
|
||||
revenue_analytics_customer.name AS name,
|
||||
inner.amount AS amount,
|
||||
@@ -2013,6 +1911,108 @@
|
||||
GROUP BY invoice.customer) AS cohort_inner ON equals(cohort_inner.customer_id, outer.id)) AS revenue_analytics_customer ON equals(inner.customer_id, revenue_analytics_customer.id)
|
||||
ORDER BY amount DESC
|
||||
LIMIT 20 BY month
|
||||
LIMIT 100
|
||||
UNION ALL
|
||||
SELECT inner.customer_id AS customer_id,
|
||||
revenue_analytics_customer.name AS name,
|
||||
inner.amount AS amount,
|
||||
inner.month AS month
|
||||
FROM
|
||||
(SELECT revenue_analytics_revenue_item.customer_id AS customer_id,
|
||||
toStartOfMonth(toTimeZone(revenue_analytics_revenue_item.timestamp, 'UTC')) AS month,
|
||||
sum(revenue_analytics_revenue_item.amount) AS amount
|
||||
FROM
|
||||
(SELECT toString(events.uuid) AS id,
|
||||
toString(events.uuid) AS invoice_item_id,
|
||||
'revenue_analytics.events.purchase' AS source_label,
|
||||
toTimeZone(events.timestamp, 'UTC') AS timestamp,
|
||||
timestamp AS created_at,
|
||||
isNotNull(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, 'subscription'), ''), 'null'), '^"|"$', '')) AS is_recurring,
|
||||
replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, 'product'), ''), 'null'), '^"|"$', '') AS product_id,
|
||||
toString(if(not(empty(events__override.distinct_id)), events__override.person_id, events.person_id)) AS customer_id,
|
||||
events.`$group_0` AS group_0_key,
|
||||
events.`$group_1` AS group_1_key,
|
||||
events.`$group_2` AS group_2_key,
|
||||
events.`$group_3` AS group_3_key,
|
||||
events.`$group_4` AS group_4_key,
|
||||
NULL AS invoice_id,
|
||||
replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, 'subscription'), ''), 'null'), '^"|"$', '') AS subscription_id,
|
||||
toString(events.`$session_id`) AS session_id,
|
||||
events.event AS event_name,
|
||||
replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, 'coupon'), ''), 'null'), '^"|"$', '') AS coupon,
|
||||
coupon AS coupon_id,
|
||||
upper(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, 'currency'), ''), 'null'), '^"|"$', '')) AS original_currency,
|
||||
accurateCastOrNull(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, 'revenue'), ''), 'null'), '^"|"$', ''), 'Decimal64(10)') AS original_amount,
|
||||
1 AS enable_currency_aware_divider,
|
||||
if(enable_currency_aware_divider, accurateCastOrNull(1, 'Decimal64(10)'), accurateCastOrNull(100, 'Decimal64(10)')) AS currency_aware_divider,
|
||||
divideDecimal(original_amount, currency_aware_divider) AS currency_aware_amount,
|
||||
'GBP' AS currency,
|
||||
if(isNull(upper(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, 'currency'), ''), 'null'), '^"|"$', ''))), accurateCastOrNull(currency_aware_amount, 'Decimal64(10)'), if(equals(upper(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, 'currency'), ''), 'null'), '^"|"$', '')), 'GBP'), toDecimal64(currency_aware_amount, 10), if(dictGetOrDefault(`posthog_test`.`exchange_rate_dict`, 'rate', upper(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, 'currency'), ''), 'null'), '^"|"$', '')), toDate(toTimeZone(events.timestamp, 'UTC')), toDecimal64(0, 10)) = 0, toDecimal64(0, 10), multiplyDecimal(divideDecimal(toDecimal64(currency_aware_amount, 10), dictGetOrDefault(`posthog_test`.`exchange_rate_dict`, 'rate', upper(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, 'currency'), ''), 'null'), '^"|"$', '')), toDate(toTimeZone(events.timestamp, 'UTC')), toDecimal64(0, 10))), dictGetOrDefault(`posthog_test`.`exchange_rate_dict`, 'rate', 'GBP', toDate(toTimeZone(events.timestamp, 'UTC')), toDecimal64(0, 10)))))) AS amount
|
||||
FROM events
|
||||
LEFT OUTER JOIN
|
||||
(SELECT argMax(person_distinct_id_overrides.person_id, person_distinct_id_overrides.version) AS person_id,
|
||||
person_distinct_id_overrides.distinct_id AS distinct_id
|
||||
FROM person_distinct_id_overrides
|
||||
WHERE equals(person_distinct_id_overrides.team_id, 99999)
|
||||
GROUP BY person_distinct_id_overrides.distinct_id
|
||||
HAVING ifNull(equals(argMax(person_distinct_id_overrides.is_deleted, person_distinct_id_overrides.version), 0), 0) SETTINGS optimize_aggregation_in_order=1) AS events__override ON equals(events.distinct_id, events__override.distinct_id)
|
||||
WHERE and(equals(events.team_id, 99999), and(equals(events.event, 'purchase'), 1, isNotNull(amount)))
|
||||
ORDER BY timestamp DESC) AS revenue_analytics_revenue_item
|
||||
WHERE and(and(greaterOrEquals(toTimeZone(revenue_analytics_revenue_item.timestamp, 'UTC'), assumeNotNull(toDateTime('2023-11-01 00:00:00', 'UTC'))), lessOrEquals(toTimeZone(revenue_analytics_revenue_item.timestamp, 'UTC'), assumeNotNull(toDateTime('2024-01-31 23:59:59', 'UTC')))), equals(revenue_analytics_revenue_item.source_label, 'revenue_analytics.events.purchase'))
|
||||
GROUP BY customer_id,
|
||||
month
|
||||
ORDER BY amount DESC
|
||||
LIMIT 20 BY month) AS inner
|
||||
LEFT JOIN
|
||||
(SELECT toString(persons.id) AS id,
|
||||
'revenue_analytics.events.purchase' AS source_label,
|
||||
persons.created_at AS timestamp,
|
||||
persons.properties___name AS name,
|
||||
persons.properties___email AS email,
|
||||
persons.properties___phone AS phone,
|
||||
persons.properties___address AS address,
|
||||
persons.properties___metadata AS metadata,
|
||||
persons.`properties___$geoip_country_name` AS country,
|
||||
formatDateTime(toStartOfMonth(persons.created_at), '%Y-%m') AS cohort,
|
||||
NULL AS initial_coupon,
|
||||
NULL AS initial_coupon_id
|
||||
FROM
|
||||
(SELECT person.id AS id,
|
||||
replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(person.properties, 'name'), ''), 'null'), '^"|"$', '') AS properties___name,
|
||||
replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(person.properties, 'email'), ''), 'null'), '^"|"$', '') AS properties___email,
|
||||
replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(person.properties, 'phone'), ''), 'null'), '^"|"$', '') AS properties___phone,
|
||||
replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(person.properties, 'address'), ''), 'null'), '^"|"$', '') AS properties___address,
|
||||
replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(person.properties, 'metadata'), ''), 'null'), '^"|"$', '') AS properties___metadata,
|
||||
replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(person.properties, '$geoip_country_name'), ''), 'null'), '^"|"$', '') AS `properties___$geoip_country_name`,
|
||||
toTimeZone(person.created_at, 'UTC') AS created_at
|
||||
FROM person
|
||||
WHERE and(equals(person.team_id, 99999), in(tuple(person.id, person.version),
|
||||
(SELECT person.id AS id, max(person.version) AS version
|
||||
FROM person
|
||||
WHERE equals(person.team_id, 99999)
|
||||
GROUP BY person.id
|
||||
HAVING and(ifNull(equals(argMax(person.is_deleted, person.version), 0), 0), ifNull(less(argMax(toTimeZone(person.created_at, 'UTC'), person.version), plus(now64(6, 'UTC'), toIntervalDay(1))), 0))
|
||||
ORDER BY argMax(toTimeZone(person.created_at, 'UTC'), person.version) DESC))) SETTINGS optimize_aggregation_in_order=1) AS persons
|
||||
INNER JOIN
|
||||
(SELECT DISTINCT events__person.id AS person_id
|
||||
FROM events
|
||||
LEFT OUTER JOIN
|
||||
(SELECT argMax(person_distinct_id_overrides.person_id, person_distinct_id_overrides.version) AS person_id,
|
||||
person_distinct_id_overrides.distinct_id AS distinct_id
|
||||
FROM person_distinct_id_overrides
|
||||
WHERE equals(person_distinct_id_overrides.team_id, 99999)
|
||||
GROUP BY person_distinct_id_overrides.distinct_id
|
||||
HAVING ifNull(equals(argMax(person_distinct_id_overrides.is_deleted, person_distinct_id_overrides.version), 0), 0) SETTINGS optimize_aggregation_in_order=1) AS events__override ON equals(events.distinct_id, events__override.distinct_id)
|
||||
INNER JOIN
|
||||
(SELECT person.id AS id
|
||||
FROM person
|
||||
WHERE equals(person.team_id, 99999)
|
||||
GROUP BY person.id
|
||||
HAVING and(ifNull(equals(argMax(person.is_deleted, person.version), 0), 0), ifNull(less(argMax(toTimeZone(person.created_at, 'UTC'), person.version), plus(now64(6, 'UTC'), toIntervalDay(1))), 0)) SETTINGS optimize_aggregation_in_order=1) AS events__person ON equals(if(not(empty(events__override.distinct_id)), events__override.person_id, events.person_id), events__person.id)
|
||||
WHERE equals(events.team_id, 99999)) AS events ON equals(persons.id, events.person_id)
|
||||
ORDER BY persons.created_at DESC) AS revenue_analytics_customer ON equals(inner.customer_id, revenue_analytics_customer.id)
|
||||
ORDER BY amount DESC
|
||||
LIMIT 20 BY month
|
||||
LIMIT 100 SETTINGS readonly=2,
|
||||
max_execution_time=60,
|
||||
allow_experimental_object_type=1,
|
||||
|
||||
Reference in New Issue
Block a user