From 661d006f44a9fb6c19f00f25f4937f067f49f491 Mon Sep 17 00:00:00 2001 From: Quigley Malcolm Date: Fri, 8 Sep 2023 13:36:01 -0700 Subject: [PATCH] Add `date_spine` macro (and macros it depends on) from dbt-utils to core The macros added are - date_spine - get_intervals_between - generate_series - get_powers_of_two We're adding these to core because they are becoming more prevalently used with the increase usage in the semantic layer. Basically if you are using the semantic layer currently, then it is almost a requirement to use dbt-utils, which is undesireable given the SL is supported directly in core. The primary focus of this was to just add `date_spine`. However, because `date_spine` depends on other macros, these other macros were also moved. --- .../macros/utils/date_spine.sql | 75 +++++++++++++++++++ .../macros/utils/generate_series.sql | 53 +++++++++++++ 2 files changed, 128 insertions(+) create mode 100644 core/dbt/include/global_project/macros/utils/date_spine.sql create mode 100644 core/dbt/include/global_project/macros/utils/generate_series.sql diff --git a/core/dbt/include/global_project/macros/utils/date_spine.sql b/core/dbt/include/global_project/macros/utils/date_spine.sql new file mode 100644 index 00000000000..833fbcc575b --- /dev/null +++ b/core/dbt/include/global_project/macros/utils/date_spine.sql @@ -0,0 +1,75 @@ +{% macro get_intervals_between(start_date, end_date, datepart) -%} + {{ return(adapter.dispatch('get_intervals_between', 'dbt')(start_date, end_date, datepart)) }} +{%- endmacro %} + +{% macro default__get_intervals_between(start_date, end_date, datepart) -%} + {%- call statement('get_intervals_between', fetch_result=True) %} + + select {{ dbt.datediff(start_date, end_date, datepart) }} + + {%- endcall -%} + + {%- set value_list = load_result('get_intervals_between') -%} + + {%- if value_list and value_list['data'] -%} + {%- set values = value_list['data'] | map(attribute=0) | list %} + {{ return(values[0]) }} + {%- else -%} + {{ return(1) }} + {%- endif -%} + +{%- endmacro %} + + + + +{% macro date_spine(datepart, start_date, end_date) %} + {{ return(adapter.dispatch('date_spine', 'dbt')(datepart, start_date, end_date)) }} +{%- endmacro %} + +{% macro default__date_spine(datepart, start_date, end_date) %} + + + {# call as follows: + + date_spine( + "day", + "to_date('01/01/2016', 'mm/dd/yyyy')", + "dbt.dateadd(week, 1, current_date)" + ) #} + + + with rawdata as ( + + {{dbt.generate_series( + dbt.get_intervals_between(start_date, end_date, datepart) + )}} + + ), + + all_periods as ( + + select ( + {{ + dbt.dateadd( + datepart, + "row_number() over (order by 1) - 1", + start_date + ) + }} + ) as date_{{datepart}} + from rawdata + + ), + + filtered as ( + + select * + from all_periods + where date_{{datepart}} <= {{ end_date }} + + ) + + select * from filtered + +{% endmacro %} diff --git a/core/dbt/include/global_project/macros/utils/generate_series.sql b/core/dbt/include/global_project/macros/utils/generate_series.sql new file mode 100644 index 00000000000..f6a09605af3 --- /dev/null +++ b/core/dbt/include/global_project/macros/utils/generate_series.sql @@ -0,0 +1,53 @@ +{% macro get_powers_of_two(upper_bound) %} + {{ return(adapter.dispatch('get_powers_of_two', 'dbt')(upper_bound)) }} +{% endmacro %} + +{% macro default__get_powers_of_two(upper_bound) %} + + {% if upper_bound <= 0 %} + {{ exceptions.raise_compiler_error("upper bound must be positive") }} + {% endif %} + + {% for _ in range(1, 100) %} + {% if upper_bound <= 2 ** loop.index %}{{ return(loop.index) }}{% endif %} + {% endfor %} + +{% endmacro %} + + +{% macro generate_series(upper_bound) %} + {{ return(adapter.dispatch('generate_series', 'dbt')(upper_bound)) }} +{% endmacro %} + +{% macro default__generate_series(upper_bound) %} + + {% set n = dbt.get_powers_of_two(upper_bound) %} + + with p as ( + select 0 as generated_number union all select 1 + ), unioned as ( + + select + + {% for i in range(n) %} + p{{i}}.generated_number * power(2, {{i}}) + {% if not loop.last %} + {% endif %} + {% endfor %} + + 1 + as generated_number + + from + + {% for i in range(n) %} + p as p{{i}} + {% if not loop.last %} cross join {% endif %} + {% endfor %} + + ) + + select * + from unioned + where generated_number <= {{upper_bound}} + order by generated_number + +{% endmacro %}