The code below shows the implementation of custom composite data type in Postgres that represents quarters of a year. This data type is named "quarter". The implementation supports addition of a number of quarters to a "quarter" date to produce a new "quarter" date, and supports calculation of the difference between two "quarter" dates. Comparison operators are also defined for use in expressions using "quarter" dates and to allow indexing of data tables by "quarter" values.
The Postgres code to define this data type, and arithmetic and comparison operators, is shown below. Details on the creation of a custom data type and operators can be found in the Postgres documentation.
The definition of the "quarter" data type is simply:
create type quarter as ( year integer, quarter integer );
To support addition and subtraction of an integer number of quarters to (or from) a "quarter" data type, a function must be defined to carry out this calculation. The arguments to the function are a "quarter" data type and the number of quarters; the second argument may be either positive or negative.
create or replace function qtr_add( qtr quarter, addqtr integer) returns quarter as $BODY$ DECLARE qtr2 quarter; BEGIN -- Adjust the year if addqtr >= 0 then qtr2.year := qtr.year + div((qtr.quarter + addqtr-1), 4); else qtr2.year := qtr.year - div(((4 - qtr.quarter) - addqtr), 4); end if; -- Adjust the quarter qtr2.quarter := mod(qtr.quarter + addqtr - 1, 4) +1; if qtr2.quarter < 1 then qtr2.quarter := qtr2.quarter + 4; end if; -- return qtr2; END; $BODY$ language plpgsql immutable leakproof strict;
Subtracting two "quarter" dates can be carried out in a simple expression, but a function is defined to carry out this calculation because it is used for both the mathematical and comparison operators.
create or replace function qtr_diff( qtr1 quarter, qtr2 quarter) returns integer as $BODY$ BEGIN return 4 * (qtr1.year - qtr2.year) + (qtr1.quarter - qtr2.quarter); END; $BODY$ language plpgsql immutable leakproof strict;
To support comparison operators (<, <=, =, <>, >=, and >), a function is defined to compare two "quarter" dates. This function returns -1, 0, or 1 depending on whether its first argument is less than, equal to, or greater than its second argument.
create or replace function quarter_comp( qtr1 quarter, qtr2 quarter) returns integer as $BODY$ DECLARE diff integer; BEGIN diff := qtr_diff(qtr1, qtr2); if diff = 0 then return 0; elsif diff < 1 then return -1; end if; return 1; END; $BODY$ language plpgsql immutable leakproof strict;
Each comparison operation is carried out by an individual function that uses the 'quarter_comp()' function.
-- -------------------------------------------------------------------------- -- qtr_lt() -- -------------------------------------------------------------------------- create or replace function qtr_lt( qtr1 quarter, qtr2 quarter) returns boolean as $BODY$ BEGIN return quarter_comp(qtr1, qtr2) < 0; END; $BODY$ language plpgsql immutable leakproof strict; -- -------------------------------------------------------------------------- -- qtr_lte() -- -------------------------------------------------------------------------- create or replace function qtr_lte( qtr1 quarter, qtr2 quarter) returns boolean as $BODY$ BEGIN return quarter_comp(qtr1, qtr2) <= 0; END; $BODY$ language plpgsql immutable leakproof strict; -- -------------------------------------------------------------------------- -- qtr_eq() -- -------------------------------------------------------------------------- create or replace function qtr_eq( qtr1 quarter, qtr2 quarter) returns boolean as $BODY$ BEGIN return quarter_comp(qtr1, qtr2) = 0; END; $BODY$ language plpgsql immutable leakproof strict; -- -------------------------------------------------------------------------- -- qtr_ne() -- -------------------------------------------------------------------------- create or replace function qtr_ne( qtr1 quarter, qtr2 quarter) returns boolean as $BODY$ BEGIN return quarter_comp(qtr1, qtr2) <> 0; END; $BODY$ language plpgsql immutable leakproof strict; -- -------------------------------------------------------------------------- -- qtr_gte() -- -------------------------------------------------------------------------- create or replace function qtr_gte( qtr1 quarter, qtr2 quarter) returns boolean as $BODY$ BEGIN return quarter_comp(qtr1, qtr2) >= 0; END; $BODY$ language plpgsql immutable leakproof strict; -- -------------------------------------------------------------------------- -- qtr_gt() -- -------------------------------------------------------------------------- create or replace function qtr_gt( qtr1 quarter, qtr2 quarter) returns boolean as $BODY$ BEGIN return quarter_comp(qtr1, qtr2) > 0; END; $BODY$ language plpgsql immutable leakproof strict;
After the functions to carry out the arithmetic and comparison operations have been defined, the operators themselves can be defined.
-- -------------------------------------------------------------------------- -- + -- -------------------------------------------------------------------------- create operator +( procedure = qtr_add, leftarg = quarter, rightarg = integer, commutator = +); -- -------------------------------------------------------------------------- -- - -- -------------------------------------------------------------------------- create operator -( procedure = qtr_diff, leftarg = quarter, rightarg = quarter); -- -------------------------------------------------------------------------- -- < -- -------------------------------------------------------------------------- create operator <( procedure = qtr_lt, leftarg = quarter, rightarg = quarter, commutator = >, negator = >=); -- -------------------------------------------------------------------------- -- <= -- -------------------------------------------------------------------------- create operator <=( procedure = qtr_lte, leftarg = quarter, rightarg = quarter, commutator = >=, negator = >); -- -------------------------------------------------------------------------- -- = -- -------------------------------------------------------------------------- create operator =( procedure = qtr_eq, leftarg = quarter, rightarg = quarter, commutator = =, negator = <>); -- -------------------------------------------------------------------------- -- <> -- -------------------------------------------------------------------------- create operator <>( procedure = qtr_ne, leftarg = quarter, rightarg = quarter, commutator = <>, negator = =); -- -------------------------------------------------------------------------- -- > -- -------------------------------------------------------------------------- create operator >( procedure = qtr_gt, leftarg = quarter, rightarg = quarter, commutator = <, negator = <=); -- -------------------------------------------------------------------------- -- >= -- -------------------------------------------------------------------------- create operator >=( procedure = qtr_gte, leftarg = quarter, rightarg = quarter, commutator = >=, negator = <);
To allow "quarter" dates to be used in indexes, an operator class must be defined.
create operator class quarter_ops default for type quarter using btree as operator 1 <, operator 2 <=, operator 3 =, operator 4 >=, operator 5 >, function 1 quarter_comp(quarter, quarter);
The same approach could be used to define a custom "month" data type. The code for a "month" data type would be almost identical, with changes needed only to the data type name used and to the functions 'qtr_add()' and 'qtr_diff()'.