为什么 Oracle 的 SQL 中的文字“01-01-07”是错误的?

Tim*_*Tim 1 sql oracle date

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;
Run Code Online (Sandbox Code Playgroud)

我正在研究这个问题,我在 sqldeveloper 中创建了表,我尝试在 B 中运行相同的代码,并且它有效。所以我想知道为什么答案 B 被认为是错误的。

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;
Run Code Online (Sandbox Code Playgroud)

我做的桌子。我尝试运行的脚本

SELECT * FROM cust_trans WHERE transdate = '01-01-07'; 
Run Code Online (Sandbox Code Playgroud)

它在sqldeveloper中成功运行,我正在运行最新的oracle版本

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
Run Code Online (Sandbox Code Playgroud)

我的配置

MT0*_*MT0 6

日期以默认日期格式存储在表dd-mm-rrCUST_TRANS

这种说法是错误的!ADATE是一种二进制数据类型,由 7 个字节组成,分别表示:世纪、世纪年份、月、日、小时、分钟和秒。它始终与所有这些组件一起存储,并且永远不会以任何特定(人类可读)格式存储,因为它存储为二进制值。

如果您以该格式存储类似日期的数据dd-mm-rr,那么您将它们存储为字符串而不是DATE. 如果您将日期存储为 a,DATE则无法以人类可读的格式存储它们。

如果您想查看 Oracle 如何存储,DATE则使用:

SELECT DUMP(transdate) FROM cust_trans;
Run Code Online (Sandbox Code Playgroud)

提出问题的人可能的意思是,这dd-mm-rr是 Oracle 用于字符串到日期和日期到字符串转换(使用NLS_DATE_FORMAT会话参数)的默认格式,以及某些客户端应用程序(即 SQL*Plus 和 SQL) Developer)用作显示日期时的默认显示格式。但是,仅仅因为某些会话参数允许对字符串进行隐式转换并不意味着这是一个好的做法。

声明这dd-mm-rr是默认日期格式也是错误的,因为它只是某些地区(阿尔及利亚、巴林、印度、摩洛哥、荷兰和突尼斯)会话参数的默认格式;NLS_DATE_FORMAT在世界其他地方,这不是默认设置。


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

我尝试在 B 中运行相同的代码,并且它有效。所以我想知道为什么答案B被认为是错误的。

B. 如果 Oracle 可以执行从字符串到 a 的隐式转换DATE,并且它使用NLS_DATE_FORMAT会话来执行此操作,则将起作用。

该代码实际上与以下内容相同:

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

如果NLS_DATE_FORMAT匹配dd-mm-rr(或字符串到日期转换规则的其他格式),那么它将解析它。

如果你这样做:

ALTER SESSION SET NLS_DATE_FORMAT = 'dd-mm-rr';
SELECT * FROM cust_trans WHERE transdate = '01-01-07'
Run Code Online (Sandbox Code Playgroud)

然后日期将被解析为2007-01-01 00:00:00但如果您使用:

ALTER SESSION SET NLS_DATE_FORMAT = 'YYYY-MM-DD HH24:MI:SS';
SELECT * FROM cust_trans WHERE transdate = '01-01-07'
Run Code Online (Sandbox Code Playgroud)

然后日期将被解析为0001-01-07 00:00:00具有意外的世纪并且交换了年份和日期。

如果您使用:

ALTER SESSION SET NLS_DATE_FORMAT = 'DD-MON-RR';
SELECT * FROM cust_trans WHERE transdate = '01-01-07'
Run Code Online (Sandbox Code Playgroud)

然后它不会解析该值并引发异常ORA-01843: not a valid month

db<>在这里摆弄

永远不应该依赖字符串到日期的隐式转换。


更好的解决方案是:

因为这些都不依赖于数据类型之间的隐式转换。


至于问题的答案。

如果NLS_DATE_FORMATDD-MM-RR,则四个(而不是三个)选项是将成功解析和执行的查询。

鉴于设置:

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';
Run Code Online (Sandbox Code Playgroud)

然后:

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

    将执行从字符串文字'10'到数字的隐式转换10,然后添加 10 天transdate并输出:

    转译+'10'
    7月11日
  • B.SELECT * FROM cust_trans WHERE transdate = '01-01-07';

    将执行从字符串到日期的隐式转换并输出:

    客户编号 翻译 运输公司
    20 01-01-07 5000
  • C。SELECT transamt FROM cust_trans WHERE custno > '11';

    有效并输出:

    运输公司
    5000
  • D .SELECT * FROM cust_trans WHERE transdate='01-JANUARY-07';

    将执行从字符串到日期的隐式转换,并应用字符串到日期的转换规则,该规则MM还允许匹配格式模型MON,并且MONTH将输出:

    客户编号 翻译 运输公司
    20 01-01-07 5000
  • E.SELECT custno + 'A' FROM cust_trans WHERE transamt > 2000;

    不需要像transamt > 2000有效过滤器那样执行隐式转换。但是,custno + 'A'它无效,因为您无法将字符串添加到日期。如果您想连接字符串,那么您需要||运算符而不是+。这将引发异常:

    ORA-01722: invalid number
    
    Run Code Online (Sandbox Code Playgroud)

    由于+运算符期望第二个操作数是数字,但事实并非如此。

db<>在这里摆弄