diff --git a/metricflow/test/integration/test_cases/itest_conversion_metric.yaml b/metricflow/test/integration/test_cases/itest_conversion_metric.yaml new file mode 100644 index 0000000000..c025be8f13 --- /dev/null +++ b/metricflow/test/integration/test_cases/itest_conversion_metric.yaml @@ -0,0 +1,271 @@ +--- +integration_test: + name: conversion_rate_metric + description: Query a conversion metric that calculates the conversion rate + model: SIMPLE_MODEL + metrics: ["visit_buy_conversion_rate"] + group_bys: ["metric_time"] + check_query: | + SELECT + opportunities.metric_time AS metric_time__day + , CAST(conversions.buys AS {{ double_data_type_name }}) / CAST(NULLIF(opportunities.visits, 0) AS {{ double_data_type_name }}) AS visit_buy_conversion_rate + FROM ( + SELECT + metric_time, SUM(a.visits) AS visits + FROM ( + SELECT + ds AS metric_time, 1 AS visits + FROM {{ source_schema }}.fct_visits visits + ) a + GROUP BY + a.metric_time + ) opportunities + FULL OUTER JOIN ( + SELECT + b.ds AS metric_time, SUM(b.buys) AS buys + FROM ( + SELECT DISTINCT + first_value(v.ds) OVER (PARTITION BY buy_source.ds, buy_source.user_id ORDER BY v.ds DESC NULLS FIRST) AS ds + , first_value(v.user_id) OVER (PARTITION BY buy_source.ds, buy_source.user_id ORDER BY v.ds DESC NULLS FIRST) AS user_id + , first_value(v.referrer_id) OVER (PARTITION BY buy_source.ds, buy_source.user_id ORDER BY v.ds DESC NULLS FIRST) AS referrer_id + , buy_source.uuid + , 1 AS buys + FROM {{ source_schema }}.fct_visits v + INNER JOIN + ( + SELECT *, {{ generate_random_uuid() }} AS uuid FROM {{ source_schema }}.fct_buys + ) buy_source + ON + v.user_id = buy_source.user_id AND v.ds <= buy_source.ds AND v.ds > {{ render_date_sub("buy_source", "ds", 7, TimeGranularity.DAY) }} + ) b + GROUP BY + b.ds + ) conversions + ON opportunities.metric_time = conversions.metric_time +--- +integration_test: + name: conversion_rate_metric_with_dimension + description: Query a conversion metric that calculates the conversion rate without time dimension + model: SIMPLE_MODEL + metrics: ["visit_buy_conversion_rate"] + group_bys: ["visit__referrer_id"] + check_query: | + SELECT + opportunities.referrer_id AS visit__referrer_id + , CAST(conversions.buys AS {{ double_data_type_name }}) / CAST(NULLIF(opportunities.visits, 0) AS {{ double_data_type_name }}) AS visit_buy_conversion_rate + FROM ( + SELECT + referrer_id, SUM(a.visits) AS visits + FROM ( + SELECT + referrer_id, 1 AS visits + FROM {{ source_schema }}.fct_visits visits + ) a + GROUP BY + a.referrer_id + ) opportunities + FULL OUTER JOIN ( + SELECT + referrer_id AS referrer_id, SUM(b.buys) AS buys + FROM ( + SELECT DISTINCT + first_value(v.ds) OVER (PARTITION BY buy_source.ds, buy_source.user_id ORDER BY v.ds DESC NULLS FIRST) AS ds + , first_value(v.user_id) OVER (PARTITION BY buy_source.ds, buy_source.user_id ORDER BY v.ds DESC NULLS FIRST) AS user_id + , first_value(v.referrer_id) OVER (PARTITION BY buy_source.ds, buy_source.user_id ORDER BY v.ds DESC NULLS FIRST) AS referrer_id + , buy_source.uuid + , 1 AS buys + FROM {{ source_schema }}.fct_visits v + INNER JOIN + ( + SELECT *, {{ generate_random_uuid() }} AS uuid FROM {{ source_schema }}.fct_buys + ) buy_source + ON + v.user_id = buy_source.user_id AND v.ds <= buy_source.ds AND v.ds > {{ render_date_sub("buy_source", "ds", 7, TimeGranularity.DAY) }} + ) b + GROUP BY + b.referrer_id + ) conversions + ON opportunities.referrer_id = conversions.referrer_id +--- +integration_test: + name: conversion_rate_metric_with_multiple_dimension + description: Query a conversion metric that calculates the conversion rate with multiple dimension + model: SIMPLE_MODEL + metrics: ["visit_buy_conversion_rate"] + group_bys: ["metric_time", "visit__referrer_id"] + check_query: | + SELECT + opportunities.referrer_id AS visit__referrer_id + , opportunities.metric_time AS metric_time__day + , CAST(conversions.buys AS {{ double_data_type_name }}) / CAST(NULLIF(opportunities.visits, 0) AS {{ double_data_type_name }}) AS visit_buy_conversion_rate + FROM ( + SELECT + metric_time, referrer_id, SUM(a.visits) AS visits + FROM ( + SELECT + ds AS metric_time, referrer_id, 1 AS visits + FROM {{ source_schema }}.fct_visits visits + ) a + GROUP BY + a.referrer_id, a.metric_time + ) opportunities + FULL OUTER JOIN ( + SELECT + b.ds AS metric_time, referrer_id AS referrer_id, SUM(b.buys) AS buys + FROM ( + SELECT DISTINCT + first_value(v.ds) OVER (PARTITION BY buy_source.ds, buy_source.user_id ORDER BY v.ds DESC NULLS FIRST) AS ds + , first_value(v.user_id) OVER (PARTITION BY buy_source.ds, buy_source.user_id ORDER BY v.ds DESC NULLS FIRST) AS user_id + , first_value(v.referrer_id) OVER (PARTITION BY buy_source.ds, buy_source.user_id ORDER BY v.ds DESC NULLS FIRST) AS referrer_id + , buy_source.uuid + , 1 AS buys + FROM {{ source_schema }}.fct_visits v + INNER JOIN + ( + SELECT *, {{ generate_random_uuid() }} AS uuid FROM {{ source_schema }}.fct_buys + ) buy_source + ON + v.user_id = buy_source.user_id AND v.ds <= buy_source.ds AND v.ds > {{ render_date_sub("buy_source", "ds", 7, TimeGranularity.DAY) }} + ) b + GROUP BY + b.referrer_id, b.ds + ) conversions + ON opportunities.referrer_id = conversions.referrer_id AND opportunities.metric_time = conversions.metric_time +--- +integration_test: + name: conversion_count_metric_with_multiple_dimension + description: Query a conversion metric that calculates the conversion count with multiple dimension + model: SIMPLE_MODEL + metrics: ["visit_buy_conversions"] + group_bys: ["metric_time", "visit__referrer_id"] + check_query: | + SELECT + opportunities.referrer_id AS visit__referrer_id + , opportunities.metric_time AS metric_time__day + , conversions.buys AS visit_buy_conversions + FROM ( + SELECT + metric_time, referrer_id, SUM(a.visits) AS visits + FROM ( + SELECT + ds AS metric_time, referrer_id, 1 AS visits + FROM {{ source_schema }}.fct_visits visits + ) a + GROUP BY + a.referrer_id, a.metric_time + ) opportunities + FULL OUTER JOIN ( + SELECT + b.ds AS metric_time, referrer_id AS referrer_id, SUM(b.buys) AS buys + FROM ( + SELECT DISTINCT + first_value(v.ds) OVER (PARTITION BY buy_source.ds, buy_source.user_id ORDER BY v.ds DESC NULLS FIRST) AS ds + , first_value(v.user_id) OVER (PARTITION BY buy_source.ds, buy_source.user_id ORDER BY v.ds DESC NULLS FIRST) AS user_id + , first_value(v.referrer_id) OVER (PARTITION BY buy_source.ds, buy_source.user_id ORDER BY v.ds DESC NULLS FIRST) AS referrer_id + , buy_source.uuid + , 1 AS buys + FROM {{ source_schema }}.fct_visits v + INNER JOIN + ( + SELECT *, {{ generate_random_uuid() }} AS uuid FROM {{ source_schema }}.fct_buys + ) buy_source + ON + v.user_id = buy_source.user_id AND v.ds <= buy_source.ds AND v.ds > {{ render_date_sub("buy_source", "ds", 7, TimeGranularity.DAY) }} + ) b + GROUP BY + b.referrer_id, b.ds + ) conversions + ON opportunities.referrer_id = conversions.referrer_id AND opportunities.metric_time = conversions.metric_time +--- +integration_test: + name: conversion_rate_metric_with_constant_property + description: Query a conversion metric that calculates the conversion rate held by a constant property + model: SIMPLE_MODEL + metrics: ["visit_buy_conversion_rate_by_session"] + group_bys: ["metric_time"] + check_query: | + SELECT + opportunities.metric_time AS metric_time__day + , CAST(conversions.buys AS {{ double_data_type_name }}) / CAST(NULLIF(opportunities.visits, 0) AS {{ double_data_type_name }}) AS visit_buy_conversion_rate_by_session + FROM ( + SELECT + metric_time, SUM(a.visits) AS visits + FROM ( + SELECT + ds AS metric_time, 1 AS visits + FROM {{ source_schema }}.fct_visits visits + ) a + GROUP BY + a.metric_time + ) opportunities + FULL OUTER JOIN ( + SELECT + b.ds AS metric_time, SUM(b.buys) AS buys + FROM ( + SELECT DISTINCT + first_value(v.ds) OVER (PARTITION BY buy_source.ds, buy_source.user_id, buy_source.session_id ORDER BY v.ds DESC NULLS FIRST) AS ds + , first_value(v.user_id) OVER (PARTITION BY buy_source.ds, buy_source.user_id, buy_source.session_id ORDER BY v.ds DESC NULLS FIRST) AS user_id + , first_value(v.referrer_id) OVER (PARTITION BY buy_source.ds, buy_source.user_id, buy_source.session_id ORDER BY v.ds DESC NULLS FIRST) AS referrer_id + , buy_source.uuid + , 1 AS buys + FROM {{ source_schema }}.fct_visits v + INNER JOIN + ( + SELECT *, {{ generate_random_uuid() }} AS uuid FROM {{ source_schema }}.fct_buys + ) buy_source + ON + v.user_id = buy_source.user_id + AND v.ds <= buy_source.ds AND v.ds > {{ render_date_sub("buy_source", "ds", 7, TimeGranularity.DAY) }} + AND buy_source.session_id = v.session_id + ) b + GROUP BY + b.ds + ) conversions + ON opportunities.metric_time = conversions.metric_time +--- +integration_test: + name: conversion_rate_metric_with_constant_property_multiple_dimensions + description: Query a conversion metric that calculates the conversion rate held by a constant property + model: SIMPLE_MODEL + metrics: ["visit_buy_conversion_rate_by_session"] + group_bys: ["metric_time", "visit__referrer_id"] + check_query: | + SELECT + opportunities.referrer_id AS visit__referrer_id + , opportunities.metric_time AS metric_time__day + , CAST(conversions.buys AS {{ double_data_type_name }}) / CAST(NULLIF(opportunities.visits, 0) AS {{ double_data_type_name }}) AS visit_buy_conversion_rate_by_session + FROM ( + SELECT + metric_time, referrer_id, SUM(a.visits) AS visits + FROM ( + SELECT + ds AS metric_time, referrer_id, 1 AS visits + FROM {{ source_schema }}.fct_visits visits + ) a + GROUP BY + a.referrer_id, a.metric_time + ) opportunities + FULL OUTER JOIN ( + SELECT + b.ds AS metric_time, referrer_id AS referrer_id, SUM(b.buys) AS buys + FROM ( + SELECT DISTINCT + first_value(v.ds) OVER (PARTITION BY buy_source.ds, buy_source.user_id, buy_source.session_id ORDER BY v.ds DESC NULLS FIRST) AS ds + , first_value(v.user_id) OVER (PARTITION BY buy_source.ds, buy_source.user_id, buy_source.session_id ORDER BY v.ds DESC NULLS FIRST) AS user_id + , first_value(v.referrer_id) OVER (PARTITION BY buy_source.ds, buy_source.user_id, buy_source.session_id ORDER BY v.ds DESC NULLS FIRST) AS referrer_id + , buy_source.uuid + , 1 AS buys + FROM {{ source_schema }}.fct_visits v + INNER JOIN + ( + SELECT *, {{ generate_random_uuid() }} AS uuid FROM {{ source_schema }}.fct_buys + ) buy_source + ON + v.user_id = buy_source.user_id + AND v.ds <= buy_source.ds AND v.ds > {{ render_date_sub("buy_source", "ds", 7, TimeGranularity.DAY) }} + AND buy_source.session_id = v.session_id + ) b + GROUP BY + b.referrer_id, b.ds + ) conversions + ON opportunities.referrer_id = conversions.referrer_id AND opportunities.metric_time = conversions.metric_time diff --git a/metricflow/test/integration/test_configured_cases.py b/metricflow/test/integration/test_configured_cases.py index e31b0f98af..e2c0a3cad9 100644 --- a/metricflow/test/integration/test_configured_cases.py +++ b/metricflow/test/integration/test_configured_cases.py @@ -28,6 +28,7 @@ SqlColumnReferenceExpression, SqlDateTruncExpression, SqlExtractExpression, + SqlGenerateUuidExpression, SqlPercentileExpression, SqlPercentileExpressionArgument, SqlPercentileFunctionType, @@ -172,6 +173,11 @@ def render_time_dimension_template( f"{{{{ TimeDimension('{time_dimension_name}', '{time_granularity}', entity_path={repr(entity_path)}) }}}}" ) + def generate_random_uuid(self) -> str: + """Returns the generate random UUID SQL function.""" + expr = SqlGenerateUuidExpression() + return self._sql_client.sql_query_plan_renderer.expr_renderer.render_sql_expr(expr).sql + def filter_not_supported_features( sql_client: SqlClient, required_features: Tuple[RequiredDwEngineFeatures, ...] @@ -295,6 +301,7 @@ def test_case( render_dimension_template=check_query_helpers.render_dimension_template, render_entity_template=check_query_helpers.render_entity_template, render_time_dimension_template=check_query_helpers.render_time_dimension_template, + generate_random_uuid=check_query_helpers.generate_random_uuid, ) if case.where_filter else None, @@ -319,6 +326,7 @@ def test_case( render_percentile_expr=check_query_helpers.render_percentile_expr, mf_time_spine_source=semantic_manifest_lookup.time_spine_source.spine_table.sql, double_data_type_name=check_query_helpers.double_data_type_name, + generate_random_uuid=check_query_helpers.generate_random_uuid, ) ) # If we sort, it's effectively not checking the order whatever order that the output was would be overwritten. diff --git a/metricflow/test/model/test_semantic_model_container.py b/metricflow/test/model/test_semantic_model_container.py index 7dfb6685b7..80ec27d5c7 100644 --- a/metricflow/test/model/test_semantic_model_container.py +++ b/metricflow/test/model/test_semantic_model_container.py @@ -115,7 +115,7 @@ def test_local_linked_elements_for_metric( # noqa: D def test_get_semantic_models_for_entity(semantic_model_lookup: SemanticModelLookup) -> None: # noqa: D entity_reference = EntityReference(element_name="user") linked_semantic_models = semantic_model_lookup.get_semantic_models_for_entity(entity_reference=entity_reference) - assert len(linked_semantic_models) == 8 + assert len(linked_semantic_models) == 10 def test_linkable_set( # noqa: D diff --git a/metricflow/test/plan_conversion/test_dataflow_to_sql_plan.py b/metricflow/test/plan_conversion/test_dataflow_to_sql_plan.py index 17f068b749..41c41f9b13 100644 --- a/metricflow/test/plan_conversion/test_dataflow_to_sql_plan.py +++ b/metricflow/test/plan_conversion/test_dataflow_to_sql_plan.py @@ -1012,6 +1012,37 @@ def test_compute_metrics_node_ratio_from_multiple_semantic_models( ) +@pytest.mark.sql_engine_snapshot +def test_conversion_metric( + request: FixtureRequest, + mf_test_session_state: MetricFlowTestSessionState, + dataflow_plan_builder: DataflowPlanBuilder, + dataflow_to_sql_converter: DataflowToSqlQueryPlanConverter, + sql_client: SqlClient, +) -> None: + """Test conversion metric data flow plan rendering.""" + dimension_spec = DimensionSpec( + element_name="referrer_id", + entity_links=(EntityReference(element_name="visit"),), + ) + metric_spec = MetricSpec(element_name="visit_buy_conversion_rate") + + dataflow_plan = dataflow_plan_builder.build_plan( + query_spec=MetricFlowQuerySpec( + metric_specs=(metric_spec,), + dimension_specs=(dimension_spec,), + ), + ) + + convert_and_check( + request=request, + mf_test_session_state=mf_test_session_state, + dataflow_to_sql_converter=dataflow_to_sql_converter, + sql_client=sql_client, + node=dataflow_plan.sink_output_nodes[0].parent_node, + ) + + @pytest.mark.sql_engine_snapshot def test_combine_output_node( # noqa: D request: FixtureRequest, diff --git a/metricflow/test/snapshots/test_dataflow_to_sql_plan.py/SqlQueryPlan/DuckDB/test_conversion_metric__plan0.sql b/metricflow/test/snapshots/test_dataflow_to_sql_plan.py/SqlQueryPlan/DuckDB/test_conversion_metric__plan0.sql new file mode 100644 index 0000000000..0a56dd7299 --- /dev/null +++ b/metricflow/test/snapshots/test_dataflow_to_sql_plan.py/SqlQueryPlan/DuckDB/test_conversion_metric__plan0.sql @@ -0,0 +1,350 @@ +-- Compute Metrics via Expressions +SELECT + subq_15.visit__referrer_id + , CAST(subq_15.buys AS DOUBLE) / CAST(NULLIF(subq_15.visits, 0) AS DOUBLE) AS visit_buy_conversion_rate +FROM ( + -- Combine Metrics + SELECT + COALESCE(subq_3.visit__referrer_id, subq_14.visit__referrer_id) AS visit__referrer_id + , MAX(subq_3.visits) AS visits + , MAX(subq_14.buys) AS buys + FROM ( + -- Aggregate Measures + SELECT + subq_2.visit__referrer_id + , SUM(subq_2.visits) AS visits + FROM ( + -- Pass Only Elements: + -- ['visits', 'visit__referrer_id'] + SELECT + subq_1.visit__referrer_id + , subq_1.visits + FROM ( + -- Metric Time Dimension 'ds' + SELECT + subq_0.ds__day + , subq_0.ds__week + , subq_0.ds__month + , subq_0.ds__quarter + , subq_0.ds__year + , subq_0.ds__extract_year + , subq_0.ds__extract_quarter + , subq_0.ds__extract_month + , subq_0.ds__extract_day + , subq_0.ds__extract_dow + , subq_0.ds__extract_doy + , subq_0.visit__ds__day + , subq_0.visit__ds__week + , subq_0.visit__ds__month + , subq_0.visit__ds__quarter + , subq_0.visit__ds__year + , subq_0.visit__ds__extract_year + , subq_0.visit__ds__extract_quarter + , subq_0.visit__ds__extract_month + , subq_0.visit__ds__extract_day + , subq_0.visit__ds__extract_dow + , subq_0.visit__ds__extract_doy + , subq_0.ds__day AS metric_time__day + , subq_0.ds__week AS metric_time__week + , subq_0.ds__month AS metric_time__month + , subq_0.ds__quarter AS metric_time__quarter + , subq_0.ds__year AS metric_time__year + , subq_0.ds__extract_year AS metric_time__extract_year + , subq_0.ds__extract_quarter AS metric_time__extract_quarter + , subq_0.ds__extract_month AS metric_time__extract_month + , subq_0.ds__extract_day AS metric_time__extract_day + , subq_0.ds__extract_dow AS metric_time__extract_dow + , subq_0.ds__extract_doy AS metric_time__extract_doy + , subq_0.user + , subq_0.visit__user + , subq_0.referrer_id + , subq_0.visit__referrer_id + , subq_0.visits + , subq_0.visitors + FROM ( + -- Read Elements From Semantic Model 'visits_source' + SELECT + 1 AS visits + , visits_source_src_10011.user_id AS visitors + , DATE_TRUNC('day', visits_source_src_10011.ds) AS ds__day + , DATE_TRUNC('week', visits_source_src_10011.ds) AS ds__week + , DATE_TRUNC('month', visits_source_src_10011.ds) AS ds__month + , DATE_TRUNC('quarter', visits_source_src_10011.ds) AS ds__quarter + , DATE_TRUNC('year', visits_source_src_10011.ds) AS ds__year + , EXTRACT(year FROM visits_source_src_10011.ds) AS ds__extract_year + , EXTRACT(quarter FROM visits_source_src_10011.ds) AS ds__extract_quarter + , EXTRACT(month FROM visits_source_src_10011.ds) AS ds__extract_month + , EXTRACT(day FROM visits_source_src_10011.ds) AS ds__extract_day + , EXTRACT(isodow FROM visits_source_src_10011.ds) AS ds__extract_dow + , EXTRACT(doy FROM visits_source_src_10011.ds) AS ds__extract_doy + , visits_source_src_10011.referrer_id + , DATE_TRUNC('day', visits_source_src_10011.ds) AS visit__ds__day + , DATE_TRUNC('week', visits_source_src_10011.ds) AS visit__ds__week + , DATE_TRUNC('month', visits_source_src_10011.ds) AS visit__ds__month + , DATE_TRUNC('quarter', visits_source_src_10011.ds) AS visit__ds__quarter + , DATE_TRUNC('year', visits_source_src_10011.ds) AS visit__ds__year + , EXTRACT(year FROM visits_source_src_10011.ds) AS visit__ds__extract_year + , EXTRACT(quarter FROM visits_source_src_10011.ds) AS visit__ds__extract_quarter + , EXTRACT(month FROM visits_source_src_10011.ds) AS visit__ds__extract_month + , EXTRACT(day FROM visits_source_src_10011.ds) AS visit__ds__extract_day + , EXTRACT(isodow FROM visits_source_src_10011.ds) AS visit__ds__extract_dow + , EXTRACT(doy FROM visits_source_src_10011.ds) AS visit__ds__extract_doy + , visits_source_src_10011.referrer_id AS visit__referrer_id + , visits_source_src_10011.user_id AS user + , visits_source_src_10011.user_id AS visit__user + FROM ***************************.fct_visits visits_source_src_10011 + ) subq_0 + ) subq_1 + ) subq_2 + GROUP BY + subq_2.visit__referrer_id + ) subq_3 + FULL OUTER JOIN ( + -- Aggregate Measures + SELECT + subq_13.visit__referrer_id + , SUM(subq_13.buys) AS buys + FROM ( + -- Pass Only Elements: + -- ['buys', 'visit__referrer_id'] + SELECT + subq_12.visit__referrer_id + , subq_12.buys + FROM ( + -- Find conversions for EntitySpec(element_name='user', entity_links=()) within the range of count=7 granularity=TimeGranularity.DAY + SELECT + subq_11.visit__referrer_id + , subq_11.buys + , subq_11.visits + FROM ( + -- Dedupe the fanout on (MetadataSpec(element_name='mf_internal_uuid'),) in the conversion data set + SELECT DISTINCT + first_value(subq_6.visits) OVER (PARTITION BY subq_10.user, subq_10.ds__day ORDER BY subq_6.ds__day DESC) AS visits + , first_value(subq_6.visit__referrer_id) OVER (PARTITION BY subq_10.user, subq_10.ds__day ORDER BY subq_6.ds__day DESC) AS visit__referrer_id + , subq_10.mf_internal_uuid AS mf_internal_uuid + , subq_10.buys AS buys + FROM ( + -- Pass Only Elements: + -- ['visits', 'visit__referrer_id'] + SELECT + subq_5.visit__referrer_id + , subq_5.visits + FROM ( + -- Metric Time Dimension 'ds' + SELECT + subq_4.ds__day + , subq_4.ds__week + , subq_4.ds__month + , subq_4.ds__quarter + , subq_4.ds__year + , subq_4.ds__extract_year + , subq_4.ds__extract_quarter + , subq_4.ds__extract_month + , subq_4.ds__extract_day + , subq_4.ds__extract_dow + , subq_4.ds__extract_doy + , subq_4.visit__ds__day + , subq_4.visit__ds__week + , subq_4.visit__ds__month + , subq_4.visit__ds__quarter + , subq_4.visit__ds__year + , subq_4.visit__ds__extract_year + , subq_4.visit__ds__extract_quarter + , subq_4.visit__ds__extract_month + , subq_4.visit__ds__extract_day + , subq_4.visit__ds__extract_dow + , subq_4.visit__ds__extract_doy + , subq_4.ds__day AS metric_time__day + , subq_4.ds__week AS metric_time__week + , subq_4.ds__month AS metric_time__month + , subq_4.ds__quarter AS metric_time__quarter + , subq_4.ds__year AS metric_time__year + , subq_4.ds__extract_year AS metric_time__extract_year + , subq_4.ds__extract_quarter AS metric_time__extract_quarter + , subq_4.ds__extract_month AS metric_time__extract_month + , subq_4.ds__extract_day AS metric_time__extract_day + , subq_4.ds__extract_dow AS metric_time__extract_dow + , subq_4.ds__extract_doy AS metric_time__extract_doy + , subq_4.user + , subq_4.visit__user + , subq_4.referrer_id + , subq_4.visit__referrer_id + , subq_4.visits + , subq_4.visitors + FROM ( + -- Read Elements From Semantic Model 'visits_source' + SELECT + 1 AS visits + , visits_source_src_10011.user_id AS visitors + , DATE_TRUNC('day', visits_source_src_10011.ds) AS ds__day + , DATE_TRUNC('week', visits_source_src_10011.ds) AS ds__week + , DATE_TRUNC('month', visits_source_src_10011.ds) AS ds__month + , DATE_TRUNC('quarter', visits_source_src_10011.ds) AS ds__quarter + , DATE_TRUNC('year', visits_source_src_10011.ds) AS ds__year + , EXTRACT(year FROM visits_source_src_10011.ds) AS ds__extract_year + , EXTRACT(quarter FROM visits_source_src_10011.ds) AS ds__extract_quarter + , EXTRACT(month FROM visits_source_src_10011.ds) AS ds__extract_month + , EXTRACT(day FROM visits_source_src_10011.ds) AS ds__extract_day + , EXTRACT(isodow FROM visits_source_src_10011.ds) AS ds__extract_dow + , EXTRACT(doy FROM visits_source_src_10011.ds) AS ds__extract_doy + , visits_source_src_10011.referrer_id + , DATE_TRUNC('day', visits_source_src_10011.ds) AS visit__ds__day + , DATE_TRUNC('week', visits_source_src_10011.ds) AS visit__ds__week + , DATE_TRUNC('month', visits_source_src_10011.ds) AS visit__ds__month + , DATE_TRUNC('quarter', visits_source_src_10011.ds) AS visit__ds__quarter + , DATE_TRUNC('year', visits_source_src_10011.ds) AS visit__ds__year + , EXTRACT(year FROM visits_source_src_10011.ds) AS visit__ds__extract_year + , EXTRACT(quarter FROM visits_source_src_10011.ds) AS visit__ds__extract_quarter + , EXTRACT(month FROM visits_source_src_10011.ds) AS visit__ds__extract_month + , EXTRACT(day FROM visits_source_src_10011.ds) AS visit__ds__extract_day + , EXTRACT(isodow FROM visits_source_src_10011.ds) AS visit__ds__extract_dow + , EXTRACT(doy FROM visits_source_src_10011.ds) AS visit__ds__extract_doy + , visits_source_src_10011.referrer_id AS visit__referrer_id + , visits_source_src_10011.user_id AS user + , visits_source_src_10011.user_id AS visit__user + FROM ***************************.fct_visits visits_source_src_10011 + ) subq_4 + ) subq_5 + ) subq_6 + INNER JOIN ( + -- Pass Only Elements: + -- ['buys', 'mf_internal_uuid'] + SELECT + subq_9.buys + , subq_9.mf_internal_uuid + FROM ( + -- Add column with generated UUID + SELECT + subq_8.ds__day + , subq_8.ds__week + , subq_8.ds__month + , subq_8.ds__quarter + , subq_8.ds__year + , subq_8.ds__extract_year + , subq_8.ds__extract_quarter + , subq_8.ds__extract_month + , subq_8.ds__extract_day + , subq_8.ds__extract_dow + , subq_8.ds__extract_doy + , subq_8.buy__ds__day + , subq_8.buy__ds__week + , subq_8.buy__ds__month + , subq_8.buy__ds__quarter + , subq_8.buy__ds__year + , subq_8.buy__ds__extract_year + , subq_8.buy__ds__extract_quarter + , subq_8.buy__ds__extract_month + , subq_8.buy__ds__extract_day + , subq_8.buy__ds__extract_dow + , subq_8.buy__ds__extract_doy + , subq_8.metric_time__day + , subq_8.metric_time__week + , subq_8.metric_time__month + , subq_8.metric_time__quarter + , subq_8.metric_time__year + , subq_8.metric_time__extract_year + , subq_8.metric_time__extract_quarter + , subq_8.metric_time__extract_month + , subq_8.metric_time__extract_day + , subq_8.metric_time__extract_dow + , subq_8.metric_time__extract_doy + , subq_8.user + , subq_8.buy__user + , subq_8.buys + , subq_8.buyers + , GEN_RANDOM_UUID() AS mf_internal_uuid + FROM ( + -- Metric Time Dimension 'ds' + SELECT + subq_7.ds__day + , subq_7.ds__week + , subq_7.ds__month + , subq_7.ds__quarter + , subq_7.ds__year + , subq_7.ds__extract_year + , subq_7.ds__extract_quarter + , subq_7.ds__extract_month + , subq_7.ds__extract_day + , subq_7.ds__extract_dow + , subq_7.ds__extract_doy + , subq_7.buy__ds__day + , subq_7.buy__ds__week + , subq_7.buy__ds__month + , subq_7.buy__ds__quarter + , subq_7.buy__ds__year + , subq_7.buy__ds__extract_year + , subq_7.buy__ds__extract_quarter + , subq_7.buy__ds__extract_month + , subq_7.buy__ds__extract_day + , subq_7.buy__ds__extract_dow + , subq_7.buy__ds__extract_doy + , subq_7.ds__day AS metric_time__day + , subq_7.ds__week AS metric_time__week + , subq_7.ds__month AS metric_time__month + , subq_7.ds__quarter AS metric_time__quarter + , subq_7.ds__year AS metric_time__year + , subq_7.ds__extract_year AS metric_time__extract_year + , subq_7.ds__extract_quarter AS metric_time__extract_quarter + , subq_7.ds__extract_month AS metric_time__extract_month + , subq_7.ds__extract_day AS metric_time__extract_day + , subq_7.ds__extract_dow AS metric_time__extract_dow + , subq_7.ds__extract_doy AS metric_time__extract_doy + , subq_7.user + , subq_7.buy__user + , subq_7.buys + , subq_7.buyers + FROM ( + -- Read Elements From Semantic Model 'buys_source' + SELECT + 1 AS buys + , buys_source_src_10002.user_id AS buyers + , DATE_TRUNC('day', buys_source_src_10002.ds) AS ds__day + , DATE_TRUNC('week', buys_source_src_10002.ds) AS ds__week + , DATE_TRUNC('month', buys_source_src_10002.ds) AS ds__month + , DATE_TRUNC('quarter', buys_source_src_10002.ds) AS ds__quarter + , DATE_TRUNC('year', buys_source_src_10002.ds) AS ds__year + , EXTRACT(year FROM buys_source_src_10002.ds) AS ds__extract_year + , EXTRACT(quarter FROM buys_source_src_10002.ds) AS ds__extract_quarter + , EXTRACT(month FROM buys_source_src_10002.ds) AS ds__extract_month + , EXTRACT(day FROM buys_source_src_10002.ds) AS ds__extract_day + , EXTRACT(isodow FROM buys_source_src_10002.ds) AS ds__extract_dow + , EXTRACT(doy FROM buys_source_src_10002.ds) AS ds__extract_doy + , DATE_TRUNC('day', buys_source_src_10002.ds) AS buy__ds__day + , DATE_TRUNC('week', buys_source_src_10002.ds) AS buy__ds__week + , DATE_TRUNC('month', buys_source_src_10002.ds) AS buy__ds__month + , DATE_TRUNC('quarter', buys_source_src_10002.ds) AS buy__ds__quarter + , DATE_TRUNC('year', buys_source_src_10002.ds) AS buy__ds__year + , EXTRACT(year FROM buys_source_src_10002.ds) AS buy__ds__extract_year + , EXTRACT(quarter FROM buys_source_src_10002.ds) AS buy__ds__extract_quarter + , EXTRACT(month FROM buys_source_src_10002.ds) AS buy__ds__extract_month + , EXTRACT(day FROM buys_source_src_10002.ds) AS buy__ds__extract_day + , EXTRACT(isodow FROM buys_source_src_10002.ds) AS buy__ds__extract_dow + , EXTRACT(doy FROM buys_source_src_10002.ds) AS buy__ds__extract_doy + , buys_source_src_10002.user_id AS user + , buys_source_src_10002.user_id AS buy__user + FROM ***************************.fct_buys buys_source_src_10002 + ) subq_7 + ) subq_8 + ) subq_9 + ) subq_10 + ON + ( + subq_6.user = subq_10.user + ) AND ( + ( + subq_6.ds__day <= subq_10.ds__day + ) AND ( + subq_6.ds__day > subq_10.ds__day - INTERVAL 7 day + ) + ) + ) subq_11 + ) subq_12 + ) subq_13 + GROUP BY + subq_13.visit__referrer_id + ) subq_14 + ON + subq_3.visit__referrer_id = subq_14.visit__referrer_id + GROUP BY + COALESCE(subq_3.visit__referrer_id, subq_14.visit__referrer_id) +) subq_15 diff --git a/metricflow/test/snapshots/test_dataflow_to_sql_plan.py/SqlQueryPlan/DuckDB/test_conversion_metric__plan0_optimized.sql b/metricflow/test/snapshots/test_dataflow_to_sql_plan.py/SqlQueryPlan/DuckDB/test_conversion_metric__plan0_optimized.sql new file mode 100644 index 0000000000..f340be4a39 --- /dev/null +++ b/metricflow/test/snapshots/test_dataflow_to_sql_plan.py/SqlQueryPlan/DuckDB/test_conversion_metric__plan0_optimized.sql @@ -0,0 +1,83 @@ +-- Compute Metrics via Expressions +SELECT + visit__referrer_id + , CAST(buys AS DOUBLE) / CAST(NULLIF(visits, 0) AS DOUBLE) AS visit_buy_conversion_rate +FROM ( + -- Combine Metrics + SELECT + COALESCE(subq_19.visit__referrer_id, subq_30.visit__referrer_id) AS visit__referrer_id + , MAX(subq_19.visits) AS visits + , MAX(subq_30.buys) AS buys + FROM ( + -- Aggregate Measures + SELECT + visit__referrer_id + , SUM(visits) AS visits + FROM ( + -- Read Elements From Semantic Model 'visits_source' + -- Metric Time Dimension 'ds' + -- Pass Only Elements: + -- ['visits', 'visit__referrer_id'] + SELECT + referrer_id AS visit__referrer_id + , 1 AS visits + FROM ***************************.fct_visits visits_source_src_10011 + ) subq_18 + GROUP BY + visit__referrer_id + ) subq_19 + FULL OUTER JOIN ( + -- Find conversions for EntitySpec(element_name='user', entity_links=()) within the range of count=7 granularity=TimeGranularity.DAY + -- Pass Only Elements: + -- ['buys', 'visit__referrer_id'] + -- Aggregate Measures + SELECT + visit__referrer_id + , SUM(buys) AS buys + FROM ( + -- Dedupe the fanout on (MetadataSpec(element_name='mf_internal_uuid'),) in the conversion data set + SELECT DISTINCT + first_value(subq_22.visits) OVER (PARTITION BY subq_26.user, subq_26.ds__day ORDER BY subq_22.ds__day DESC) AS visits + , first_value(subq_22.visit__referrer_id) OVER (PARTITION BY subq_26.user, subq_26.ds__day ORDER BY subq_22.ds__day DESC) AS visit__referrer_id + , subq_26.mf_internal_uuid AS mf_internal_uuid + , subq_26.buys AS buys + FROM ( + -- Read Elements From Semantic Model 'visits_source' + -- Metric Time Dimension 'ds' + -- Pass Only Elements: + -- ['visits', 'visit__referrer_id'] + SELECT + referrer_id AS visit__referrer_id + , 1 AS visits + FROM ***************************.fct_visits visits_source_src_10011 + ) subq_22 + INNER JOIN ( + -- Read Elements From Semantic Model 'buys_source' + -- Metric Time Dimension 'ds' + -- Add column with generated UUID + -- Pass Only Elements: + -- ['buys', 'mf_internal_uuid'] + SELECT + 1 AS buys + , GEN_RANDOM_UUID() AS mf_internal_uuid + FROM ***************************.fct_buys buys_source_src_10002 + ) subq_26 + ON + ( + subq_22.user = subq_26.user + ) AND ( + ( + subq_22.ds__day <= subq_26.ds__day + ) AND ( + subq_22.ds__day > subq_26.ds__day - INTERVAL 7 day + ) + ) + ) subq_27 + GROUP BY + visit__referrer_id + ) subq_30 + ON + subq_19.visit__referrer_id = subq_30.visit__referrer_id + GROUP BY + COALESCE(subq_19.visit__referrer_id, subq_30.visit__referrer_id) +) subq_31 diff --git a/metricflow/test/snapshots/test_dataflow_to_sql_plan.py/SqlQueryPlan/test_conversion_metric__plan0.xml b/metricflow/test/snapshots/test_dataflow_to_sql_plan.py/SqlQueryPlan/test_conversion_metric__plan0.xml new file mode 100644 index 0000000000..ebfbf27b26 --- /dev/null +++ b/metricflow/test/snapshots/test_dataflow_to_sql_plan.py/SqlQueryPlan/test_conversion_metric__plan0.xml @@ -0,0 +1,1206 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +