SlideShare a Scribd company logo
PostgreSQL Ranges & Intervals:
Data structures for calendars.
Steven Lembark
Workhorse Computing
lembark@wrkhors.com
Generating a calendar.
Periods by type: Annual, Quarterly.
Effective window by start/end date.
Hierarchy of periods: Quarters within Years.
Generating a calendar.
Search by publication date.
Classify by effective period.
Search for documents by time.
Define document expiration.
Define searchable window.
Defining a calendar.
Start with two relations:
Start date + Calendar Size
Period types.
Fiscal calendar has arbitrary start.
Wallclock calendar starts on 01-Jan.
Defining a calendar.
Start with two relations:
( start, window )
( type, size )
Defining a calendar.
Start with two relations:
( date, interval )
( enum, interval )
Defining a date
“date” type.
Stringy definitions:
’01-Feb-2021’
’2021.02.01’
Has no time.
Conversion to timestamp with ‘00:00:00’
Handling enums
One way:
table foo ( date_type enum ( ‘year’,’month’ ) … );
table bar ( date_type enum ( ‘year’,’month’ ) … );
Handling enums
One way:
table foo ( date_type enum ( ‘year’,’month’ ) … );
table bar ( date_type enum ( ‘year’,’month’ ) … );
Oops:
table foo ( date_type enum ( ‘year’,’month’ ) … );
table bar ( date_type enum ( ‘year’,’mnoth’ ) … );
Handling enums
Better way:
create type date_t enum ( ‘year’, ‘month’ );
table foo ( date_type date_t, … );
table bar ( date_type date_t, … );
Handling enums
Additions in one place:
create type date_t enum ( ‘year’, ‘month’, ‘quarter’ );
table foo ( date_type date_t, … );
table bar ( date_type date_t, … );
Intervals in time
Intervals define periods between dates and times.
Offset timestamp, date, time values.
Intervals in time
YEAR
MONTH
DAY
HOUR
MINUTE
SECOND
YEAR TO MONTH
DAY TO HOUR
DAY TO MINUTE
DAY TO SECOND
HOUR TO MINUTE
HOUR TO SECOND
MINUTE TO SECOND
Intervals in time with rounding
YEAR
MONTH
DAY
HOUR
MINUTE
SECOND
YEAR TO MONTH
DAY TO HOUR
DAY TO MINUTE
DAY TO SECOND
HOUR TO MINUTE
HOUR TO SECOND
MINUTE TO SECOND
Interval input
interval ‘1 year’
interval ‘2 years’
interval ‘2 years 1 month 3 days 5 hours 6 seconds’
PG handles leap year for you:
‘31-Jan-2020’::date + interval ‘29 days’
Definine the period window
The definition of start date + window.
Should have only one row.
Minimal definition of start time.
Minimal definition of end time.
Definine the period window
create table period_window
(
one_row integer not null
primary key default 0
check( one_row = 0 )
, base_year integer not null
check
(
base_year > 2000
and
base_year <= date_part('year', now() )
)
, final_window interval year
not null
check
(
final_window > interval '0 year'
and
final_window < interval '20 year'
)
);
One row:
Single definition.
Definine the period window
create table period_window
(
one_row integer not null
primary key default 0
check( one_row = 0 )
, base_year integer not null
check
(
base_year > 2000
and
base_year <= date_part('year', now() )
)
, final_window interval year
not null
check
(
final_window > interval '0 year'
and
final_window < interval '20 year'
)
);
One row:
First year:
2000 to current.
Fiscal calendar uses
date.
Definine the period window
create table period_window
(
one_row integer not null
primary key default 0
check( one_row = 0 )
, base_year integer not null
check
(
base_year > 2000
and
base_year <= date_part('year', now() )
)
, final_window interval year
not null
check
(
final_window > interval '0 year'
and
final_window < interval '20 year'
)
);
One row:
First year:
Interval:
Defined in years.
Define time units
Start with units of time.
Convienent names:
create type period_t
as enum
(
‘annual’
, ‘quarterly’
, ‘monthly’
, ‘weekly’
, ‘daily’
)
;
Define time units
Start with units of time.
Map name to intervals:
create type period_t
as enum
(
‘annual’
, ‘quarterly’
, ‘monthly’
, ‘weekly’
, ‘daily’
)
;
create table period_interval
(
period_type
period_t
primary key
, period_size
interval not null
, unique
( period )
);
Loading initial data
Not much data:
Integer + window.
Few basic types.
insert into period_window
(
base_year
, final_window
)
values
(
2018, interval '10 years'
);
insert into period_interval
(
period_size
, period_type
)
values
( '1 year' , ‘annual’ )
, ( '3 months' , ‘quarterly’ )
, ( '1 month' , ‘monthly’ )
, ( ‘7 days’ , ‘weekly’ )
, ( '1 day' , ‘daily’ )
Generate a calendar
“generated...”
Replace sequence type.
Allows cloning a table.
create table period
(
period_sk
integer
generated by default as identity
primary key
, period_type
period_t
references period_interval
, period
daterange
not null
, unique
(
period
)
);
Generate a calendar
Re-use enum via type.
create table period
(
period_sk
integer
generated by default as identity
primary key
, period_type
period_t
references period_interval
, period
daterange
not null
, unique
(
period
)
);
Generate a calendar
Periods are ranges.
Periods are unique.
create table period
(
period_sk
integer
generated by default as identity
primary key
, period_type
period_t
references period_interval
, period
daterange
not null
, unique
(
period
)
);
First step: Start date.
We have an integer:
base_year.
Intervals offset
timestamps.
select
make_timestamp
(
base_year, 1, 1, 0, 0, 0
) "base_date"
, final_window
from
period_window
Generate a calendar
“Common Table
Expression”
“base_date” is a
pseudo-table.
Defined for one query.
with base_date
as
(
select
make_timestamp
(
base_year, 1, 1, 0, 0, 0
) "base_date"
, final_window
from
period_window
)
Generate a calendar
“cross join”:
start timesteamp
end interval
name name
width interval
with base_date
as ...
(
select
period_type
, period
, generate_series
(
base_date
, base_date + final_window
, period_size
) "start"
from
base_date
cross join
period_interval
) z
Generate a calendar
generate_series:
Multiple rows.
From base to final.
Units of period interval.
Generate a calendar
with base_date
as ...
from
(
select
period_type
, period
, generate_series
(
base_date
, base_date + final_window
, period_size
) "start"
from
base_date
cross join
period_interval
) z
‘annual’
, interval ‘1 year’
, ‘01-Jan-2020’
...
‘annual’
, interval ‘1 year’
, ‘01-Jan-2021’
with base_date
as ...
from
(
select
period_type
, period
, generate_series
(
base_date
, base_date + final_window
, period_size
) "start"
from
base_date
cross join
period_interval
) z
Generate a calendar
‘quarterly’
, interval ‘3 months’
, ‘01-Jan-2020’
...
‘annual’
, interval ‘3 months’
, ‘01-Apr-2020’
with base_date
as ...
from
(
select
period_type
, period
, generate_series
(
base_date
, base_date + final_window
, period_size
) "start"
from
base_date
cross join
period_interval
) z
Generate a calendar
Syntax:
Sub-queries get names.
with base_date
as ...
from
(
select
period_type
, period
, generate_series
(
base_date
, base_date + final_window
, period_size
) "start"
from
base_date
cross join
period_interval
) z
Generate a calendar
Sequence of values:
“daterange” composite:
lower
upper
with base_date
as ...
select
period_type
, daterange
(
start::date
, ( start + period )::date
, '[)'
)
from
(
select
period_type
, period_size
, generate_series ... “start”
...
)z
Generate a calendar
Result:
Periods by width.
period_sk | period_type | period
----------+-------------+-------------------------
1 | annual | [2018-01-01,2019-01-01)
2 | annual | [2019-01-01,2020-01-01)
3 | annual | [2020-01-01,2021-01-01)
4 | annual | [2021-01-01,2022-01-01)
5 | annual | [2022-01-01,2023-01-01)
6 | annual | [2023-01-01,2024-01-01)
7 | annual | [2024-01-01,2025-01-01)
8 | annual | [2025-01-01,2026-01-01)
9 | annual | [2026-01-01,2027-01-01)
10 | annual | [2027-01-01,2028-01-01)
11 | annual | [2028-01-01,2029-01-01)
12 | quarterly | [2018-01-01,2018-04-01)
13 | quarterly | [2018-04-01,2018-07-01)
14 | quarterly | [2018-07-01,2018-10-01)
15 | quarterly | [2018-10-01,2019-01-01)
16 | quarterly | [2019-01-01,2019-04-01)
17 | quarterly | [2019-04-01,2019-07-01)
18 | quarterly | [2019-07-01,2019-10-01)
Generate a calendar
Result:
‘[ … , … )’
Non-inclusive upper.
Ranges don’t intersect.
period_sk | period_type | period
----------+-------------+-------------------------
1 | annual | [2018-01-01,2019-01-01)
2 | annual | [2019-01-01,2020-01-01)
3 | annual | [2020-01-01,2021-01-01)
4 | annual | [2021-01-01,2022-01-01)
5 | annual | [2022-01-01,2023-01-01)
6 | annual | [2023-01-01,2024-01-01)
7 | annual | [2024-01-01,2025-01-01)
8 | annual | [2025-01-01,2026-01-01)
9 | annual | [2026-01-01,2027-01-01)
10 | annual | [2027-01-01,2028-01-01)
11 | annual | [2028-01-01,2029-01-01)
12 | quarterly | [2018-01-01,2018-04-01)
13 | quarterly | [2018-04-01,2018-07-01)
14 | quarterly | [2018-07-01,2018-10-01)
15 | quarterly | [2018-10-01,2019-01-01)
16 | quarterly | [2019-01-01,2019-04-01)
17 | quarterly | [2019-04-01,2019-07-01)
18 | quarterly | [2019-07-01,2019-10-01)
Generate a calendar
Result:
‘[ … , … )’
“2019-01-01”
matches #2 & #16 on
[2019-01-01, … )
period_sk | period_type | period
----------+-------------+-------------------------
1 | annual | [2018-01-01,2019-01-01)
2 | annual | [2019-01-01,2020-01-01)
3 | annual | [2020-01-01,2021-01-01)
4 | annual | [2021-01-01,2022-01-01)
5 | annual | [2022-01-01,2023-01-01)
6 | annual | [2023-01-01,2024-01-01)
7 | annual | [2024-01-01,2025-01-01)
8 | annual | [2025-01-01,2026-01-01)
9 | annual | [2026-01-01,2027-01-01)
10 | annual | [2027-01-01,2028-01-01)
11 | annual | [2028-01-01,2029-01-01)
12 | quarterly | [2018-01-01,2018-04-01)
13 | quarterly | [2018-04-01,2018-07-01)
14 | quarterly | [2018-07-01,2018-10-01)
15 | quarterly | [2018-10-01,2019-01-01)
16 | quarterly | [2019-01-01,2019-04-01)
17 | quarterly | [2019-04-01,2019-07-01)
18 | quarterly | [2019-07-01,2019-10-01)
Generate a calendar
Selecting from the calendar
“Range contains”
‘@>’
select
period_sk
, period_type
, period
from
period
where
period @> '15-Mar-2021'::date
order by
period_type
, period
;
Selecting from the calendar
All period types.
No match on:
‘15-Mar-2021’ )
select
period_sk
, period_type
, period
from
period
where
period @> '15-Mar-2021'::date
order by
period_type
, period
;
period_sk | period_type | period
-----------+-------------+-------------------------
4 | annual | [2021-01-01,2022-01-01)
24 | quarterly | [2021-01-01,2021-04-01)
91 | monthly | [2021-03-01,2021-04-01)
341 | weekly | [2021-03-15,2021-03-22)
1865 | single | [2021-03-15,2021-03-16)
(5 rows)
Selecting from the calendar
Intersecting:
‘&&’
select
period_sk, period_type, period
from
curr_report_period
where
period && daterange( '15-Mar-2021', '20-Aug-2022' )
and
period_type = 'quarterly'
order by
period_type
, period
;
period_sk | period_type | period
-----------+-------------+-------------------------
24 | quarterly | [2021-01-01,2021-04-01)
25 | quarterly | [2021-04-01,2021-07-01)
26 | quarterly | [2021-07-01,2021-10-01)
27 | quarterly | [2021-10-01,2022-01-01)
28 | quarterly | [2022-01-01,2022-04-01)
29 | quarterly | [2022-04-01,2022-07-01)
30 | quarterly | [2022-07-01,2022-10-01)
Indexing the calendar
GiST indexes describe arbitrary trees.
Originally designed for full-text search.
Search where ‘=’ doesn’t apply.
Indexing the calendar
GiST indexes
Good for ranges
and areas.
create index on
period
using gist
(
period
)
include ( period_sk )
where active
;
create index on
period
using gist
(
period
, period_type
)
include ( period_sk )
where active
;
Indexing the calendar
GiST indexes
Adds un-indexed
data to the index
leaf nodes.
Query on period
gets SK.
create index on
period
using gist
(
period
)
include ( period_sk )
where active
;
create index on
period
using gist
(
period
, period_type
)
include ( period_sk )
where active
;
Using the index
Find partitions with
quarterly reports
including these dates.
explain
select
period_sk
from
period
where
period && daterange( '15-Mar-2021', '20-Aug-2022' )
and
period_type = 'quarterly'
;
Using the index
Find partitions with
quarterly reports
including these dates.
Partition of data can
use period_sk.
explain
select
period_sk
from
period
where
period && daterange( '15-Mar-2021', '20-Aug-2022' )
and
period_type = 'quarterly'
;
Plan without index
QUERY PLAN
-------------------------------------------------------------------------------------------
Bitmap Heap Scan on period (cost=15.87..80.54 rows=1 width=4)
Recheck Cond: active
Filter: ((period && '[2021-03-15,2022-08-20)'::daterange) AND (period_type =
'quarterly'::period_t))
-> Bitmap Index Scan on period_period_sk_idx
(cost=0.00..15.87 rows=1578 width=0)
(4 rows)
Plan with index
QUERY PLAN
-------------------------------------------------------------------------------------------
Bitmap Heap Scan on period (cost=15.87..80.54 rows=1 width=4)
Recheck Cond: active
Filter: ((period && '[2021-03-15,2022-08-20)'::daterange) AND (period_type =
'quarterly'::period_t))
-> Bitmap Index Scan on period_period_sk_idx
(cost=0.00..15.87 rows=1578 width=0)
(4 rows)
QUERY PLAN
-------------------------------------------------------------------------------------------
Index Only Scan using period_period_period_type_period_sk_idx on period
(cost=0.15..2.37 rows=1 width=4)
Index Cond: ((period && '[2021-03-15,2022-08-20)'::daterange) AND (period_type =
'quarterly'::period_t))
(2 rows)
Plan with index
QUERY PLAN
-------------------------------------------------------------------------------------------
Bitmap Heap Scan on period (cost=15.87..80.54 rows=1 width=4)
Recheck Cond: active
Filter: ((period && '[2021-03-15,2022-08-20)'::daterange) AND (period_type =
'quarterly'::period_t))
-> Bitmap Index Scan on period_period_sk_idx
(cost=0.00..15.87 rows=1578 width=0)
(4 rows)
QUERY PLAN
-------------------------------------------------------------------------------------------
Index Only Scan using period_period_period_type_period_sk_idx on period
(cost=0.15..2.37 rows=1 width=4)
Index Cond: ((period && '[2021-03-15,2022-08-20)'::daterange) AND (period_type =
'quarterly'::period_t))
(2 rows)
Plan with index
QUERY PLAN
-------------------------------------------------------------------------------------------
Bitmap Heap Scan on period (cost=15.87..80.54 rows=1 width=4)
Recheck Cond: active
Filter: ((period && '[2021-03-15,2022-08-20)'::daterange) AND (period_type =
'quarterly'::period_t))
-> Bitmap Index Scan on period_period_sk_idx
(cost=0.00..15.87 rows=1578 width=0)
(4 rows)
QUERY PLAN
-------------------------------------------------------------------------------------------
Index Only Scan using period_period_period_type_period_sk_idx on period
(cost=0.15..2.37 rows=1 width=4)
Index Cond: ((period && '[2021-03-15,2022-08-20)'::daterange) AND (period_type =
'quarterly'::period_t))
(2 rows)
Plan with index
QUERY PLAN
-------------------------------------------------------------------------------------------
Index Only Scan using period_period_period_type_period_sk_idx on period
(cost=0.15..2.37 rows=1 width=4)
Index Cond: ((period && '[2021-03-15,2022-08-20)'::daterange) AND (period_type =
'quarterly'::period_t))
(2 rows)
Works in PG: Data types & extensibility.
Range type & GiST index.
Summary
Tables for window, intervals:
Declarative definition of data.
No iteration, no loops.
Just sets & SQL.
Summary
Types for calendar:
interval
daterange & tsrange & tstzrange
Series of values:
generate_series( start, end, step );
Summary
SQL:
Common Table Expression (“CTE”).
Inlined in Postgres – fast.
Name subqueries.
Cross-join == Cartesian Product.
Bedside reading
https://www.postgresql.org/docs/13/...
textsearch-indexes.html GIN & GiST
rangetypes.html Data types
datatype-datetime.html Date & Time spec’s
functions-datetime.html Date op’s & func’s.
functions-range.html Range op’s & func’s.

