使 oracle last_day 函数在 oracle 和 h2 之间兼容

Posted

技术标签:

【中文标题】使 oracle last_day 函数在 oracle 和 h2 之间兼容【英文标题】:Make oracle last_day function compatible between oracle and h2 【发布时间】:2019-09-04 15:58:22 【问题描述】:

我在 java 项目中有一个 sql 查询。它必须在 oracle 和 h2 中执行,具体取决于客户端和 DBMS 选举。

它必须返回 sent_date 为 Month 减一的值(上个月的注册表)

SELECT * FROM TABLE WHERE SENT_DATE BETWEEN ADD_MONTHS(TRUNC(SYSDATE,'mm'),-1) 
AND LAST_DAY(ADD_MONTHS(TRUNC(SYSDATE,'mm'),-1)) ORDER BY ELAPSED_TIME DESC

问题是,LAST_DAY 函数是 Oracle 特定的函数,在 H2 中不存在,并且 H2 日期管理有时在 Oracle 中不起作用,但我需要它完全兼容。之前有一个问题Make Oracle last_day function be compatible with H2 database,但答案集中在测试不佳的系统上,而不是正确的解决方案上。

我们怎样才能做到这一点?

【问题讨论】:

【参考方案1】:

可能会更开心:

SELECT * FROM TABLE
WHERE SENT_DATE BETWEEN ADD_MONTHS(TRUNC(SYSDATE,'mm'),-1) AND TRUNC(SYSDATE,'mm') - 1
ORDER BY ELAPSED_TIME DESC

表明它们评估为相同的值:

SELECT ADD_MONTHS(TRUNC(SYSDATE,'mm'),-1) as orig_start,
  LAST_DAY(ADD_MONTHS(TRUNC(SYSDATE,'mm'),-1)) as orig_end,
  TRUNC(SYSDATE,'mm') - 1 as new_end
FROM dual;

ORIG_START          ORIG_END            NEW_END            
------------------- ------------------- -------------------
2019-08-01 00:00:00 2019-08-31 00:00:00 2019-08-31 00:00:00

但请记住,between 包含在内,因此如果您的 sent_date 有非午夜时间,则将排除 2019-08-31 00:00:01 到 2019-08-31 23:59:59 之间的任何时间。

这样做更安全:

SELECT * FROM TABLE
WHERE SENT_DATE >= ADD_MONTHS(TRUNC(SYSDATE,'mm'),-1)
AND SENT_DATE < TRUNC(SYSDATE,'mm')
ORDER BY ELAPSED_TIME DESC

这将包括 2019 年 8 月 1 日 00:00:00 或之后的所有内容,直到(但不包括)2019 年 9 月 1 日 00:00:00 - 这样就不会出现缺失的日期。

或者如果它不能识别 add_months 或者:

SELECT * FROM TABLE
WHERE SENT_DATE >= TRUNC(SYSDATE,'mm') - INTERVAL '1' MONTH
AND SENT_DATE < TRUNC(SYSDATE,'mm')
ORDER BY ELAPSED_TIME DESC

(我无法测试这些中的任何一个是否真的在 H2 中工作... *8-)

【讨论】:

【参考方案2】:
LAST_DAY(ADD_MONTHS(TRUNC(SYSDATE,'mm'),-1))

可以简化为

TRUNC(SYSDATE,'mm') - 1

没有LAST_DAY函数,但是H2也不支持带有'mm'参数的非标准TRUNC

所以你也需要避免它。这样的查询将与两个数据库兼容(但是,您需要 H2 的最新版本,例如 1.4.199):

SELECT * FROM tableName WHERE SENT_DATE BETWEEN
    CURRENT_DATE - (EXTRACT(DAY FROM CURRENT_DATE) - 1) - INTERVAL '1' MONTH
AND
    CURRENT_DATE - EXTRACT(DAY FROM CURRENT_DATE)
ORDER BY ELAPSED_TIME DESC

请注意,这个查询不是标准的,我故意使用Oracle风格的日期算法来简化查询,H2支持它。如果你愿意,你也可以在这里使用非标准的ADD_MONTH

如果您只需要使用标准语法来兼容更多数据库,则可以避免使用 Oracle/H2 风格的算法:

SELECT * FROM tableName WHERE SENT_DATE BETWEEN
    CURRENT_DATE - (EXTRACT(DAY FROM CURRENT_DATE) - 1) * INTERVAL '1' DAY - INTERVAL '1' MONTH
AND
    CURRENT_DATE - EXTRACT(DAY FROM CURRENT_DATE) * INTERVAL '1' DAY
ORDER BY ELAPSED_TIME DESC

还请注意,Oracle 与其他数据库不同,没有正常的DATE 数据类型,它的DATE 类型可能有时间(H2 可以在Oracle 兼容模式下模拟这种偏差)。因此,所有这些带有“BETWEEN”的查询,包括您最初的仅 Oracle 查询,只有在您确保您的日期都在 00:00:00 时才能正常工作。如果您的日期还包含时间部分,您需要使用几个比较运算符:

SELECT * FROM tableName WHERE SENT_DATE >=
    CURRENT_DATE - (EXTRACT(DAY FROM CURRENT_DATE) - 1) - INTERVAL '1' MONTH
AND SENT_DATE <
    CURRENT_DATE - (EXTRACT(DAY FROM CURRENT_DATE) - 1)
ORDER BY ELAPSED_TIME DESC

(此处也使用了 Oracle/H2 算法,对于其他数据库,可能需要与 INTERVAL '1' DAY 相乘。)

【讨论】:

以上是关于使 oracle last_day 函数在 oracle 和 h2 之间兼容的主要内容,如果未能解决你的问题,请参考以下文章

oracle 日期常用函数 (ADD_MONTHS,LAST_DAY,NEXT_DAY,MONTHS_BETWEEN,NEW_TIME,ROUND,TRUNC)

Oracle trunc()函数的用法

Oracle - LAST_DAY 和 TRUNC - 执行顺序

oracle LAST_DAY(sysdate) 比较问题

oracle获取本月第一天和最后一天及Oracle trunc()函数的用法

具有函数 ORA-08103 的 Oracle 存储过程