Thread: BUG #1871: operations with data types

BUG #1871: operations with data types

From
""
Date:
The following bug has been logged online:

Bug reference:      1871
Logged by:
Email address:      anris@polynet.lviv.ua
PostgreSQL version: 7-8
Operating system:   Linux
Description:        operations with data types
Details:

May be it is not bug, but anywhere:

here you are sample query
select '2005-08-31'::date + '1 month'::interval-'1 month'::interval

from the mathematical me the resulting value should be '2005-08-31', but
......

can you explain this

Re: BUG #1871: operations with data types

From
Michael Fuhr
Date:
On Fri, Sep 09, 2005 at 01:00:31PM +0100, anris@polynet.lviv.ua wrote:
> select '2005-08-31'::date + '1 month'::interval-'1 month'::interval
>
> from the mathematical me the resulting value should be '2005-08-31'

You didn't show any output; this is what I get:

test=> select '2005-08-31'::date + '1 month'::interval - '1 month'::interval;
      ?column?
---------------------
 2005-08-30 00:00:00
(1 row)

Apparently the two intervals don't cancel each other out (i.e.,
they're not optimized to zero), so effectively we get this:

test=> select '2005-08-31'::date + '1 month'::interval;
      ?column?
---------------------
 2005-09-30 00:00:00
(1 row)

test=> select '2005-09-30 00:00:00'::timestamp - '1 month'::interval;
      ?column?
---------------------
 2005-08-30 00:00:00
(1 row)

I'm wondering if the first expression ('2005-08-31' + '1 month')
should raise an exception.  Date & Darwen say it should in _A Guide
to the SQL Standard_, Fourth Edition, p. 276:

  ....thus, for example, the expression

      DATE '1998-08-31' + INTERVAL '1' MONTH

  ("August 31st, 1998 plus one month") apparently evaluates to

      DATE '1998-09-31'

  ("September 31st, 1998"), and thus fails (an "invalid date"
  exception is raised.  It does _not_ evaluate (as might perhaps
  have been expected) to

      DATE '1998-10-01'

  ("October 1st, 1998"), because such a result would require an
  adjustment to the DAY field after the MONTH addition had been
  performed.  In other words, if interval _i_ is added to date _d_,
  and _i_ is of type year-month, then the DAY value in the result
  is the same as the DAY value in _d_ (i.e., the DAY value does
  not change).

SQL:2003 (draft) Foundation, 6.30 <datetime value expression>,
General Rule 4 says

  If the <interval value expression> or <interval term> is a
  year-month interval, then the DAY field of the result is the
  same as the DAY field of the <datetime term> or <datetime value
  expression>.

and General Rule 6b says

  If, after the preceding step, any <primary datetime field> of the
  result is outside the permissible range of values for the field
  or the result is invalid based on the natural rules for dates and
  times, then an exception condition is raised: data exception --
  datetime field overflow.

Based on these rules, I'd expect '2005-08-31' + '1 month' to evaluate
to '2005-09-31' and thus raise an exception; instead, PostgreSQL
returns '2005-09-30'.

Any standards lawyers out there?  Have I misunderstood anything?

--
Michael Fuhr

Re: BUG #1871: operations with data types

From
Tom Lane
Date:
Michael Fuhr <mike@fuhr.org> writes:
> Apparently the two intervals don't cancel each other out (i.e.,
> they're not optimized to zero),

Well, no, because + and - associate left-to-right.

> so effectively we get this:

> test=> select '2005-08-31'::date + '1 month'::interval;
>       ?column?
> ---------------------
>  2005-09-30 00:00:00
> (1 row)

This seems to be the crux of the issue: is the above expression valid
and if so what should it yield?

I think you are right that the SQL spec wants it to raise an error,
but the spec's rules about datetime behavior are uselessly narrow
minded (last I checked, they still had not heard of daylight savings
time ... so they're obviously not trying very hard in this area).

The behavior that's in our code now is to round back to the last real
day of the month.  This might not be the best choice, but raising an
error doesn't seem better to me offhand.  Date and Darwen seem to
think rounding forward to the first day of the next month would be
more natural.  I'm not sure why; it certainly wouldn't fix the
complainant's issue, only surprise him in a different way.  Also,
even if you like round-forward for the above case, what about
subtraction --- what should '2005-10-31' - '1 month' give?  Rounding
down definitely feels more natural in that case, at least to me.

Any comments out there?

            regards, tom lane

Re: BUG #1871: operations with data types

From
Michael Fuhr
Date:
On Sun, Sep 11, 2005 at 12:43:58AM -0400, Tom Lane wrote:
> Michael Fuhr <mike@fuhr.org> writes:
> > Apparently the two intervals don't cancel each other out (i.e.,
> > they're not optimized to zero),
>
> Well, no, because + and - associate left-to-right.

Sure, I wasn't expecting any different -- I was just mentioning it
for the mathematically inclined who might think +1 and -1 should
cancel each other out.

> > test=> select '2005-08-31'::date + '1 month'::interval;
> >       ?column?
> > ---------------------
> >  2005-09-30 00:00:00
> > (1 row)
>
> This seems to be the crux of the issue: is the above expression valid
> and if so what should it yield?

Dunno.  It does seem inconsistent that these expressions give the
same answer:

select '2005-08-30'::date + '1 month'::interval,
       '2005-08-30'::date + '1 day'::interval + '1 month'::interval;
      ?column?       |      ?column?
---------------------+---------------------
 2005-09-30 00:00:00 | 2005-09-30 00:00:00
(1 row)

and these give different answers:

select '2005-08-30'::date + '1 day'::interval + '1 month'::interval,
       '2005-08-30'::date + '1 month'::interval + '1 day'::interval;
      ?column?       |      ?column?
---------------------+---------------------
 2005-09-30 00:00:00 | 2005-10-01 00:00:00
(1 row)

but I doubt I could make an argument for an alternative that was
any better than its counterargument.

Wait a minute, here's proof that it *must* be wrong ;-)

mysql> select date_add('2005-08-31', interval 1 month);
+------------------------------------------+
| date_add('2005-08-31', interval 1 month) |
+------------------------------------------+
| 2005-09-30                               |
+------------------------------------------+
1 row in set (0.11 sec)

What do other DBMSs do?

--
Michael Fuhr

Re: BUG #1871: operations with data types

From
Tom Lane
Date:
Michael Fuhr <mike@fuhr.org> writes:
> [ Mike's unhappy that ] these give different answers:

> select '2005-08-30'::date + '1 day'::interval + '1 month'::interval,
>        '2005-08-30'::date + '1 month'::interval + '1 day'::interval;

This is certainly an arena in which you can't expect the commutative
or associative laws to hold.  For instance:

    ('2005-02-28' + '1 day') + '1 month'

You can't possibly argue that that produces anything but
'2005-03-01' + '1 month' and thence '2005-04-01'.  On the other
hand, you can't possibly argue that

    ('2005-02-28' + '1 month') + '1 day'

produces anything but '2005-03-28' + '1 day' and thence '2005-03-29'.
So the original complaint can at best be described as lacking thought.

> Wait a minute, here's proof that it *must* be wrong ;-)
> mysql> select date_add('2005-08-31', interval 1 month);
> | 2005-09-30                               |

rotfl ;-)

> What do other DBMSs do?

This is a fair question.  Given that the SQL committee hasn't provided
any useful leadership, what are other groups doing?

            regards, tom lane

Re: BUG #1871: operations with data types

From
Michael Fuhr
Date:
On Sun, Sep 11, 2005 at 03:03:57AM -0400, Tom Lane wrote:
> Michael Fuhr <mike@fuhr.org> writes:
> > What do other DBMSs do?
>
> This is a fair question.  Given that the SQL committee hasn't provided
> any useful leadership, what are other groups doing?

I don't have access to an Oracle system, but the following page has
examples:

  http://philip.greenspun.com/sql/dates

It looks like Oracle has "old" and "new" (version 9 and later?)
ways of doing the query and they give different results:

  -- old
  select add_months(to_date('2003-07-31','YYYY-MM-DD'),-1) from dual;

    ADD_MONTHS
  ----------
  2003-06-30

  -- new
  select to_timestamp('2003-07-31','YYYY-MM-DD') - interval '1' month from dual;

  ERROR at line 1:
  ORA-01839: date not valid for month specified

I just noticed something in PostgreSQL that might be considered
surprising (although I do see "Add ISO INTERVAL handling" in the
TODO list):

  test=> select date '2005-08-01' + interval '1 month';
        ?column?
  ---------------------
   2005-09-01 00:00:00
  (1 row)

  test=> select date '2005-08-01' + interval '1' month;
        ?column?
  ---------------------
   2005-08-01 00:00:00
  (1 row)

  test=> select interval '1' month;
   interval
  ----------
   00:00:00
  (1 row)

What's the parser doing here?

--
Michael Fuhr

Re: BUG #1871: operations with data types

From
Tom Lane
Date:
Michael Fuhr <mike@fuhr.org> writes:
> I just noticed something in PostgreSQL that might be considered
> surprising (although I do see "Add ISO INTERVAL handling" in the
> TODO list):

>   test=> select interval '1' month;
>    interval
>   ----------
>    00:00:00
>   (1 row)

> What's the parser doing here?

Not getting it right ;-).  Trying this in historical versions is
amusing:

7.0:
regression=# select interval '1' month;
ERROR:  parser: parse error at or near "month"

7.1:
regression=# select interval '1' month;
ERROR:  Bad interval external representation '1'

7.2:
regression=# select interval '1' month;
 interval
----------
 00:00
(1 row)

7.3:
regression=# select interval '1' month;
 interval
----------
 00:00:01
(1 row)

7.4 and up:
regression=# select interval '1' month;
 interval
----------
 00:00:00
(1 row)

What is happening in the current versions is that coerce_type thinks
it can coerce the literal string to interval without supplying the
modifier, and then use interval_scale() to apply the typmod.  This
works OK for most of the data types, but not for interval it seems...

Basically the support for these weird syntaxes is something that Tom
Lockhart never finished, and no one has bothered to pick up the work
since he left the project.

            regards, tom lane

Re: BUG #1871: operations with data types

From
Bruce Momjian
Date:
Added to TODO:

    o Fix SELECT INTERVAL '1' MONTH;

---------------------------------------------------------------------------

Tom Lane wrote:
> Michael Fuhr <mike@fuhr.org> writes:
> > I just noticed something in PostgreSQL that might be considered
> > surprising (although I do see "Add ISO INTERVAL handling" in the
> > TODO list):
>
> >   test=> select interval '1' month;
> >    interval
> >   ----------
> >    00:00:00
> >   (1 row)
>
> > What's the parser doing here?
>
> Not getting it right ;-).  Trying this in historical versions is
> amusing:
>
> 7.0:
> regression=# select interval '1' month;
> ERROR:  parser: parse error at or near "month"
>
> 7.1:
> regression=# select interval '1' month;
> ERROR:  Bad interval external representation '1'
>
> 7.2:
> regression=# select interval '1' month;
>  interval
> ----------
>  00:00
> (1 row)
>
> 7.3:
> regression=# select interval '1' month;
>  interval
> ----------
>  00:00:01
> (1 row)
>
> 7.4 and up:
> regression=# select interval '1' month;
>  interval
> ----------
>  00:00:00
> (1 row)
>
> What is happening in the current versions is that coerce_type thinks
> it can coerce the literal string to interval without supplying the
> modifier, and then use interval_scale() to apply the typmod.  This
> works OK for most of the data types, but not for interval it seems...
>
> Basically the support for these weird syntaxes is something that Tom
> Lockhart never finished, and no one has bothered to pick up the work
> since he left the project.
>
>             regards, tom lane
>
> ---------------------------(end of broadcast)---------------------------
> TIP 3: Have you checked our extensive FAQ?
>
>                http://www.postgresql.org/docs/faq
>

--
  Bruce Momjian                        |  http://candle.pha.pa.us
  pgman@candle.pha.pa.us               |  (610) 359-1001
  +  If your life is a hard drive,     |  13 Roberts Road
  +  Christ can be your backup.        |  Newtown Square, Pennsylvania 19073