Обсуждение: sign function with INTERVAL?

Поиск
Список
Период
Сортировка

sign function with INTERVAL?

От
Daniel Lenski
Дата:
Hi all,
Is there a good reason why the SIGN() function does not work with the
INTERVAL type? (It is only defined for numeric types.)
(http://www.postgresql.org/docs/9.5/static/functions-math.html)

select sign(-3); -- okay
select sign(interval '4 years'); -- ERROR:  function sign(interval)
does not exist

I'm trying to find a straightforward and reliable way to differentiate
positive, negative, and zero time intervals while handling NULL in the
same way as the SIGN() function.

What I have come up with is this rather inelegant and error-prone case
statement:

case when x is null then null x>interval '0' then +1 when x<interval
'0' then -1 when x=interval '0' then 0 end

Here's an example:

with t as (select interval '4 years 2 months' as x, interval '-1
minute 2 seconds' as y, interval '0' as z, null::interval as w)
select case when x is null then null when x>interval '0' then +1 when
x<interval '0' then -1 when x=interval '0' then 0 end,
       case when y is null then null when y>interval '0' then +1 when
y<interval '0' then -1 when y=interval '0' then 0 end,
       case when z is null then null when z>interval '0' then +1 when
z<interval '0' then -1 when z=interval '0' then 0 end,
       case when w is null then null when w>interval '0' then +1 when
w<interval '0' then -1 when w=interval '0' then 0 end
from t

Is there a more obvious way to do sign(interval)? Would it be
technically difficult to make it "just work"?

Thanks,
Dan


Re: sign function with INTERVAL?

От
Gianni Ceccarelli
Дата:
On 2016-04-13 Daniel Lenski <dlenski@gmail.com> wrote:
> Hi all,
> I'm trying to find a straightforward and reliable way to differentiate
> positive, negative, and zero time intervals while handling NULL in the
> same way as the SIGN() function.

I'm not sure that "positive time interval" is a thing. Witness:

 dakkar=> select interval '1 month - 30 days' > interval '0';
 ┌──────────┐
 │ ?column? │
 ├──────────┤
 │ f        │
 └──────────┘
 (1 row)

not positive? maybe it's negative?

 dakkar=> select interval '1 month - 30 days' < interval '0';
 ┌──────────┐
 │ ?column? │
 ├──────────┤
 │ f        │
 └──────────┘
 (1 row)

no, not negative either. Why? Well…

 dakkar=> select date '2016-03-01' + interval '1 month - 30 days';
 ┌─────────────────────┐
 │      ?column?       │
 ├─────────────────────┤
 │ 2016-03-02 00:00:00 │
 └─────────────────────┘
 (1 row)

when used this way, it looks positive, but

 dakkar=> select date '2016-02-01' + interval '1 month - 30 days';
 ┌─────────────────────┐
 │      ?column?       │
 ├─────────────────────┤
 │ 2016-01-31 00:00:00 │
 └─────────────────────┘
 (1 row)

when used this way, it looks negative.

So I suspect the reason SIGN() is not defined for intervals is that
it cannot be made to work in the general case.

--
    Dakkar - <Mobilis in mobile>
    GPG public key fingerprint = A071 E618 DD2C 5901 9574
                                 6FE2 40EA 9883 7519 3F88
                        key id = 0x75193F88

Thrashing is just virtual crashing.

Вложения

Re: [HACKERS] sign function with INTERVAL?

От
Jim Nasby
Дата:
On 4/13/16 1:36 PM, Daniel Lenski wrote:
> Hi all,
> Is there a good reason why the SIGN() function does not work with the
> INTERVAL type? (It is only defined for numeric types.)
> (http://www.postgresql.org/docs/9.5/static/functions-math.html)

The only thing that comes to mind is you can get some strange
circumstances with intervals, like '-1 mons +4 days'. I don't think that
precludes sign() though.

> What I have come up with is this rather inelegant and error-prone case
> statement:

How is it error prone?

> case when x is null then null x>interval '0' then +1 when x<interval
> '0' then -1 when x=interval '0' then 0 end

You don't need to handle null explicitly. You could do

SELECT CASE WHEN x > interval '0' THEN 1 WHEN x < interval '0' THEN -1
WHEN x = interval '0' THEN 0 END

Or, you could do...

CREATE FUNCTION sign(interval) RETURNS int LANGUAGE sql STRICT IMMUTABLE
AS $$
SELECT CASE WHEN $1 > interval '0' THEN 1 WHEN x < interval '0' THEN -1
ELSE 0 END
$$;

That works because a STRICT function won't even be called if any of it's
inputs are NULL.


> Is there a more obvious way to do sign(interval)? Would it be
> technically difficult to make it "just work"?

Actually, after looking at the code for interval_lt, all that needs to
happen to add this support is to expose interval_cmp_internal() as a
strict function. It already does exactly what you want.
--
Jim Nasby, Data Architect, Blue Treble Consulting, Austin TX
Experts in Analytics, Data Architecture and PostgreSQL
Data in Trouble? Get it in Treble! http://BlueTreble.com


Re: [HACKERS] sign function with INTERVAL?

От
Tom Lane
Дата:
Jim Nasby <Jim.Nasby@BlueTreble.com> writes:
> Actually, after looking at the code for interval_lt, all that needs to
> happen to add this support is to expose interval_cmp_internal() as a
> strict function. It already does exactly what you want.

interval_cmp() is already SQL-accessible.

            regards, tom lane


Re: [HACKERS] sign function with INTERVAL?

От
Daniel Lenski
Дата:
On Wed, Apr 13, 2016 at 12:35 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
> Jim Nasby <Jim.Nasby@BlueTreble.com> writes:
>> Actually, after looking at the code for interval_lt, all that needs to
>> happen to add this support is to expose interval_cmp_internal() as a
>> strict function. It already does exactly what you want.
>
> interval_cmp() is already SQL-accessible.

Thanks! The interval_cmp() function does not appear in the 9.5 docs.
http://www.postgresql.org/docs/9.5/static/functions-datetime.html

On Wed, Apr 13, 2016 at 11:54 AM, Gianni Ceccarelli
<dakkar@thenautilus.net> wrote:
> I'm not sure that "positive time interval" is a thing. Witness:
>
> (snip)
>
>  dakkar=> select date '2016-03-01' + interval '1 month - 30 days';
>  ┌─────────────────────┐
>  │      ?column?       │
>  ├─────────────────────┤
>  │ 2016-03-02 00:00:00 │
>  └─────────────────────┘
>  (1 row)
>
> when used this way, it looks positive, but
>
>  dakkar=> select date '2016-02-01' + interval '1 month - 30 days';
>  ┌─────────────────────┐
>  │      ?column?       │
>  ├─────────────────────┤
>  │ 2016-01-31 00:00:00 │
>  └─────────────────────┘
>  (1 row)
>
> when used this way, it looks negative.
>
> So I suspect the reason SIGN() is not defined for intervals is that
> it cannot be made to work in the general case.

I hadn't considered this case of an interval like '1 month - 30 days',
which could be either positive or negative depending on the starting
date to which it is added.

interval_cmp's handling of this case seems surprising to me. If I've
got this right, it assumes that (interval '1 month' == interval '30
days') exactly:
http://doxygen.postgresql.org/backend_2utils_2adt_2timestamp_8c_source.html#l02515

Do I have that right? I'm having trouble envisioning an application
that would ever generate intervals that contain months and days
without opposite signs, but it's useful to know that such a corner
case could exist.

Given this behavior, the only 100% reliable way to check whether an
interval is forward, backwards, or zero would be to first add, and
then subtract, the starting point:

postgres=# select interval_cmp( (date '2016-02-01' + interval '1 month
- 30 days') - date '2016-02-01', interval '0' );
 interval_cmp
--------------
           -1
(1 row)

postgres=# select interval_cmp( (date '2016-04-01' + interval '1 month
- 30 days') - date '2016-04-01', interval '0' );
 interval_cmp
--------------
            0
(1 row)

Thanks,
Dan


Re: [HACKERS] sign function with INTERVAL?

От
"David G. Johnston"
Дата:
On Wed, Apr 13, 2016 at 3:48 PM, Daniel Lenski <dlenski@gmail.com> wrote:
On Wed, Apr 13, 2016 at 12:35 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
> Jim Nasby <Jim.Nasby@BlueTreble.com> writes:
>> Actually, after looking at the code for interval_lt, all that needs to
>> happen to add this support is to expose interval_cmp_internal() as a
>> strict function. It already does exactly what you want.
>
> interval_cmp() is already SQL-accessible.

Thanks! The interval_cmp() function does not appear in the 9.5 docs.
http://www.postgresql.org/docs/9.5/static/functions-datetime.html

On Wed, Apr 13, 2016 at 11:54 AM, Gianni Ceccarelli
<dakkar@thenautilus.net> wrote:
> I'm not sure that "positive time interval" is a thing. Witness:
>
> (snip)
>
>  dakkar=> select date '2016-03-01' + interval '1 month - 30 days';
>  ┌─────────────────────┐
>  │      ?column?       │
>  ├─────────────────────┤
>  │ 2016-03-02 00:00:00 │
>  └─────────────────────┘
>  (1 row)
>
> when used this way, it looks positive, but
>
>  dakkar=> select date '2016-02-01' + interval '1 month - 30 days';
>  ┌─────────────────────┐
>  │      ?column?       │
>  ├─────────────────────┤
>  │ 2016-01-31 00:00:00 │
>  └─────────────────────┘
>  (1 row)
>
> when used this way, it looks negative.
>
> So I suspect the reason SIGN() is not defined for intervals is that
> it cannot be made to work in the general case.

I hadn't considered this case of an interval like '1 month - 30 days',
which could be either positive or negative depending on the starting
date to which it is added.

interval_cmp's handling of this case seems surprising to me. If I've
got this right, it assumes that (interval '1 month' == interval '30
days') exactly:
http://doxygen.postgresql.org/backend_2utils_2adt_2timestamp_8c_source.html#l02515


​I was trying to figure out how the months/year fit in here - or whether years are derived from days (i.e., 365 instead of 360)...​

The anchored section of code only shows stand-alone conversion factors for days->hours and months-days
 
Do I have that right? I'm having trouble envisioning an application
that would ever generate intervals that contain months and days
without opposite signs, but it's useful to know that such a corner
case could exist.

Yes.
&
​I want the date that is 1 month and 14 days (2 weeks) from now...

For added fun the SQL standard apparently disallows mixed signs (according to our docs)

>​According to the SQL standard all fields of an interval value must have the same sign, 
>so a leading negative sign applies to all fields; for example the negative sign in 
>the interval literal '-1 2:03:04' applies to both the days and hour/minute/second parts.


Given this behavior, the only 100% reliable way to check whether an
interval is forward, backwards, or zero would be to first add, and
then subtract, the starting point:

postgres=# select interval_cmp( (date '2016-02-01' + interval '1 month
- 30 days') - date '2016-02-01', interval '0' );
 interval_cmp
--------------
           -1
(1 row)

postgres=# select interval_cmp( (date '2016-04-01' + interval '1 month
- 30 days') - date '2016-04-01', interval '0' );
 interval_cmp
--------------
            0
(1 row)


​Yes, the dual nature of an interval, i.e., an assumed conversion factor (1m = 30d) if dealing with it independently but ​a conversion factor based on reality (feb has 28 days, typically) makes working with it complicated.  There is not way you could write an operator that successfully handles the later situation since you cannot write a custom ternary operator that would take two intervals and a date.  That said we already have rules that allow us to canonical-ize an interval so any form of two-interval comparison can be performed: but those results become invalidated if one has to apply the interval to a date.

In short, adding this feature would make it much easier for the inexperienced to use intervals unsafely without realizing it.  It is possible to write custom functions that do exactly what is needed based upon the usage of intervals within the system under observation.  Doability combined with ignorance hazard means that the status-quo seems acceptable.

​I guess it would be nice to expose our conversion factors in such a way that a user can readily get the number of seconds represented by a given interval when considered without respect to a starting date.  But since most uses of interval are related to dates it seems likely that comparing intervals by comparing the dates resulting from their application is the most reliable.

N.B. consider too that the signs are not the whole of it.  Intervals allow for the word "ago" to be specified.

David J.