Skip to content

Commit e5317f4

Browse files
Merge pull request #82 from dbt-labs/feature_develop_macro
Adding the develop macro
2 parents e99c912 + 038439e commit e5317f4

34 files changed

Lines changed: 1355 additions & 300 deletions

README.md

Lines changed: 64 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,12 @@
33
<!--ts-->
44
* [dbt_metrics](#dbt_metrics)
55
* [About](#about)
6-
* [Tenets](#tenets)
7-
* [Installation Instructions](#installation-instructions)
8-
* [Usage](#usage)
6+
* [Tenets](#tenets)
7+
* [Installation Instructions](#installation-instructions)
8+
* [Macros](#macros)
9+
* [Calculate](#calculate)
10+
* [Migration from metric to calculate](#migration-from-metric-to-calculate)
11+
* [Develop](#develop)
912
* [Use cases and examples](#use-cases-and-examples)
1013
* [Jaffle Shop Metrics](#jaffle-shop-metrics)
1114
* [Inside of dbt Models](#inside-of-dbt-models)
@@ -25,7 +28,7 @@
2528
* [Secondary calculation column aliases](#secondary-calculation-column-aliases)
2629

2730
<!-- Created by https://github.com/ekalinin/github-markdown-toc -->
28-
<!-- Added by: runner, at: Tue Jul 12 18:14:25 UTC 2022 -->
31+
<!-- Added by: runner, at: Tue Aug 23 15:13:02 UTC 2022 -->
2932

3033
<!--te-->
3134

@@ -50,11 +53,15 @@ Include in your `package.yml`
5053
```yaml
5154
packages:
5255
- package: dbt-labs/metrics
53-
version: 0.3.1
56+
version: [">=0.3.0", "<0.4.0"]
5457
```
5558
56-
# Usage
57-
Access metrics [like any other macro](https://docs.getdbt.com/docs/building-a-dbt-project/jinja-macros#using-a-macro-from-a-package):
59+
# Macros
60+
61+
## Calculate
62+
The calculate macro performs the metric aggregation and returns the dataset based on the specifications
63+
of the metric definition and the options selected in the macro. It can be accessed [like any other macro](https://docs.getdbt.com/docs/building-a-dbt-project/jinja-macros#using-a-macro-from-a-package):
64+
5865
```sql
5966
select *
6067
from {{ metrics.calculate(
@@ -79,6 +86,46 @@ from {{ metrics.calculate(
7986

8087
`start_date` and `end_date` are optional. When not provided, the spine will span all dates from oldest to newest in the metric's dataset. This default is likely to be correct in most cases, but you can use the arguments to either narrow the resulting table or expand it (e.g. if there was no new customers until 3 January but you want to include the first two days as well). Both values are inclusive.
8188

89+
### Migration from metric to calculate
90+
In version `0.3.0` of the dbt_metrics package, the name of the main macro was changed from `metric` to `calculate`. This was done in order to better reflect the work being performed by the macro and match the semantic naming followed by the rest of the macros in the package (describing the action, not the output). Additionally, the `metric_name` input was changed to take a single `metric` function or multiple `metric` functions provided in a list.
91+
92+
To correctly change this syntax, you must:
93+
- change `metrics.metric` to `metrics.calculate`.
94+
- change `metric_name` to `metric('name_here')`
95+
- alternatively use `[metric('name_here'),metric('another_name_here')]` for multiple metrics
96+
97+
## Develop
98+
There are times when you want to test what a metric might look like before defining it in your project. In these cases you should use the `develop` metric, which allows you to provide a single metric in a contained yml in order to simulate what the metric might loook like if defined in your project.
99+
100+
**Limitations:**
101+
- The provided yml can only contain one metric
102+
- The metric in question cannot be an expression metric
103+
104+
```sql
105+
{% set my_metric_yml -%}
106+
107+
metrics:
108+
- name: develop_metric
109+
model: ref('fact_orders')
110+
label: Total Discount ($)
111+
timestamp: order_date
112+
time_grains: [day, week, month]
113+
type: average
114+
sql: discount_total
115+
dimensions:
116+
- had_discount
117+
- order_country
118+
119+
{%- endset %}
120+
121+
select *
122+
from {{ metrics.develop(
123+
develop_yml=my_metric_yml,
124+
grain='month'
125+
)
126+
}}
127+
```
128+
82129
# Use cases and examples
83130

84131
## Jaffle Shop Metrics
@@ -177,7 +224,16 @@ vars:
177224
```
178225
179226
### Dimensions from calendar tables
180-
You may want to aggregate metrics by a dimension in your custom calendar table, for example `is_weekend`. You can include this within the list of `dimensions` in the macro call **without** it needing to be defined in the metric definition. The macro will correctly recognize that it is coming from the calendar dimension and treat it accordingly.
227+
You may want to aggregate metrics by a dimension in your custom calendar table, for example `is_weekend`. You can include this within the list of `dimensions` in the macro call **without** it needing to be defined in the metric definition.
228+
229+
To do so, set a list variable at the project level called `custom_calendar_dimension_list`, as shown in the example below.
230+
231+
```yml
232+
vars:
233+
custom_calendar_dimension_list: ["is_weekend"]
234+
```
235+
236+
The `is_weekend` field can now be used by your metrics.
181237

182238
## Time Grains
183239
The package protects against nonsensical secondary calculations, such as a month-to-date aggregate of data which has been rolled up to the quarter. If you customise your calendar (for example by adding a [4-5-4 retail calendar](https://calogica.com/sql/dbt/2018/11/15/retail-calendar-in-sql.html) month), you will need to override the [`get_grain_order()`](/macros/secondary_calculations/validate_grain_order.sql) macro. In that case, you might remove `month` and replace it with `month_4_5_4`. All date columns must be prefixed with `date_` in the table. Do not include the prefix when defining your metric, it will be added automatically.

integration_tests/dbt_project.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,4 +28,4 @@ models:
2828

2929
vars:
3030
dbt_metrics_calendar_model: custom_calendar
31-
testing: fact_orders
31+
custom_calendar_dimension_list: ["is_weekend"]

integration_tests/models/metric_testing_models/base_count_distinct_metric.yml

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
11
version: 2
22
models:
33
- name: base_count_distinct_metric
4-
tests:
5-
- dbt_utils.equality:
6-
compare_model: ref('base_count_distinct_metric__expected')
74

85
metrics:
96
- name: base_count_distinct_metric

integration_tests/models/metric_testing_models/base_sum_metric.sql

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,5 @@ from
33
{{ metrics.calculate(
44
metric('base_sum_metric'),
55
grain='day',
6-
dimensions=['had_discount'])
6+
dimensions=['had_discount','is_weekend'])
77
}}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
{% set my_metric_yml -%}
2+
3+
metrics:
4+
- name: develop_metric
5+
model: ref('fact_orders')
6+
label: Total Discount ($)
7+
timestamp: order_date
8+
time_grains: [day, week, month]
9+
type: average
10+
sql: discount_total
11+
dimensions:
12+
- had_discount
13+
- order_country
14+
15+
{%- endset %}
16+
17+
select *
18+
from {{ metrics.develop(
19+
develop_yml=my_metric_yml,
20+
grain='month'
21+
)
22+
}}

integration_tests/models/metric_testing_models/expression_metric.sql

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,6 @@ from
55
grain='day',
66
dimensions=['had_discount','order_country','is_weekend'],
77
start_date = '2022-01-01',
8-
end_date = '2022-01-10')
8+
end_date = '2022-01-10'
9+
)
910
}}

integration_tests/models/metric_testing_models/metric_on_expression_metric.sql

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,7 @@ from
55
grain='day',
66
dimensions=['had_discount','order_country','is_weekend'],
77
start_date = '2022-01-01',
8-
end_date = '2022-01-05')
8+
end_date = '2022-01-05'
9+
10+
)
911
}}

macros/calculate.sql

Lines changed: 77 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,93 @@
77
-- Need this here, since the actual ref is nested within loops/conditions:
88
-- depends on: {{ ref(var('dbt_metrics_calendar_model', 'dbt_metrics_default_calendar')) }}
99

10+
{# ############
11+
VARIABLE SETTING - Creating the metric tree and making sure metric list is a list!
12+
############ #}
13+
14+
{% if metric_list is not iterable %}
15+
{% set metric_list = [metric_list] %}
16+
{% endif %}
17+
18+
{% set metric_tree = metrics.get_metric_tree(metric_list) %}
19+
20+
{# ############
21+
SQL GEN VARIABLE SETTING - Gotta catch all those variables! Common dimension list is the pikachu
22+
############ #}
23+
24+
{# We have to break out calendar dimensions as their own list of acceptable dimensions.
25+
This is because of the date-spining. If we don't do this, it creates impossible combinations
26+
of calendar dimension + base dimensions #}
27+
{%- set calendar_dimensions = metrics.get_calendar_dimension_list() -%}
28+
29+
{# Additionally, we also have to restrict the dimensions coming in from the macro to
30+
no longer include those we've designated as calendar dimensions. That way they
31+
are correctly handled by the spining. We override the dimensions variable for
32+
cleanliness #}
33+
{%- set non_calendar_dimensions = metrics.get_non_calendar_dimension_list(dimensions, calendar_dimensions) -%}
34+
35+
{# Finally we set the relevant periods, which is a list of all time grains that need to be contained
36+
within the final dataset in order to accomplish base + secondary calc functionality. #}
37+
{%- set relevant_periods = metrics.get_relevent_periods(grain, secondary_calculations) %}
38+
39+
{# ############
40+
VALIDATION - Make sure everything is good!
41+
############ #}
42+
1043
{%- if not execute %}
11-
{%- do return("not execute") %}
44+
{%- do return("Did not execute") %}
45+
{%- endif %}
46+
47+
{%- if not metric_list %}
48+
{%- do exceptions.raise_compiler_error("No metric or metrics provided") %}
1249
{%- endif %}
1350

51+
{%- if not grain %}
52+
{%- do exceptions.raise_compiler_error("No date grain provided") %}
53+
{%- endif %}
54+
55+
{% if where is iterable and (where is not string and where is not mapping) %}
56+
{%- do exceptions.raise_compiler_error("From v0.3.0 onwards, the where clause takes a single string, not a list of filters. Please fix to reflect this change") %}
57+
{% endif %}
58+
59+
{% do metrics.validate_grain(grain, metric_tree['full_set'], metric_tree['base_set'])%}
60+
61+
{% do metrics.validate_expression_metrics(metric_tree['full_set'])%}
62+
63+
{% do metrics.validate_dimension_list(dimensions, metric_tree['full_set'], calendar_dimensions) %}
64+
65+
{# ############
66+
SECONDARY CALCULATION VALIDATION - Let there be window functions
67+
############ #}
68+
69+
{% for metric in metric_list %}
70+
{% set metric_type = metric.type%}
71+
{%- for calc_config in secondary_calculations if calc_config.aggregate %}
72+
{%- do metrics.validate_aggregate_coherence(metric_type, calc_config.aggregate) %}
73+
{%- endfor %}
74+
{%endfor%}
75+
76+
{%- for calc_config in secondary_calculations if calc_config.period %}
77+
{%- do metrics.validate_grain_order(grain, calc_config.period) %}
78+
{%- endfor %}
79+
80+
{# ############
81+
SQL GENERATION - Lets build that SQL!
82+
############ #}
83+
1484
{%- set sql = metrics.get_metric_sql(
1585
metric_list=metric_list,
1686
grain=grain,
1787
dimensions=dimensions,
1888
secondary_calculations=secondary_calculations,
1989
start_date=start_date,
2090
end_date=end_date,
21-
where=where
91+
where=where,
92+
initiated_by='calculate',
93+
metric_tree=metric_tree,
94+
calendar_dimensions=calendar_dimensions,
95+
non_calendar_dimensions=non_calendar_dimensions,
96+
relevant_periods=relevant_periods
2297
) %}
2398
({{ sql }}) metric_subq
2499
{%- endmacro %}

macros/develop.sql

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
{% macro develop(develop_yml, grain, dimensions=[], secondary_calculations=[], start_date=None, end_date=None, where=None) -%}
2+
{{ return(adapter.dispatch('develop', 'metrics')(develop_yml, grain, dimensions, secondary_calculations, start_date, end_date, where)) }}
3+
{% endmacro %}
4+
5+
6+
{% macro default__develop(develop_yml, grain, dimensions=[], secondary_calculations=[], start_date=None, end_date=None, where=None) -%}
7+
-- Need this here, since the actual ref is nested within loops/conditions:
8+
-- depends on: {{ ref(var('dbt_metrics_calendar_model', 'dbt_metrics_default_calendar')) }}
9+
10+
{%- if not execute %}
11+
{%- do return("not execute") %}
12+
{%- endif %}
13+
14+
{% set develop_yml = fromyaml(develop_yml)%}
15+
16+
{# ############
17+
VALIDATION OF PROVIDED YML - Gotta make sure the metric looks good!
18+
############ #}
19+
20+
{% if develop_yml["metrics"] | length > 1%}
21+
{%- do exceptions.raise_compiler_error("The develop macro only supports testing a single macro.") %}
22+
{% endif %}
23+
24+
{% set metric_definition = develop_yml["metrics"][0] %}
25+
26+
{%- if not metric_definition["name"] %}
27+
{%- do exceptions.raise_compiler_error("The provided yml is missing a name") %}
28+
{%- endif %}
29+
30+
{%- if not metric_definition["model"] %}
31+
{%- do exceptions.raise_compiler_error("The provided yml is missing a model") %}
32+
{%- endif %}
33+
34+
{%- if not metric_definition["timestamp"] %}
35+
{%- do exceptions.raise_compiler_error("The provided yml is missing a timestamp") %}
36+
{%- endif %}
37+
38+
{%- if not metric_definition["time_grains"] %}
39+
{%- do exceptions.raise_compiler_error("The provided yml is missing time grains") %}
40+
{%- endif %}
41+
42+
{%- if grain not in metric_definition["time_grains"] %}
43+
{%- do exceptions.raise_compiler_error("The selected grain is missing from the metric definition yml") %}
44+
{%- endif %}
45+
46+
{%- if not metric_definition["type"] %}
47+
{%- do exceptions.raise_compiler_error("The provided yml is missing a metric type") %}
48+
{%- endif %}
49+
50+
{%- if metric_definition["type"] == 'expression' %}
51+
{%- do exceptions.raise_compiler_error("The develop macro does not support expression metrics") %}
52+
{%- endif %}
53+
54+
{%- if not metric_definition["sql"] %}
55+
{%- do exceptions.raise_compiler_error("The provided yml is missing a sql field") %}
56+
{%- endif %}
57+
58+
{% for dim in dimensions %}
59+
{% if dim not in metric_definition["dimensions"] %}
60+
{%- do exceptions.raise_compiler_error("The macro provided dimension is missing from the metric definition") %}
61+
{% endif %}
62+
{% endfor %}
63+
64+
{# ############
65+
VALIDATION OF MACRO INPUTS - Making sure we have a provided grain!
66+
############ #}
67+
68+
{%- if not grain %}
69+
{%- do exceptions.raise_compiler_error("No date grain provided") %}
70+
{%- endif %}
71+
72+
{# ############
73+
VARIABLE SETTING - Creating the faux metric tree and faux metric list. The faux fur of 2022
74+
############ #}
75+
76+
{% set metric_list = [metric_definition["name"]] %}
77+
{% set metric_tree = metrics.get_faux_metric_tree(metric_list) %}
78+
{% set metric_type = metric_definition["type"]%}
79+
80+
{# ############
81+
SECONDARY CALCULATION VALIDATION - Gotta make sure the secondary calcs are good!
82+
############ #}
83+
84+
{%- for calc_config in secondary_calculations if calc_config.aggregate %}
85+
{%- do metrics.validate_aggregate_coherence(metric_type, calc_config.aggregate) %}
86+
{%- endfor %}
87+
88+
{%- for calc_config in secondary_calculations if calc_config.period %}
89+
{%- do metrics.validate_grain_order(grain, calc_config.period) %}
90+
{%- endfor %}
91+
92+
{# ############
93+
VARIABLES FOR SQL GEN - More variables we need for sql gen
94+
############ #}
95+
96+
{%- set calendar_dimensions = metrics.get_calendar_dimension_list() -%}
97+
{%- set non_calendar_dimensions = metrics.get_non_calendar_dimension_list(dimensions,calendar_dimensions) -%}
98+
{%- set relevant_periods = metrics.get_relevent_periods(grain, secondary_calculations) %}
99+
100+
{# ############
101+
SQL GENERATION - Lets build that SQL!
102+
############ #}
103+
104+
{%- set sql = metrics.get_metric_sql(
105+
metric_list=metric_list,
106+
grain=grain,
107+
dimensions=dimensions,
108+
secondary_calculations=secondary_calculations,
109+
start_date=start_date,
110+
end_date=end_date,
111+
where=where,
112+
initiated_by='develop',
113+
metric_definition=metric_definition,
114+
metric_tree=metric_tree,
115+
calendar_dimensions=calendar_dimensions,
116+
non_calendar_dimensions=non_calendar_dimensions,
117+
relevant_periods=relevant_periods
118+
) %}
119+
({{ sql }}) metric_subq
120+
{%- endmacro %}

0 commit comments

Comments
 (0)