【问题标题】:Create date from year, month and day从年、月和日创建日期
【发布时间】:2015-10-10 20:32:06
【问题描述】:

Oracle 是否有一个内置函数可以从其各个组件(年、月和日)创建一个仅在丢失数据时返回 null 的日期?

我知道TO_DATE(),但我需要先编写一个字符串,|| 运算符和CONCAT() 函数都不能轻松处理丢失的数据:

-- my_year NUMBER(4,0) NULL
SELECT TO_DATE(my_year || '-01-01', 'YYYY-MM-DD') AS my_date
FROM my_table;

只要my_yearNULL,我们就会以TO_DATE('-01-01', 'YYYY-MM-DD') 结束,并且:

ORA-01841: (full) year must be between -4713 and +9999, and not be 0

【问题讨论】:

  • my_year的值是多少?
  • @AlexK。 - 任何适合my_year NUMBER(4,0) NULL 列的内容,包括NULL

标签: sql oracle oracle10g oracle-xe


【解决方案1】:

对于您的示例,您可以使用case:

select (case when my_year is not null and my_year <> 0 and
                  my_year between -4713 and 9999
             then TO_DATE(my_year || '-01-01', 'YYYY-MM-DD')
        end)

不幸的是,如果可能的话,Oracle 没有进行转换的方法,否则返回 NULL。 SQL Server 最近为此引入了try_convert()

一种选择是为失败的转换编写带有异常处理程序的您自己的函数。异常处理程序将简单地返回 NULL 用于错误格式。

【讨论】:

  • 谢谢。我想我的担心是真的:)
【解决方案2】:

您不能将零年与to_date('0000-01-01', 'YYYY-MM-DD') 一起使用,但奇怪的是您可以与日期文字date '0000-01-01' 一起使用。其本身成为-1年,但您可以使用它进行计算;您也可以根据您的数值添加一个间隔,例如:

SELECT DATE '0000-01-01' + NUMTOYMINTERVAL(my_year, 'YEAR') AS my_date
FROM my_table;

numtoyminterval function 如果参数为 null,则返回 null,将其添加到固定日期也会为您返回 null:

alter session set nls_date_format = 'SYYYY-MM-DD';

select date '0000-01-01' + numtoyminterval(null, 'YEAR') from dual;

DATE'0000-01-01'+NUMTOYMINTERVAL(NULL,'YEAR')
---------------------------------------------


select date '0000-01-01' + numtoyminterval(2015, 'YEAR') from dual;

DATE'0000-01-01'+NUMTOYMINTERVAL(2015,'YEAR')
---------------------------------------------
 2015-01-01                                  

select date '0000-01-01' + numtoyminterval(9999, 'YEAR') from dual;

DATE'0000-01-01'+NUMTOYMINTERVAL(9999,'YEAR')
---------------------------------------------
 9999-01-01                                  

select date '0000-01-01' + numtoyminterval(-4712, 'YEAR') from dual;

DATE'0000-01-01'+NUMTOYMINTERVAL(-4712,'YEAR')
----------------------------------------------
-4712-01-01                                   

这不是万无一失的;如果你尝试在 -4713 之前继续,它仍然会出错:

select date '0000-01-01' + numtoyminterval(-4713, 'YEAR') from dual;

SQL Error: ORA-01841: (full) year must be between -4713 and +9999, and not be 0
...

尽管您可以通过对列的检查约束来避免这种情况。由于 0 年到 -1 年的无声转换,如果您的 my_year 值为 0 或 -1,您会得到相同的答案:

select date '0000-01-01' + numtoyminterval(0, 'YEAR') from dual;

DATE'0000-01-01'+NUMTOYMINTERVAL(0,'YEAR')
------------------------------------------
-0001-01-01                               

select date '0000-01-01' + numtoyminterval(-1, 'YEAR') from dual;

DATE'0000-01-01'+NUMTOYMINTERVAL(-1,'YEAR')
-------------------------------------------
-0001-01-01                                

Gordon Linoff 的案例方法更加稳健,但如果您只是真正处理“理智”积极的年份,这可能会奏效。如果没有,那就有点意思了……

【讨论】:

  • 这就是我的答案!我将建议使用 y2m 和 d2s 间隔参数包装一个用户定义的函数;但都是一样的。
  • 是的,不确定它是否只是问题开始的一年,但正如你所说,它是可扩展的。并且可能值得包装在一个函数中。
【解决方案3】:

我最终编写了一个用户定义的函数来封装逻辑。它从其各个组件中返回一个日期,如果日期无效,则返回 NULL

CREATE OR REPLACE FUNCTION TRY_TO_DATE (
    V_YEAR IN NUMBER,
    V_MONTH IN NUMBER,
    V_DAY IN NUMBER
) RETURN DATE DETERMINISTIC IS
BEGIN
    RETURN TO_DATE(LPAD(V_YEAR, 4, '0') || LPAD(V_MONTH, 2, '0') || LPAD(V_DAY, 2, '0'), 'YYYY-MM-DD');
    EXCEPTION
        WHEN OTHERS THEN RETURN NULL;
END TRY_TO_DATE;
/

Fiddle

【讨论】:

    【解决方案4】:
    where my_year > 0
    

    这只会返回可以创建日期的行。忽略 null 或值为 0 的行。如果某些值不在 -4713 和 +9999 范围内,当然也应该排除 where 子句中的值。

    【讨论】:

    • 我仍然需要使用该行。从计算中过滤掉“坏”值并不难(参见 Gordon 的回答),但语法过于冗长。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-05-06
    • 1970-01-01
    • 1970-01-01
    • 2018-10-15
    • 1970-01-01
    • 2011-04-26
    • 1970-01-01
    相关资源
    最近更新 更多