简体   繁体   中英

Oracle PL/SQL: how the SYSDATE is different from 'DD-MMM-YY'?

I'm wondering how come SYSDATE is different from, let's say 28-APR-18 (assuming that SYSDATE is April, 28 of 2018).

I was debugging a little script I made, and got plenty of errors. After a while I managed to narrow it down to how the tables were filled (they were filled using the literal ' DD-MMM-YYYY ' method and I was comparing against SYSDATE ).

To understand, I wrote the following to see how each compares:

declare var1 DATE; var2 DATE;
BEGIN
   var1 := SYSDATE;
   var2 := '27-APR-18';

   if var1 = var2 then
   DBMS_OUTPUT.PUT_LINE('oh yeah');
   else DBMS_OUTPUT.PUT_LINE('WTF?'); 
       DBMS_OUTPUT.PUT_LINE(SYSDATE);DBMS_OUTPUT.PUT_LINE('27-APR-18');
   end if;
END;

If the above is ran, I get the following (which is getting me puzzled):

WTF?
27-APR-18
27-APR-18


PL/SQL procedure successfully completed.

Since they are both declared as DATE type, shouldn't they both be equal?

Thanks for your time and help!

In Oracle, a DATE value - despite the name - contains a time part as well. SYSDATE contains the current date and the current time (up to seconds).

The Oracle tools by default (stupidly) hide the time part of a DATE value. If you run:

select to_char(sysdate, 'yyyy-mm-dd hh24:mi:ss') as sysdate
from dual;

you can see that.

So SYSDATE might be 2018-04-27 09:15:42 whereas the string (!) constant '27-APR-18' is silently converted to a DATE value at midnight: 2017-04-28 00:00:00

More details in the chapter Basic Elements of Oracle SQL in the manual


If you don't care about the time part, you can use trunc() to set the time to midnight, trunc(sysdate) yields 2018-04-27 00:00:00 (if today is 2018-04-27 ). Note that trunc() does not "remove" the time, it only sets it to 00:00:00


Unrelated, but:

You should never rely on implicit casting between strings and other non-character types which var2 := '27-APR-18' does - it would eg fail on my computer as my default NLS date format is different.

If you need a DATE value, then specify a proper date literal:

var2 := DATE '2018-04-27'; 

or

var2 := to_date('27-APR-18', 'dd-mon-rr');

or

var2 := to_date('27-APR-18 00:00:00', 'dd-mon-rr hh24:mi:ss');

Apart from what @horse has explained, you may use TRUNC on sysdate for var1 and date literal on var2 in YYYY-MM-DD format, it would match.

declare 
var1 DATE; 
var2 DATE;
BEGIN
   var1 := TRUNC(SYSDATE);
   var2 := DATE '2018-04-27';

   if var1 = var2 then
        DBMS_OUTPUT.PUT_LINE('oh yeah');
   else 
     DBMS_OUTPUT.PUT_LINE('WTF?'); 
     DBMS_OUTPUT.PUT_LINE(SYSDATE ||','||'27-APR-18');
   end if;
END;

oh yeah

Looking at your code (with line numbers)

 1 DECLARE
 2   var1 DATE;
 3   var2 DATE;
 4 BEGIN
 5   var1 := SYSDATE;
 6   var2 := '27-APR-18';
 7 
 8   if var1 = var2 then
 9     DBMS_OUTPUT.PUT_LINE('oh yeah');
10   END
11     DBMS_OUTPUT.PUT_LINE('WTF?'); 
12     DBMS_OUTPUT.PUT_LINE(SYSDATE);
13     DBMS_OUTPUT.PUT_LINE('27-APR-18');
14   END IF;
15 END;

In line 5 : '27-APR-18' is a text literal (not a DATE data type) and when you try to assign it to a DATE varaible Oracle will try to be helpful and implicitly cast it to a DATE using the TO_DATE( date_string, format_model ) function. Since this is an implicit cast then Oracle will use its default format model which is the user's NLS_DATE_FORMAT session parameter.

You can see the format of the NLS_DATE_FORMAT session parameter using the query:

SELECT VALUE
FROM   NLS_SESSION_PARAMETERS
WHERE  PARAMETER = 'NLS_DATE_FORMAT';

And line 5 is the equivalent of:

var2 := TO_DATE(
          '27-APR-18',
          ( SELECT VALUE
            FROM   NLS_SESSION_PARAMETERS
            WHERE  PARAMETER = 'NLS_DATE_FORMAT' )
        );

If the default format model matches the string then this implicit cast will work - otherwise your code will raise an exception.

The other possible problem is where the NLS_DATE_FORMAT is DD-MON-YYYY (or an equivalent) and then 27-APR-18 will be cast to the date 0018-04-27 00:00:00 and have a (probably) unexpected and wrong century!

(Note: NLS_DATE_FORMAT is a session parameter so each user can set their own value so you should NEVER rely on implicit casts otherwise you will find that the same code which is working for yourself fails for other users or can even fail for the same user in different sessions - without your code ever changing.)

What you should do in line 5 is either use a DATE literal or explicitly cast the text literal using the TO_DATE function with a format model:

var2 := DATE '2018-04-27';
var2 := TO_DATE( '27-APR-18', 'DD-MON-RR' );

In line 8 : You are comparing two date; however, the DATE data type always has a time component and unless you run the code at 2018-04-27T00:00:00 then SYSDATE will have a "non-zero" time component whereas var2 did not have an explicitly specified time component so the implicit cast will have given it a time component of 00:00:00 (midnight). This means that, except for at exactly midnight, the two will never be equal and the comparison will return FALSE .

If you want to zero the time components of a DATE when you are comparing two dates then use the TRUNC() function or specify a range, like these:

IF TRUNC( var1 ) = var2 THEN
IF var2 <= var1 AND var1 < var2 + INTERVAL '1' DAY THEN

In line 12 : This is the reverse of line 5 - DBMS_OUTPUT.PUT_LINE( string ) takes a string argument - not a DATE - so Oracle will implicitly try cast the DATE to a string (using the NLS_DATE_FORMAT again). So line 12 is effectively;

DBMS_OUTPUT.PUT_LINE(
  TO_CHAR(
    SYSDATE,
    ( SELECT VALUE
      FROM   NLS_SESSION_PARAMETERS
      WHERE  PARAMETER = 'NLS_DATE_FORMAT' )
  )
)

Since your NLS_DATE_FORMAT is DD-MON-RR this is not displaying the time components and is not showing you why the variables are different.

If you do:

ALTER SESSION SET NLS_DATE_FORMAT = 'YYYY-MM-DD HH24:MI:SS';

DECLARE
  var1 DATE;
  var2 DATE;
BEGIN
  var1 := SYSDATE;
  var2 := DATE '2018-04-27';
  -- or var2 := TO_DATE( '2018-04-27 00:00:00', 'YYYY-MM-DD HH24:MI:SS' );
  -- or var2 := TIMESTAMP '2018-04-27 00:00:00';

  if var1 = var2 then
    DBMS_OUTPUT.PUT_LINE('oh yeah');
  END
    DBMS_OUTPUT.PUT_LINE('WTF?'); 
    DBMS_OUTPUT.PUT_LINE( var1 );
    -- or DBMS_OUTPUT.PUT_LINE( TO_CHAR( var1, 'YYYY-MM-DD HH24:MI:SS' ) );
    DBMS_OUTPUT.PUT_LINE( var2 );
    -- or DBMS_OUTPUT.PUT_LINE( TO_CHAR( var2, 'YYYY-MM-DD HH24:MI:SS' ) );
  END IF;
END;
/

Then the output will be:

2018-04-27 10:28:59
2018-04-27 00:00:00

and you can see the difference.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM