Project: YugabyteDB (App Definition and Development / Database)
File: declaring-intervals.md

title: Declaring intervals [YSQL] headerTitle: Declaring intervals linkTitle: Declaring intervals description: Explains that the nominally fourteen distinct interval declaration syntaxes have just six distinct semantics. [YSQL] menu: v2.18: identifier: declaring-intervals parent: type-interval weight: 30 type: docs {{< tip title="Download and [re]install the date-time utilities code." >}} The code on this page depends on the code presented in the section and on the , explained in the section . This is all included in the larger that includes all of the reusable code that the overall section describes and uses. Even if you have already installed the code kit, install it again now. Do this because a code example in the section redefines one of the interval utility functions. {{< /tip >}} There are over one hundred different spellings of the declaration of an interval. This might seem daunting. However, when you understand the degrees of freedom that the variations exploit, the mental model will seem straightforward. By analogy, when you consider the optional annotations of a bare numeric declaration to specify the scale and precision, you realize that multiplying the numbers of possible spellings for these two annotations gives a vast number of distinct possible spellings. But you need only to understand the concepts of scale and precision. The syntax variants for interval declarations express analogous concepts. This page explains it all. And it also points out that the variations in the syntax spellings can be grouped to reflect the fact that, within each group, the variants all express the same semantics. However, if you follow the approach described in the section , then you will not need to understand what this page explains. The explanations and the supporting code below address the semantics of the different spellings of interval declarationsin other words, how these different spellings differently constrain the interval values that can be represented. The code and the explanations presented below show that though there are several syntax spellings for each, there are in fact just six kinds of interval declaration. It's convenient to call these kinds year, month, day, hour, minute, and second. The constraints are applied whenever a new interval value is created, just before recording it in the . The effects of each of the six constraints are conveniently described by this PL/pgSQL code: ```output -- Here with [mm, dd, ss] computed with, so far, no constraints. case mode when 'bare' then null; when 'second' then null; when 'minute' then ss := trunc(ss/60.0)*60.0; when 'hour' then ss := trunc(ss/(60.060.0))60.0*60.0; when 'day' then ss := 0.0; when 'month' then ss := 0.0; dd := 0; when 'year' then ss := 0.0; dd := 0; mm := (mm/12)*12; -- integer division uses "trunc()" semantics end case; ``` {{< tip title="Yugabyte recommends using only the bare 'interval' declaration." >}} The term of art "bare interval" is to be taken literally: no trailing key words, and no (p) precision specifier. Yugabyte staff members have carefully considered the practical value that these various constraints bring and have concluded that none captures the intent of the SQL Standard or brings useful

The section shows how you can usefully constrain interval values, declared using the bare syntax, to allow the internal interval representation to record only: either the years and months fields (corresponding to the mm field in the internal representation). or the days field (corresponding to the dd field in the internal representation). or the hours, minutes, and seconds fields (corresponding to the ss field in the internal representation). If you follow this recommendation, then you don't need to study the remainder of this section. {{< /tip >}} The SQL Standard (cosmetically reworded) says this: There are two classes of intervals. One class, called year-month intervals, has an express or implied date-time precision that includes no fields other than YEAR and MONTH, though not both are required. The other class, called day-time intervals, has an express or implied interval value precision that includes no fields other than DAY, HOUR, MINUTE, and SECOND, though not all are required. Briefly, day-time intervals implement clock-time-semantics and year-month intervals implement calendar-time-semantics. See the section . The PostgreSQL design that YSQL inherits allows, for example, the declaration spelling day to second but, as the tests below show, the day to part of the phrase has no effect and so all six fields (years, months, days, hours, minutes , and seconds) are allowed. This is not usefulin particular because the sharp distinction that the SQL Standard intends between clock-time-semantics and calendar-time-semantics is blurred: hybrid semantics emerge. However, the regime that the defines is useful because the semantics of interval arithmetic for the values of each of the constrained domains is differentand honors the spirit of the SQL Standard. See the section . Moreover, the fact that there are three variants, in contrast to the SQL Standard's two variants, brings beneficial extra functionality. The specifies that this is the general syntax for an interval declaration: ```output interval [ fields ] [ (p) ] ``` And it states that the fields element restricts the set of stored fields by writing one of these listed phrases (or, as the syntax diagram says, by writing nothing): ```output year month year to month day hour day to hour minute day to minute hour to minute second day to second hour to second minute to second <"fields" specification omitted> ``` This list shows no fewer than fourteen distinct syntax spellings. The optional (p) element specifies the precision, in microseconds, with which the seconds field of the internal representation records the value. It is legal only after the "second" keyword or after the bare interval declaration. The allowed values are in 0..6. Omitting the (p) element has the same effect as specifying (6). So with the fourteen spellings listed above, together with the eight precision designations (0, 1, 2, 3, 4, 5, 6, or omitting this element) it multiplies up to 112 distinct ways to declare an interval as a table column, in PL/pgSQL code, and so on. Note: No pair chosen from the fourteen different interval declarations can be used to distinguish procedure or function overloads. The same applies to the variations in the specification of the (p) element. The account below shows that the different declaration syntaxes produce only six distinct semantic outcomes. The blank lines in the list above, and in the code, reflect this by grouping the syntaxes according to their semantic outcome. The anonymous block below demonstrates the syntax

