3
CUST_TRANS -
Name Null? Type
-------------- ----------------- ------------------
CUSTNO NOT NULL CHAR (2)
TRANSDATE DATE
TRANSAMT NUMBER (6, 2)
CUSTNO TRANSDATE TRANSAMT
------------- ----------------------- -----------------------
11 01-JAN-07 1000
22 01-FEB-07 2000
33 01-MAR-07 3000

Dates are stored in the default date format dd-mm-rr in the CUST_TRANS table.
Which three SQL statements would execute successfully? (Choose three.)

    A. SELECT transdate + '10' FROM cust_trans;
    B. SELECT * FROM cust_trans WHERE transdate = '01-01-07';
    C. SELECT transamt FROM cust_trans WHERE custno > '11';
    D. SELECT * FROM cust_trans WHERE transdate='01-JANUARY-07';
    E. SELECT custno + 'A' FROM cust_trans WHERE transamt > 2000;

i am working on this question, and i created the table in sqldeveloper,i tried running the same code in B, and it works. so i am wondering why is answer B considered to be wrong.

INSERT ALL 
INTO CUST_TRANS VALUES (11, '01-jAN-07', 1000)
INTO CUST_TRANS VALUES (22, '01-FEB-07', 2000)
INTO CUST_TRANS VALUES (33, '01-MAR-07', 3000)

SELECT * FROM DUAL;

the table i made. the script i tried running

SELECT * FROM cust_trans WHERE transdate = '01-01-07'; 

it runs successfully in sqldeveloper, i am running the latest oracle version

NLS_LANGUAGE    ENGLISH
NLS_TERRITORY   CANADA
NLS_CURRENCY    $
NLS_ISO_CURRENCY    CANADA
NLS_NUMERIC_CHARACTERS  .,
NLS_CALENDAR    GREGORIAN
NLS_DATE_FORMAT RR-MM-DD
NLS_DATE_LANGUAGE   ENGLISH
NLS_SORT    BINARY
NLS_TIME_FORMAT HH24:MI:SSXFF
NLS_TIMESTAMP_FORMAT    RR-MM-DD HH24:MI:SSXFF
NLS_TIME_TZ_FORMAT  HH24:MI:SSXFF TZR
NLS_TIMESTAMP_TZ_FORMAT RR-MM-DD HH24:MI:SSXFF TZR
NLS_DUAL_CURRENCY   $
NLS_COMP    BINARY
NLS_LENGTH_SEMANTICS    BYTE
NLS_NCHAR_CONV_EXCP FALSE

my config

Tim
  • 95
  • 7
  • 2
    See [Datetime Literals](https://docs.oracle.com/cd/B19306_01/server.102/b14200/sql_elements003.htm#BABGIGCJ) for why `'01-01-07'` isn't valid – Phil Jul 12 '22 at 04:56
  • @Phil thanks, i will take note of that for the future – Tim Jul 12 '22 at 04:57
  • 1
    See this [db<>fiddle](https://dbfiddle.uk/?rdbms=oracle_21&fiddle=e2f3f831e11a2c8233abe1940f5bf15d) – Abra Jul 12 '22 at 05:01
  • @Abra i've runned the code in sqldeveloper and it works fine – Tim Jul 12 '22 at 05:02
  • @Phil i fixed it, hope is better now – Tim Jul 12 '22 at 05:04
  • 3
    The question is strange, because date is a binary data type that doesn't have any storage format related to the human-readable representation – astentx Jul 12 '22 at 05:09
  • What version of Oracle (as in the DB server, not SQLDeveloper) are you running / connecting to? – Phil Jul 12 '22 at 05:18
  • @Phil database version 19c – Tim Jul 12 '22 at 05:20
  • Does this answer your question? [ORA-01843 not a valid month- Comparing Dates](https://stackoverflow.com/questions/21157224/ora-01843-not-a-valid-month-comparing-dates) – Phil Jul 12 '22 at 05:21
  • 1
    @phil it doesn't really answer my question, i can't produce the same error that other people are getting. I am guessing its my config, but at least now i understand this is bad practice. – Tim Jul 12 '22 at 05:33
  • @Tim compare ur default date format with other people with select * from nls_session_parameters where parameter = 'NLS_DATE_FORMAT'; – T Olaleye Jul 12 '22 at 05:49
  • 2
    VBoka explains the issue well in their answer. For you to remember: Always use date literals when possible (e.g. `DATE 2021-12-31')`. If you must deal with strings containing dates (this should almost never happen; well maybe when reading dates from some text file?), then always convert the string explicitely with the appropriate format (e.g. `TO_DATE(:datestring, 'DD-MON-YYYY', 'NLS_DATE_LANGUAGE=ENGLISH')`). – Thorsten Kettner Jul 12 '22 at 07:10
  • @ThorstenKettner But the question was "why is this set of answers valid?" The statement is correct and it's a shame to have such a question in the exam (if this is a real exam question; I've found it as a dump of 1Z0-047) because the code relying on the *user's* defaults to access shared data is, well, not a production-ready – astentx Jul 12 '22 at 07:18
  • @astentx: Yep, that's true of course. The question is flawed and must not be part of an exam. I just thought I'd point out that we don't want implicit date conversions to happen, because they make the queries prone to runtime errors. – Thorsten Kettner Jul 12 '22 at 07:38
  • I've long ago abandoned worrying about the NLS default parsing blah blah because of all the hoohah that you're seeing in this discussion. I just write `WHERE transdate = TO_DATE( '2001-01-07', 'YYYY-MM-DD' )` and it always works no matter what settings are set. – Andy Lester Jul 12 '22 at 14:03

2 Answers2

6

Dates are stored in the default date format dd-mm-rr in the CUST_TRANS table.

This statement is wrong! A DATE is a binary data type consisting of 7 bytes representing: century, year-of-century, month, day, hours, minutes and seconds. It is ALWAYS stored with all of those components and it is NEVER stored is any particular (human-readable) format as it is stored as binary values.

If you are storing date-like data in the format dd-mm-rr then you are storing them as strings and not as a DATE. If you are storing dates as a DATE then you cannot be storing them in a human-readable format.

If you want to see how Oracle stores the DATE then use:

SELECT DUMP(transdate) FROM cust_trans;

What the person who set the question probably means is that dd-mm-rr is the default format which Oracle uses for string-to-date and date-to-string conversions (which uses the NLS_DATE_FORMAT session parameter) and that some client applications (i.e. SQL*Plus and SQL Developer) use as the default display format when displaying dates. However, just because some session parameters allow for implicit conversion of your string does not mean that it is good practice.

It is also wrong to state that dd-mm-rr is the default date format as it is only the default for the NLS_DATE_FORMAT session parameter in certain territories (Algeria, Bahrain, India, Morocco, The Netherlands and Tunisia); in other places around the world it is not the default.


B. SELECT * FROM cust_trans WHERE transdate = '01-01-07';

I tried running the same code in B, and it works. So I am wondering why is answer B considered to be wrong.

B. will work provided Oracle can perform an implicit cast from a string to a DATE and it does that using the NLS_DATE_FORMAT session.

The code is effectively the same as:

SELECT *
FROM   cust_trans
WHERE  transdate = TO_DATE(
                     '01-01-07',
                     ( SELECT value
                       FROM   NLS_SESSION_PARAMETERS
                       WHERE  PARAMETER = 'NLS_DATE_FORMAT' )
                   );

If the NLS_DATE_FORMAT matches dd-mm-rr (or another format by the string-to-date conversion rules) then it will parse it.

If you do:

ALTER SESSION SET NLS_DATE_FORMAT = 'dd-mm-rr';
SELECT * FROM cust_trans WHERE transdate = '01-01-07'

Then the date will get parsed to 2007-01-01 00:00:00 but if you use:

ALTER SESSION SET NLS_DATE_FORMAT = 'YYYY-MM-DD HH24:MI:SS';
SELECT * FROM cust_trans WHERE transdate = '01-01-07'

Then the date will get parsed to 0001-01-07 00:00:00 which has an unexpected century and the year and days swapped.

If you use:

ALTER SESSION SET NLS_DATE_FORMAT = 'DD-MON-RR';
SELECT * FROM cust_trans WHERE transdate = '01-01-07'

Then it does not parse the value and raises the exception ORA-01843: not a valid month.

db<>fiddle here

You should NEVER rely on implicit casts of strings to dates.


A better solution would be:

  • Use a date literal

    SELECT * FROM cust_trans WHERE transdate = DATE '2007-01-01'
    
  • Use a timestamp literal

    SELECT * FROM cust_trans WHERE transdate = TIMESTAMP '2007-01-01 00:00:00'
    
  • or, use an explicit string-to-date conversion

    SELECT * FROM cust_trans WHERE transdate = TO_DATE('01-01-07', 'DD-MM-RR')
    

as none of these rely on an implicit cast between data types.


As for the answer to the question.

If the NLS_DATE_FORMAT is DD-MM-RR then four (not three) of the options are queries that will successfully parse and execute.

Given the setup:

CREATE TABLE cust_trans (
  custno    CHAR(2),
  transdate DATE,
  transamt  NUMBER
);
INSERT INTO cust_trans (custno, transdate, transamt) VALUES ('25', DATE '2007-01-01', 5000);
ALTER SESSION SET NLS_DATE_FORMAT = 'DD-MM-RR';

Then:

  • A. SELECT transdate + '10' FROM cust_trans;

    Will perform an implicit cast from the string literal '10' to a number 10 and then will add 10 days to transdate and outputs:

    TRANSDATE+'10'
    11-01-07
  • B. SELECT * FROM cust_trans WHERE transdate = '01-01-07';

    Will perform an implicit cast from string-to-date and output:

    CUSTNO TRANSDATE TRANSAMT
    20 01-01-07 5000
  • C. SELECT transamt FROM cust_trans WHERE custno > '11';

    Is valid and outputs:

    TRANSAMT
    5000
  • D. SELECT * FROM cust_trans WHERE transdate='01-JANUARY-07';

    Will perform an implicit cast from string-to-date and apply the string-to-date conversion rules that allow MM to also match the format models MON and MONTH and will output:

    CUSTNO TRANSDATE TRANSAMT
    20 01-01-07 5000
  • E. SELECT custno + 'A' FROM cust_trans WHERE transamt > 2000;

    Does not need to perform an implicit cast as transamt > 2000 is a valid filter. However, custno + 'A' is not valid as you cannot add a string to a date. If you wanted to concatenate strings then you need the || operator and not +. This will raise the exception:

    ORA-01722: invalid number
    

    As the + operator expects the second operand to be numeric and it is not.

db<>fiddle here

MT0
  • 143,790
  • 11
  • 59
  • 117
1

You have something called nls_date_format. You can set this for your session by altering it.

When you set it to the format of your question like this:

ALTER SESSION SET NLS_DATE_FORMAT = 'dd-mm-rr';

The B answer works just fine.

When you set it to, for example:

ALTER SESSION SET NLS_DATE_FORMAT = 'DD MON YYYY';

It will not work.

DEMO

You can check how this parameter is set in your session with this query:

select *
from nls_session_parameters
where parameter = 'NLS_DATE_FORMAT';
VBoka
  • 8,995
  • 3
  • 16
  • 24
  • So why does D work? (`SELECT * FROM cust_trans WHERE transdate='01-JANUARY-07'`) It looks to me that it does **not** conform to format of NLS_DATE_FORMAT. – Abra Jul 12 '22 at 05:49
  • Hi @Abra it will also not work for the 'ALTER SESSION SET NLS_DATE_FORMAT = 'DD MON YYYY';' format. D Answer will work for this format: 'ALTER SESSION SET NLS_DATE_FORMAT = 'DD-MON-YY';' and both will work with this format : 'ALTER SESSION SET NLS_DATE_FORMAT = 'DD-MM-YY';' – VBoka Jul 12 '22 at 06:06
  • @Abra See my answer. It works because of the [string-to-date conversion rules](https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlrf/Format-Models.html#GUID-5B755E80-3CB2-4901-BBCF-F0FC764E0BB5) that allows the `MM` format model to also match the formats `MON` and `MONTH` which is why `DD-MM-RR` can match both of `01-01-07` and `01-JANUARY-07`. (However, there is no rule that allows the reverse so the `MON` format model cannot match the `MM` format which is why `DD-MON-RR` will fail for `01-01-07`.) – MT0 Jul 12 '22 at 08:27