【问题标题】:PL/SQL function with date parameter returns wrong output带有日期参数的 PL/SQL 函数返回错误的输出
【发布时间】:2020-07-25 04:32:18
【问题描述】:

为了使我的主要代码更具可读性,我在函数中封装了一个将日期转换为纪元格式的操作。但是,该函数返回不正确的值。我做错了什么?

我得到了什么
功能

create or replace function date_to_epoch(
        date_in in date)
        return number
        is
            epoch_out number;
            begin
            epoch_out := round((to_date(date_in, 'yyyy-mm-dd hh24:mi:ss') - to_date('1970-01-01', 'yyyy-mm-dd'))*24*60*60);
            return epoch_out;
            end;

我调用函数:

declare
    date_out number;
begin
select date_to_epoch(to_date('2020-01-01 00:00:00', 'yyyy-mm-dd hh24:mi:ss')) into date_out from dual;
DBMS_OUTPUT.PUT_LINE('Output:' || date_out);
end;

返回:-62134128000

我的期望
当我在一个简单的 SQL 语句中运行相同的操作时,我得到了预期的输出:

select round((to_date('2020-01-01', 'yyyy-mm-dd hh24:mi:ss') - to_date('1970-01-01', 'yyyy-mm-dd'))*24*60*60) from dual;

返回:1577836800

【问题讨论】:

    标签: sql oracle date plsql sql-function


    【解决方案1】:

    您的输入参数已经是一个日期,因此您不应在其上使用to_date()。当你这样做时,坏事就会发生。

    我会把你的代码写成:

    create or replace function date_to_epoch(date_in in date)
    return number
    is
        epoch_out number;
    begin
        epoch_out := round((date_in - date '1970-01-01')*24*60*60);
        return epoch_out;
    end;
    

    请注意,您实际上并不需要中间变量来存储函数的返回值。你可以直接这样做:

    create or replace function date_to_epoch(date_in in date)
    return number
    is
    begin
       return round((date_in - date '1970-01-01')*24*60*60);
    end;
    

    在这个demo on DB Fiddle中,代码产生:

    Output:1577836800
    

    【讨论】:

    • 因为函数实际上是确定性的,所以我会将其添加到函数声明中。它可能会以多种方式提供帮助。
    • @mathguy 你能举一个小例子说明它会有所帮助吗?
    • @honeybees - 如果您需要在基于函数的索引或约束中使用该函数,该函数必须是确定性的。
    【解决方案2】:

    我做错了什么?

    TO_DATE function 有签名:

    TO_DATE( date_string, format_model, nls_param )
    

    所有参数都应具有字符串数据类型。

    您没有将字符串作为第一个参数传递;您正在传递 DATE 数据类型。 Oracle 将尝试提供帮助,并通过使用当前会话的 NLS 日期格式会话参数隐式调用 TO_CHAR 将您传递的 DATE 转换为字符串。

    所以,你的功能是有效的:

    create or replace function date_to_epoch(
      date_in in date
    ) return number
    is
      epoch_out number;
    begin
      epoch_out := round(
                     ( to_date(
                         TO_CHAR(
                           date_in,
                           ( SELECT value
                             FROM   NLS_SESSION_PARAMETERS
                             WHERE  parameter = 'NLS_DATE_FORMAT' )
                         ),
                         'yyyy-mm-dd hh24:mi:ss'
                       )
                       -
                       to_date(
                         '1970-01-01',
                         'yyyy-mm-dd'
                       )
                     )*24*60*60
                   );
      return epoch_out;
    end;
    

    要修复它,您只需将日期输入用作日期,而不是尝试将已经是日期的内容转换为日期:

    CREATE FUNCTION date_to_epoch(
      date_in IN DATE
    ) RETURN NUMBER DETERMINISTIC
    IS
    BEGIN
      RETURN ROUND( ( date_in - DATE '1970-01-01' )*24*60*60 );
    END date_to_epoch;
    

    但是,您还需要确保您的日期在 UTC 时区中,并且在计算纪元之前它们没有转换为 UTC:

    CREATE FUNCTION date_to_epoch(
      date_in  IN DATE,
      timezone IN VARCHAR2 DEFAULT 'UTC'
    ) RETURN NUMBER DETERMINISTIC
    IS
    BEGIN
      RETURN ROUND(
        ( CAST(
            FROM_TZ( date_in, timezone ) -- Convert to a timestamp in the correct TZ
            AT TIME ZONE 'UTC'           -- Change to the UTC TZ
            AS DATE                      -- Cast back to a date
          )
          - DATE '1970-01-01'
        )*24*60*60
      );
    END date_to_epoch;
    /
    

    所以:

    SELECT date_to_epoch( DATE '1970-01-01' )        AS utc_epoch,
           date_to_epoch( DATE '1970-01-01', 'PST' ) AS pst_epoch
    FROM   DUAL;
    

    输出:

    UTC_EPOCH | PST_EPOCH --------: | --------: 0 | 28800

    db小提琴here

    【讨论】:

    • 它有效,谢谢。确保输入日期为 UTC 的建议也是一个很好的建议。就我而言,我必须假设输入日期为 UTC,因为我的数据源不包含时区信息。但是根据你的建议,我会让主代码更聪明一点,这样它就可以识别出时区和夏令时的差异。
    • @honeybees - 例如,如果您住在东京,并且您的所有业务都在那里,那么一个同样可能的假设(如果不是更多的话)是所有日期都在东京时区。这不是应该做的假设;相反,请询问您的业务用户。 (或者询问那些建立数据模型的人——他们可能知道假设的时区。)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-06-04
    • 2013-05-06
    • 1970-01-01
    相关资源
    最近更新 更多