feat: fix queries and tests before upgrading to ClickHouse v25.8.11.66 (#40927)

This commit is contained in:
Paweł Szczur
2025-11-05 14:55:49 +01:00
committed by GitHub
parent 94008e956b
commit 5d723de47f
31 changed files with 616 additions and 460 deletions

View File

@@ -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

View File

@@ -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,

View File

@@ -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,

View File

@@ -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(

View File

@@ -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"
),
)

View File

@@ -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):

View File

@@ -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},

View File

@@ -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,

View File

@@ -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,

View File

@@ -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,

View File

@@ -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,

View File

@@ -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,

View File

@@ -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,

View File

@@ -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,

View File

@@ -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

View File

@@ -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,
)

View File

@@ -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,

View File

@@ -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

View File

@@ -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()

View File

@@ -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):

View File

@@ -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):

View File

@@ -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):

View File

@@ -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
'''
# ---

View File

@@ -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
'''
# ---

View File

@@ -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
'''
# ---

View File

@@ -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
'''
# ---

View File

@@ -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(

View File

@@ -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)

View File

@@ -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,

View File

@@ -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

View File

@@ -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,