More Related Content

PDF
SessionSeven_WorkingWithDatesandTime
DOCX
Builtinfunctions in vbscript and its types.docx
PDF
How to work with dates and times in swift 3
DOCX
Design a class named Month. The class should have the following priv
PDF
17 ruby date time
PPTX
YTD, QTD & MTD.pptx
PDF
Python and PostgreSQL: Let's Work Together! | PyConFr 2018 | Dimitri Fontaine
PDF
Dax queries.pdf
SessionSeven_WorkingWithDatesandTime
Builtinfunctions in vbscript and its types.docx
How to work with dates and times in swift 3
Design a class named Month. The class should have the following priv
17 ruby date time
YTD, QTD & MTD.pptx
Python and PostgreSQL: Let's Work Together! | PyConFr 2018 | Dimitri Fontaine
Dax queries.pdf

Similar to Generating & Querying Calendar Tables in Posgresql (20)

PDF
Change to oop formatimport java.util.Scanner;import java.io.;.pdf
PPT
Project presentation(View calender)
PDF
Cara membuat time schedule dan kurva s
PPTX
Date and time functions.pptx
PDF
LCC Asia Pacific_financialmodelling_excelshortcuts1
PDF
Visual Programming Lacture Nine 9 Structure.pdf
PPT
27. mathematical, date and time functions in VB Script
PPTX
MYSQL DATA TYPES for newbies helpful.pptx
PPTX
How To Create Calendar View In Odoo 16
PPTX
Advanced Views - Calendar View in Odoo 17
PDF
Data Wrangling: Working with Date / Time Data and Visualizing It
PPT
AIN102D Access date functions sample queries
PDF
Calculation Groups - color 1 slide per page.pdf
PPTX
TSQL Functions (SQL Server)
PDF
Date and Timestamp Types In Snowflake (By Faysal Shaarani)
PPTX
Intro to DAX Patterns
PPTX
Date object.pptx date and object v
PDF
Business Intelligence Portfolio
PDF
Business Intelligence Portfolio
PPTX
unit 5_Real time Data Analysis vsp.pptx
Change to oop formatimport java.util.Scanner;import java.io.;.pdf
Project presentation(View calender)
Cara membuat time schedule dan kurva s
Date and time functions.pptx
LCC Asia Pacific_financialmodelling_excelshortcuts1
Visual Programming Lacture Nine 9 Structure.pdf
27. mathematical, date and time functions in VB Script
MYSQL DATA TYPES for newbies helpful.pptx
How To Create Calendar View In Odoo 16
Advanced Views - Calendar View in Odoo 17
Data Wrangling: Working with Date / Time Data and Visualizing It
AIN102D Access date functions sample queries
Calculation Groups - color 1 slide per page.pdf
TSQL Functions (SQL Server)
Date and Timestamp Types In Snowflake (By Faysal Shaarani)
Intro to DAX Patterns
Date object.pptx date and object v
Business Intelligence Portfolio
Business Intelligence Portfolio
unit 5_Real time Data Analysis vsp.pptx
Ad

More from Workhorse Computing (20)

PDF
Object::Trampoline: Follow the bouncing object.
PDF
Wheels we didn't re-invent: Perl's Utility Modules
PDF
mro-every.pdf
PDF
Paranormal statistics: Counting What Doesn't Add Up
PDF
The $path to knowledge: What little it take to unit-test Perl.
PDF
Unit Testing Lots of Perl
PDF
Hypers and Gathers and Takes! Oh my!
PDF
BSDM with BASH: Command Interpolation
PDF
Findbin libs
PDF
Memory Manglement in Raku
PDF
BASH Variables Part 1: Basic Interpolation
PDF
Effective Benchmarks
PDF
Metadata-driven Testing
PDF
The W-curve and its application.
PDF
Keeping objects healthy with Object::Exercise.
PDF
Perl6 Regexen: Reduce the line noise in your code.
PDF
Smoking docker
PDF
Getting Testy With Perl6
PDF
Neatly Hashing a Tree: FP tree-fold in Perl5 & Perl6
PDF
Neatly folding-a-tree
Object::Trampoline: Follow the bouncing object.
Wheels we didn't re-invent: Perl's Utility Modules
mro-every.pdf
Paranormal statistics: Counting What Doesn't Add Up
The $path to knowledge: What little it take to unit-test Perl.
Unit Testing Lots of Perl
Hypers and Gathers and Takes! Oh my!
BSDM with BASH: Command Interpolation
Findbin libs
Memory Manglement in Raku
BASH Variables Part 1: Basic Interpolation
Effective Benchmarks
Metadata-driven Testing
The W-curve and its application.
Keeping objects healthy with Object::Exercise.
Perl6 Regexen: Reduce the line noise in your code.
Smoking docker
Getting Testy With Perl6
Neatly Hashing a Tree: FP tree-fold in Perl5 & Perl6
Neatly folding-a-tree
Ad

Recently uploaded (20)

PPTX
MYSQL Presentation for SQL database connectivity
PPTX
Spectroscopy.pptx food analysis technology
PDF
Encapsulation theory and applications.pdf
PPTX
Group 1 Presentation -Planning and Decision Making .pptx
PPTX
Digital-Transformation-Roadmap-for-Companies.pptx
PPTX
Machine Learning_overview_presentation.pptx
PDF
The Rise and Fall of 3GPP – Time for a Sabbatical?
PDF
Profit Center Accounting in SAP S/4HANA, S4F28 Col11
PPTX
Programs and apps: productivity, graphics, security and other tools
PDF
Getting Started with Data Integration: FME Form 101
PDF
Advanced methodologies resolving dimensionality complications for autism neur...
PDF
cuic standard and advanced reporting.pdf
PDF
MIND Revenue Release Quarter 2 2025 Press Release
PDF
Approach and Philosophy of On baking technology
PDF
Mobile App Security Testing_ A Comprehensive Guide.pdf
PDF
Building Integrated photovoltaic BIPV_UPV.pdf
PPTX
1. Introduction to Computer Programming.pptx
PDF
Optimiser vos workloads AI/ML sur Amazon EC2 et AWS Graviton
PDF
Dropbox Q2 2025 Financial Results & Investor Presentation
PPTX
SOPHOS-XG Firewall Administrator PPT.pptx
MYSQL Presentation for SQL database connectivity
Spectroscopy.pptx food analysis technology
Encapsulation theory and applications.pdf
Group 1 Presentation -Planning and Decision Making .pptx
Digital-Transformation-Roadmap-for-Companies.pptx
Machine Learning_overview_presentation.pptx
The Rise and Fall of 3GPP – Time for a Sabbatical?
Profit Center Accounting in SAP S/4HANA, S4F28 Col11
Programs and apps: productivity, graphics, security and other tools
Getting Started with Data Integration: FME Form 101
Advanced methodologies resolving dimensionality complications for autism neur...
cuic standard and advanced reporting.pdf
MIND Revenue Release Quarter 2 2025 Press Release
Approach and Philosophy of On baking technology
Mobile App Security Testing_ A Comprehensive Guide.pdf
Building Integrated photovoltaic BIPV_UPV.pdf
1. Introduction to Computer Programming.pptx
Optimiser vos workloads AI/ML sur Amazon EC2 et AWS Graviton
Dropbox Q2 2025 Financial Results & Investor Presentation
SOPHOS-XG Firewall Administrator PPT.pptx