It uses integral values for each of the inputs, except for the seconds, to make the results maximally understandable. The expected outcomes, used on the right hand sides of the putative equalities that the asserts test, are consistent with the explanations that the section gave of how a parameterization that specifies values using a [yy, mm, dd, hh, mi, ss] tuple is encoded as the internal representation that uses a [mm, dd, ss] tuple with integral mm and dd fields and a real number ss field. ```plpgsql do $body$ declare i_bare constant interval := make_interval(years=>9, months=>18, days=>700, hours=>97, mins=>86, secs=>75.123456); iyear constant interval year := ibare; imonth1 constant interval month := i_bare; imonth2 constant interval year to month := i_bare; iday constant interval day := ibare; ihour1 constant interval hour := i_bare; ihour2 constant interval day to hour := i_bare; iminute1 constant interval minute := i_bare; iminute2 constant interval day to minute := i_bare; iminute3 constant interval hour to minute := i_bare; isecond1 constant interval second := i_bare; isecond2 constant interval day to second := i_bare; isecond3 constant interval hour to second := i_bare; isecond4 constant interval minute to second := i_bare; r_year constant text := '10 years'; r_month constant text := '10 years 6 mons'; r_day constant text := '10 years 6 mons 700 days'; r_hour constant text := '10 years 6 mons 700 days 98:00:00'; r_minute constant text := '10 years 6 mons 700 days 98:27:00'; r_second constant text := '10 years 6 mons 700 days 98:27:15.123456'; begin -- "Year" group assert iyear::text = ryear, 'iyear = ryear failed'; -- "Month" group assert imonth1::text = rmonth, 'imonth1 = rmonth failed'; assert imonth2::text = rmonth, 'imonth2 = rmonth failed'; -- "Day" group assert iday::text = rday, 'iday = rday failed'; -- "Hour" group assert ihour1::text = rhour, 'ihour1 = rhour failed'; assert ihour2::text = rhour, 'ihour2 = rhour failed'; -- "Minute" group assert iminute1::text = rminute, 'iminute1 = rminute failed'; assert iminute2::text = rminute, 'iminute2 = rminute failed'; assert iminute3::text = rminute, 'iminute3 = rminute failed'; -- "Second" group assert ibare ::text = rsecond, 'ibare = rsecond failed'; assert isecond1::text = rsecond, 'isecond1 = rsecond failed'; assert isecond2::text = rsecond, 'isecond2 = rsecond failed'; assert isecond3::text = rsecond, 'isecond3 = rsecond failed'; assert isecond4::text = rsecond, 'isecond4 = rsecond failed'; end; $body$; ``` The block finishes silently, showing that each assertion holds. The declarations are grouped according to the six possible choices for the trailing keyword: year, month, day, hour, minute, or second. The assertions show that the syntax variants within each group have the same effectin other words that the optional leading phrases, year to, day to, hour to, and minute to have no semantic effect. Only the trailing keyword is semantically significant. Omitting this keyword (i.e. the bare declaration) has the same semantic effect has writing second, and so it belongs in that group. Each group has different resolution semantics: the choice of trailing keyword determines the least granular unit (_years_, _months_, _days_, _hours_, _minutes_, or _seconds_) that is respected. Try this: ```plpgsql do $body$ declare i_bare constant interval := '9 years 18 months 700 days 97 hours 86 minutes

seconds'; i6 constant interval(6) not null := i_bare; i5 constant interval(5) not null := i_bare; i4 constant interval(4) not null := i_bare; i3 constant interval(3) not null := i_bare; i2 constant interval(2) not null := i_bare; i1 constant interval(1) not null := i_bare; i0 constant interval(0) not null := i_bare; r6 constant text := '10 years 6 mons 700 days 98:27:15.123456'; r5 constant text := '10 years 6 mons 700 days 98:27:15.12346'; -- This shows the "round()" semantics. r4 constant text := '10 years 6 mons 700 days 98:27:15.1235'; r3 constant text := '10 years 6 mons 700 days 98:27:15.123'; r2 constant text := '10 years 6 mons 700 days 98:27:15.12'; r1 constant text := '10 years 6 mons 700 days 98:27:15.1'; r0 constant text := '10 years 6 mons 700 days 98:27:15'; begin -- Notice that i_bare, declared as bare "interval", -- and i6, declared as "interval(6)" are the same. assert ibare ::text = r6, 'ibare = r6 failed'; assert i6 ::text = r6, 'i6 = r6 failed'; assert i5 ::text = r5, 'i5 = r5 failed'; assert i4 ::text = r4, 'i4 = r4 failed'; assert i3 ::text = r3, 'i3 = r3 failed'; assert i2 ::text = r2, 'i2 = r2 failed'; assert i1 ::text = r1, 'i1 = r1 failed'; assert i0 ::text = r0, 'i0 = r0 failed'; end; $body$; ``` The block finishes silently showing that all the assertions hold. This confirms that the (p) element determines the precision, in microseconds, with which the seconds field of the internal representation records the value. Repeat the test after globally replacing interval with interval second in the declarations. The outcome is identical. Make sure that you've read the section before reading this section. The assumption that informs the following test is that any operation that produces an interval value first computes the _ above. The test provides new overloads for these two functions: that each adds a mode text formal parameter to express the class name of the interval declaration as one of 'bare', 'year', 'month', 'day', 'hour', 'minute', or 'second', thus: ```plpgsql drop function if exists intervalmmddss(intervalparameterization_t, text) cascade; create function intervalmmddss(p in intervalparameterization_t, mode in text) returns intervalmmddsst language plpgsql as $body$ declare -- Use the single-parameter overload mmddss constant intervalmmddsst not null := intervalmmdd_ss(p); mm int not null := mmddss.mm; dd int not null := mmddss.dd; ss double precision not null := mmddss.ss; begin case mode when 'bare' then null; when 'second' then null; when 'minute' then ss := trunc(ss/60.0)*60.0; when 'hour' then ss := trunc(ss/(60.060.0))60.0*60.0; when 'day' then ss := 0.0; when 'month' then ss := 0.0; dd := 0; when 'year' then ss :=

dd := 0; mm := (mm/12)*12; -- integer division uses "trunc()" semantics end case; return (mm, dd, ss)::intervalmmddsst; end; $body$; ``` and: ```plpgsql drop function if exists intervalvalue(intervalparameterization_t, text) cascade; create function intervalvalue(p in intervalparameterization_t, mode in text) returns interval language plpgsql as $body$ declare -- Use the single-parameter overload ibare constant interval not null := intervalvalue(p); iyear constant interval year not null := ibare; imonth constant interval month not null := ibare; iday constant interval day not null := ibare; ihour constant interval hour not null := ibare; iminute constant interval minute not null := ibare; isecond constant interval second not null := ibare; begin return case mode when 'bare' then i_bare when 'year' then i_year when 'month' then i_month when 'day' then i_day when 'hour' then i_hour when 'minute' then i_minute when 'second' then i_second end; end; $body$; ``` Test the modeled implementation of the constraints in the same way that the unconstrained model was tested in the section . The procedure assertmodelokworker() has the identical implementation to procedure _ in that section. ```plpgsql drop procedure if exists assertmodelokworker(intervalparameterization_t, text) cascade; create procedure assertmodelokworker(p in intervalparameterization_t, mode in text) language plpgsql as $body$ declare imodeled constant intervalmmddsst not null := intervalmmddss(p); ifrommodeled constant interval not null := intervalvalue(imodeled); iactual constant interval not null := intervalvalue(p); mmddssactual constant intervalmmddsst not null := intervalmmddss(i_actual); pmodeled constant intervalparameterizationt not null := parameterization(imodeled); pactual constant intervalparameterizationt not null := parameterization(iactual); begin -- Belt-and-braces check for mutual consistency among the "interval" utilities. assert (imodeled ~= mmddssactual), 'assert #1 failed'; assert (pmodeled ~= pactual ), 'assert #2 failed'; assert (ifrommodeled == i_actual ), 'assert #3 failed'; end; $body$; drop procedure if exists assertmodelok(intervalparameterizationt) cascade; create procedure assertmodelok(p in intervalparameterizationt) language plpgsql as $body$ begin call assertmodelok_worker(p, 'bare'); call assertmodelok_worker(p, 'year'); call assertmodelok_worker(p, 'month'); call assertmodelok_worker(p, 'day'); call assertmodelok_worker(p, 'hour'); call assertmodelok_worker(p, 'minute'); call assertmodelok_worker(p, 'second'); end; $body$; ``` Execute the tests using the same that was defined and used to test the unconstrained model. (This is also included in the code kit.) ```plpgsql call testinternalintervalrepresentationmodel(); ``` Each of the tests finishes silently showing that the rules explained above have so far been shown always to agree with the rules of the actual implementation. You are challenged to disprove the hypothesis by inventing more tests. If any of your tests causes assertmodelok() to finish with an assert failure, then against this documentation section. If you do find such a counter-example, you can compare the results from using the actual implementation and the modeled implementation with this re-write of the logic of the assertmodelok() procedure. Instead of using assert, it shows you the outputs for visual comparison. It has an almost identical implementation to the function in the section . The difference is that the following implentation adds the formal input parameter mode and carries this through to the new overload of intervalmmddss()_. ```plpgsql drop function if exists modelvsactualcomparison(intervalparameterization_t, text) cascade; create function modelvsactualcomparison(p in intervalparameterization_t, mode in text) returns table(x text) language plpgsql as $body$ declare imodeled constant intervalmmddsst not null := intervalmmddss(p, mode); iactual constant interval not null := intervalvalue(p, mode); pmodeled constant intervalparameterizationt not null := parameterization(imodeled); pactual constant intervalparameterizationt not null := parameterization(iactual); ssmodeledtext constant text not null := ltrim(tochar(pmodeled.ss, '9999999999990.999999')); ssactualtext constant text not null := ltrim(tochar(pactual.ss, '9999999999990.999999')); begin x := 'modeled: '|| lpad(p_modeled.yy ::text, 4)||' yy, '|| lpad(p_modeled.mm ::text, 4)||' mm, '|| lpad(p_modeled.dd ::text, 4)||' dd, '|| lpad(p_modeled.hh ::text, 4)||' hh, '|| lpad(p_modeled.mi ::text, 4)||' mi, '|| lpad(ssmodeledtext, 10)||' ss'; return next; x := 'actual: '|| lpad(p_actual.yy ::text, 4)||' yy, '|| lpad(p_actual.mm ::text, 4)||' mm, '|| lpad(p_actual.dd ::text, 4)||' dd,