Generating & Querying Calendar Tables in Posgresql

  • 1. PostgreSQL Ranges & Intervals: Data structures for calendars. Steven Lembark Workhorse Computing [email protected]
  • 2. Generating a calendar. Periods by type: Annual, Quarterly. Effective window by start/end date. Hierarchy of periods: Quarters within Years.
  • 3. Generating a calendar. Search by publication date. Classify by effective period. Search for documents by time. Define document expiration. Define searchable window.
  • 4. Defining a calendar. Start with two relations: Start date + Calendar Size Period types. Fiscal calendar has arbitrary start. Wallclock calendar starts on 01-Jan.
  • 5. Defining a calendar. Start with two relations: ( start, window ) ( type, size )
  • 6. Defining a calendar. Start with two relations: ( date, interval ) ( enum, interval )
  • 7. Defining a date “date” type. Stringy definitions: ’01-Feb-2021’ ’2021.02.01’ Has no time. Conversion to timestamp with ‘00:00:00’
  • 8. Handling enums One way: table foo ( date_type enum ( ‘year’,’month’ ) … ); table bar ( date_type enum ( ‘year’,’month’ ) … );
  • 9. Handling enums One way: table foo ( date_type enum ( ‘year’,’month’ ) … ); table bar ( date_type enum ( ‘year’,’month’ ) … ); Oops: table foo ( date_type enum ( ‘year’,’month’ ) … ); table bar ( date_type enum ( ‘year’,’mnoth’ ) … );
  • 10. Handling enums Better way: create type date_t enum ( ‘year’, ‘month’ ); table foo ( date_type date_t, … ); table bar ( date_type date_t, … );
  • 11. Handling enums Additions in one place: create type date_t enum ( ‘year’, ‘month’, ‘quarter’ ); table foo ( date_type date_t, … ); table bar ( date_type date_t, … );
  • 12. Intervals in time Intervals define periods between dates and times. Offset timestamp, date, time values.
  • 13. Intervals in time YEAR MONTH DAY HOUR MINUTE SECOND YEAR TO MONTH DAY TO HOUR DAY TO MINUTE DAY TO SECOND HOUR TO MINUTE HOUR TO SECOND MINUTE TO SECOND
  • 14. Intervals in time with rounding YEAR MONTH DAY HOUR MINUTE SECOND YEAR TO MONTH DAY TO HOUR DAY TO MINUTE DAY TO SECOND HOUR TO MINUTE HOUR TO SECOND MINUTE TO SECOND
  • 15. Interval input interval ‘1 year’ interval ‘2 years’ interval ‘2 years 1 month 3 days 5 hours 6 seconds’ PG handles leap year for you: ‘31-Jan-2020’::date + interval ‘29 days’
  • 16. Definine the period window The definition of start date + window. Should have only one row. Minimal definition of start time. Minimal definition of end time.
  • 17. Definine the period window create table period_window ( one_row integer not null primary key default 0 check( one_row = 0 ) , base_year integer not null check ( base_year > 2000 and base_year <= date_part('year', now() ) ) , final_window interval year not null check ( final_window > interval '0 year' and final_window < interval '20 year' ) ); One row: Single definition.
  • 18. Definine the period window create table period_window ( one_row integer not null primary key default 0 check( one_row = 0 ) , base_year integer not null check ( base_year > 2000 and base_year <= date_part('year', now() ) ) , final_window interval year not null check ( final_window > interval '0 year' and final_window < interval '20 year' ) ); One row: First year: 2000 to current. Fiscal calendar uses date.
  • 19. Definine the period window create table period_window ( one_row integer not null primary key default 0 check( one_row = 0 ) , base_year integer not null check ( base_year > 2000 and base_year <= date_part('year', now() ) ) , final_window interval year not null check ( final_window > interval '0 year' and final_window < interval '20 year' ) ); One row: First year: Interval: Defined in years.
  • 20. Define time units Start with units of time. Convienent names: create type period_t as enum ( ‘annual’ , ‘quarterly’ , ‘monthly’ , ‘weekly’ , ‘daily’ ) ;
  • 21. Define time units Start with units of time. Map name to intervals: create type period_t as enum ( ‘annual’ , ‘quarterly’ , ‘monthly’ , ‘weekly’ , ‘daily’ ) ; create table period_interval ( period_type period_t primary key , period_size interval not null , unique ( period ) );
  • 22. Loading initial data Not much data: Integer + window. Few basic types. insert into period_window ( base_year , final_window ) values ( 2018, interval '10 years' ); insert into period_interval ( period_size , period_type ) values ( '1 year' , ‘annual’ ) , ( '3 months' , ‘quarterly’ ) , ( '1 month' , ‘monthly’ ) , ( ‘7 days’ , ‘weekly’ ) , ( '1 day' , ‘daily’ )
  • 23. Generate a calendar “generated...” Replace sequence type. Allows cloning a table. create table period ( period_sk integer generated by default as identity primary key , period_type period_t references period_interval , period daterange not null , unique ( period ) );
  • 24. Generate a calendar Re-use enum via type. create table period ( period_sk integer generated by default as identity primary key , period_type period_t references period_interval , period daterange not null , unique ( period ) );
  • 25. Generate a calendar Periods are ranges. Periods are unique. create table period ( period_sk integer generated by default as identity primary key , period_type period_t references period_interval , period daterange not null , unique ( period ) );
  • 26. First step: Start date. We have an integer: base_year. Intervals offset timestamps. select make_timestamp ( base_year, 1, 1, 0, 0, 0 ) "base_date" , final_window from period_window Generate a calendar
  • 27. “Common Table Expression” “base_date” is a pseudo-table. Defined for one query. with base_date as ( select make_timestamp ( base_year, 1, 1, 0, 0, 0 ) "base_date" , final_window from period_window ) Generate a calendar
  • 28. “cross join”: start timesteamp end interval name name width interval with base_date as ... ( select period_type , period , generate_series ( base_date , base_date + final_window , period_size ) "start" from base_date cross join period_interval ) z Generate a calendar
  • 29. generate_series: Multiple rows. From base to final. Units of period interval. Generate a calendar with base_date as ... from ( select period_type , period , generate_series ( base_date , base_date + final_window , period_size ) "start" from base_date cross join period_interval ) z
  • 30. ‘annual’ , interval ‘1 year’ , ‘01-Jan-2020’ ... ‘annual’ , interval ‘1 year’ , ‘01-Jan-2021’ with base_date as ... from ( select period_type , period , generate_series ( base_date , base_date + final_window , period_size ) "start" from base_date cross join period_interval ) z Generate a calendar
  • 31. ‘quarterly’ , interval ‘3 months’ , ‘01-Jan-2020’ ... ‘annual’ , interval ‘3 months’ , ‘01-Apr-2020’ with base_date as ... from ( select period_type , period , generate_series ( base_date , base_date + final_window , period_size ) "start" from base_date cross join period_interval ) z Generate a calendar
  • 32. Syntax: Sub-queries get names. with base_date as ... from ( select period_type , period , generate_series ( base_date , base_date + final_window , period_size ) "start" from base_date cross join period_interval ) z Generate a calendar
  • 33. Sequence of values: “daterange” composite: lower upper with base_date as ... select period_type , daterange ( start::date , ( start + period )::date , '[)' ) from ( select period_type , period_size , generate_series ... “start” ... )z Generate a calendar
  • 34. Result: Periods by width. period_sk | period_type | period ----------+-------------+------------------------- 1 | annual | [2018-01-01,2019-01-01) 2 | annual | [2019-01-01,2020-01-01) 3 | annual | [2020-01-01,2021-01-01) 4 | annual | [2021-01-01,2022-01-01) 5 | annual | [2022-01-01,2023-01-01) 6 | annual | [2023-01-01,2024-01-01) 7 | annual | [2024-01-01,2025-01-01) 8 | annual | [2025-01-01,2026-01-01) 9 | annual | [2026-01-01,2027-01-01) 10 | annual | [2027-01-01,2028-01-01) 11 | annual | [2028-01-01,2029-01-01) 12 | quarterly | [2018-01-01,2018-04-01) 13 | quarterly | [2018-04-01,2018-07-01) 14 | quarterly | [2018-07-01,2018-10-01) 15 | quarterly | [2018-10-01,2019-01-01) 16 | quarterly | [2019-01-01,2019-04-01) 17 | quarterly | [2019-04-01,2019-07-01) 18 | quarterly | [2019-07-01,2019-10-01) Generate a calendar
  • 35. Result: ‘[ … , … )’ Non-inclusive upper. Ranges don’t intersect. period_sk | period_type | period ----------+-------------+------------------------- 1 | annual | [2018-01-01,2019-01-01) 2 | annual | [2019-01-01,2020-01-01) 3 | annual | [2020-01-01,2021-01-01) 4 | annual | [2021-01-01,2022-01-01) 5 | annual | [2022-01-01,2023-01-01) 6 | annual | [2023-01-01,2024-01-01) 7 | annual | [2024-01-01,2025-01-01) 8 | annual | [2025-01-01,2026-01-01) 9 | annual | [2026-01-01,2027-01-01) 10 | annual | [2027-01-01,2028-01-01) 11 | annual | [2028-01-01,2029-01-01) 12 | quarterly | [2018-01-01,2018-04-01) 13 | quarterly | [2018-04-01,2018-07-01) 14 | quarterly | [2018-07-01,2018-10-01) 15 | quarterly | [2018-10-01,2019-01-01) 16 | quarterly | [2019-01-01,2019-04-01) 17 | quarterly | [2019-04-01,2019-07-01) 18 | quarterly | [2019-07-01,2019-10-01) Generate a calendar
  • 36. Result: ‘[ … , … )’ “2019-01-01” matches #2 & #16 on [2019-01-01, … ) period_sk | period_type | period ----------+-------------+------------------------- 1 | annual | [2018-01-01,2019-01-01) 2 | annual | [2019-01-01,2020-01-01) 3 | annual | [2020-01-01,2021-01-01) 4 | annual | [2021-01-01,2022-01-01) 5 | annual | [2022-01-01,2023-01-01) 6 | annual | [2023-01-01,2024-01-01) 7 | annual | [2024-01-01,2025-01-01) 8 | annual | [2025-01-01,2026-01-01) 9 | annual | [2026-01-01,2027-01-01) 10 | annual | [2027-01-01,2028-01-01) 11 | annual | [2028-01-01,2029-01-01) 12 | quarterly | [2018-01-01,2018-04-01) 13 | quarterly | [2018-04-01,2018-07-01) 14 | quarterly | [2018-07-01,2018-10-01) 15 | quarterly | [2018-10-01,2019-01-01) 16 | quarterly | [2019-01-01,2019-04-01) 17 | quarterly | [2019-04-01,2019-07-01) 18 | quarterly | [2019-07-01,2019-10-01) Generate a calendar
  • 37. Selecting from the calendar “Range contains” ‘@>’ select period_sk , period_type , period from period where period @> '15-Mar-2021'::date order by period_type , period ;
  • 38. Selecting from the calendar All period types. No match on: ‘15-Mar-2021’ ) select period_sk , period_type , period from period where period @> '15-Mar-2021'::date order by period_type , period ; period_sk | period_type | period -----------+-------------+------------------------- 4 | annual | [2021-01-01,2022-01-01) 24 | quarterly | [2021-01-01,2021-04-01) 91 | monthly | [2021-03-01,2021-04-01) 341 | weekly | [2021-03-15,2021-03-22) 1865 | single | [2021-03-15,2021-03-16) (5 rows)
  • 39. Selecting from the calendar Intersecting: ‘&&’ select period_sk, period_type, period from curr_report_period where period && daterange( '15-Mar-2021', '20-Aug-2022' ) and period_type = 'quarterly' order by period_type , period ; period_sk | period_type | period -----------+-------------+------------------------- 24 | quarterly | [2021-01-01,2021-04-01) 25 | quarterly | [2021-04-01,2021-07-01) 26 | quarterly | [2021-07-01,2021-10-01) 27 | quarterly | [2021-10-01,2022-01-01) 28 | quarterly | [2022-01-01,2022-04-01) 29 | quarterly | [2022-04-01,2022-07-01) 30 | quarterly | [2022-07-01,2022-10-01)
  • 40. Indexing the calendar GiST indexes describe arbitrary trees. Originally designed for full-text search. Search where ‘=’ doesn’t apply.
  • 41. Indexing the calendar GiST indexes Good for ranges and areas. create index on period using gist ( period ) include ( period_sk ) where active ; create index on period using gist ( period , period_type ) include ( period_sk ) where active ;
  • 42. Indexing the calendar GiST indexes Adds un-indexed data to the index leaf nodes. Query on period gets SK. create index on period using gist ( period ) include ( period_sk ) where active ; create index on period using gist ( period , period_type ) include ( period_sk ) where active ;
  • 43. Using the index Find partitions with quarterly reports including these dates. explain select period_sk from period where period && daterange( '15-Mar-2021', '20-Aug-2022' ) and period_type = 'quarterly' ;
  • 44. Using the index Find partitions with quarterly reports including these dates. Partition of data can use period_sk. explain select period_sk from period where period && daterange( '15-Mar-2021', '20-Aug-2022' ) and period_type = 'quarterly' ;
  • 45. Plan without index QUERY PLAN ------------------------------------------------------------------------------------------- Bitmap Heap Scan on period (cost=15.87..80.54 rows=1 width=4) Recheck Cond: active Filter: ((period && '[2021-03-15,2022-08-20)'::daterange) AND (period_type = 'quarterly'::period_t)) -> Bitmap Index Scan on period_period_sk_idx (cost=0.00..15.87 rows=1578 width=0) (4 rows)
  • 46. Plan with index QUERY PLAN ------------------------------------------------------------------------------------------- Bitmap Heap Scan on period (cost=15.87..80.54 rows=1 width=4) Recheck Cond: active Filter: ((period && '[2021-03-15,2022-08-20)'::daterange) AND (period_type = 'quarterly'::period_t)) -> Bitmap Index Scan on period_period_sk_idx (cost=0.00..15.87 rows=1578 width=0) (4 rows) QUERY PLAN ------------------------------------------------------------------------------------------- Index Only Scan using period_period_period_type_period_sk_idx on period (cost=0.15..2.37 rows=1 width=4) Index Cond: ((period && '[2021-03-15,2022-08-20)'::daterange) AND (period_type = 'quarterly'::period_t)) (2 rows)
  • 47. Plan with index QUERY PLAN ------------------------------------------------------------------------------------------- Bitmap Heap Scan on period (cost=15.87..80.54 rows=1 width=4) Recheck Cond: active Filter: ((period && '[2021-03-15,2022-08-20)'::daterange) AND (period_type = 'quarterly'::period_t)) -> Bitmap Index Scan on period_period_sk_idx (cost=0.00..15.87 rows=1578 width=0) (4 rows) QUERY PLAN ------------------------------------------------------------------------------------------- Index Only Scan using period_period_period_type_period_sk_idx on period (cost=0.15..2.37 rows=1 width=4) Index Cond: ((period && '[2021-03-15,2022-08-20)'::daterange) AND (period_type = 'quarterly'::period_t)) (2 rows)
  • 48. Plan with index QUERY PLAN ------------------------------------------------------------------------------------------- Bitmap Heap Scan on period (cost=15.87..80.54 rows=1 width=4) Recheck Cond: active Filter: ((period && '[2021-03-15,2022-08-20)'::daterange) AND (period_type = 'quarterly'::period_t)) -> Bitmap Index Scan on period_period_sk_idx (cost=0.00..15.87 rows=1578 width=0) (4 rows) QUERY PLAN ------------------------------------------------------------------------------------------- Index Only Scan using period_period_period_type_period_sk_idx on period (cost=0.15..2.37 rows=1 width=4) Index Cond: ((period && '[2021-03-15,2022-08-20)'::daterange) AND (period_type = 'quarterly'::period_t)) (2 rows)
  • 49. Plan with index QUERY PLAN ------------------------------------------------------------------------------------------- Index Only Scan using period_period_period_type_period_sk_idx on period (cost=0.15..2.37 rows=1 width=4) Index Cond: ((period && '[2021-03-15,2022-08-20)'::daterange) AND (period_type = 'quarterly'::period_t)) (2 rows) Works in PG: Data types & extensibility. Range type & GiST index.
  • 50. Summary Tables for window, intervals: Declarative definition of data. No iteration, no loops. Just sets & SQL.
  • 51. Summary Types for calendar: interval daterange & tsrange & tstzrange Series of values: generate_series( start, end, step );
  • 52. Summary SQL: Common Table Expression (“CTE”). Inlined in Postgres – fast. Name subqueries. Cross-join == Cartesian Product.
  • 53. Bedside reading https://www.postgresql.org/docs/13/... textsearch-indexes.html GIN & GiST rangetypes.html Data types datatype-datetime.html Date & Time spec’s functions-datetime.html Date op’s & func’s. functions-range.html Range op’s & func’s.