(Oracle) SQL 中的正则表达式将日期/时间拆分为单独的日期和时间列

Posted

技术标签:

【中文标题】(Oracle) SQL 中的正则表达式将日期/时间拆分为单独的日期和时间列【英文标题】:Regular Expressions in (Oracle) SQL to split date/time into separate date and time columns 【发布时间】:2020-03-16 17:09:13 【问题描述】:

我正在尝试使用正则表达式从包含日期和时间的列中拆分日期和时间。

前- 当前:

+------------------------------+
|           original           |
+------------------------------+
| 21-JAN-16 01.20.51.097679 PM |
| 02-FEB-16 01.32.53.469568 PM |
| 01-DEC-15 06.00.41.315710 AM |
| 14-JAN-16 04.11.53.107373 PM |
| 21-JAN-16 01.05.15.113135 PM |
| 03-FEB-16 12.06.10.128735 PM |
| 04-FEB-16 06.24.59.557983 PM |
+------------------------------+

期望:

+-------------+-------------+
|    date     |    time     |
+-------------+-------------+
| 21-JAN-2016 | 01:20:51 PM |
| 02-FEB-2016 | 01:32:53 PM |
| 01-DEC-2015 | 06:00:41 AM |
| 14-JAN-2016 | 04:11:53 PM |
| 21-JAN-2016 | 01:05:15 PM |
| 03-FEB-2016 | 12:06:10 PM |
| 04-FEB-2016 | 06:24:59 PM |
+-------------+-------------+

数据类型为 VARCHAR2。

这是我尝试过的:

SELECT
REGEXP_REPLACE(original,
  '([[:digit:]]2)\-([[:alpha:]]3)\-([[:digit:]]2)\ ([[:digit:]]2)\.([[:digit:]]2)\.([[:digit:]]2)\.([[:digit:]]1,\.([[:alpha:]]2))',
  '\1-\2-20\3') "date",

REGEXP_REPLACE(original,
  '([[:digit:]]2)\-([[:alpha:]]3)\-([[:digit:]]2)\ ([[:digit:]]2)\.([[:digit:]]2)\.([[:digit:]]2)\.([[:digit:]]1,\.([[:alpha:]]2))',
  '\4:\5:\6 \7') "time"
FROM database.table

这是我运行上述代码时得到的结果:

+------------------------------+------------------------------+
|             date             |             time             |
+------------------------------+------------------------------+
| 21-JAN-16 01.20.51.097679 PM | 21-JAN-16 01.20.51.097679 PM |
| 02-FEB-16 01.32.53.469568 PM | 02-FEB-16 01.32.53.469568 PM |
| 01-DEC-15 06.00.41.315710 AM | 01-DEC-15 06.00.41.315710 AM |
| 14-JAN-16 04.11.53.107373 PM | 14-JAN-16 04.11.53.107373 PM |
| 21-JAN-16 01.05.15.113135 PM | 21-JAN-16 01.05.15.113135 PM |
| 03-FEB-16 12.06.10.128735 PM | 03-FEB-16 12.06.10.128735 PM |
| 04-FEB-16 06.24.59.557983 PM | 04-FEB-16 06.24.59.557983 PM |
+------------------------------+------------------------------+

不太清楚我哪里出错了?

【问题讨论】:

你得到的意味着没有被替换,在任何一次尝试中。这意味着您编写的正则表达式与输入字符串不匹配。几乎可以肯定的原因(或主要原因之一)是您错误地使用了反斜杠。它应该只用于逃避需要逃避的东西。例如,空间不需要转义。 Dash 不需要转义(它是一个常规字符,除非在一个无论如何都不能转义的字符类中)。等等。 当然,通过更正您的数据模型,可以避免所有这些喧嚣。日期和时间应始终存储在 DATE 或 TIMESTAMP 的某些变体中。两种数据类型都包含日期和时间组件,将两者分开只是正确使用 TO_CHAR 函数的简单问题。 【参考方案1】:

Oracle 具有多种处理日期和时间的函数。这个怎么样reduces your problem

SELECT TO_TIMESTAMP(original, 'DD-MON-RR HH:MI:SS.FF AM') as ORIGINAL_VALUE,
TO_TIMESTAMP(original, 'DD-MON-RR') AS DATE_ONLY,
TO_TIMESTAMP(original, 'HH:MI:SS.FF AM') AS TIME_ONLY
from database.table;

【讨论】:

【参考方案2】:

由于您的数据非常有规律,我根本不会使用正则表达式。 SUBSTR 工作正常。

-- sample data
with orig as (select '21-JAN-16 01.20.51.097679 PM' as dt from dual)
-- query
select substr(dt,1,9) as d, substr(dt,11) as t
from orig;

但是您的正则表达式中的问题是您的最后一个 \. 应该是 \ - 它是 PM 之前的一个空格,而不是句号。而且你不需要转义空格,所以它可以是

'([[:digit:]]2)\-([[:alpha:]]3)\-([[:digit:]]2) ([[:digit:]]2)\.([[:digit:]]2)\.([[:digit:]]2)\.([[:digit:]]1, ([[:alpha:]]2))'

【讨论】:

【参考方案3】:

不要将日期和时间用作字符串;您应该使用适当的数据类型:

使用TIMESTAMP 数据类型表示带有小数秒的日期和时间。 对日期使用 DATE 数据类型(如有必要,将时间部分截断回午夜)。 如果您想要一个没有日期的时间,请使用 INTERVAL 数据类型。

所以,你可以使用:

SELECT TRUNC( datetime ) AS "date",
       datetime - TRUNC( datetime ) AS "time"
FROM   (
  SELECT TO_TIMESTAMP( original, 'DD-MON-RR HH12.MI.SS.FF6 AM' ) AS datetime
  FROM   table_name
)

将输出哪个(取决于您的 NLS 设置):

日期 |时间 :-------- | :---------------------------- 16 年 1 月 21 日 | +000000000 13:20:51.097679000 2016 年 2 月 2 日 | +000000000 13:32:53.469568000 15 年 12 月 1 日 | +000000000 06:00:41.315710000 16 年 1 月 14 日 | +000000000 16:11:53.107373000 16 年 1 月 21 日 | +000000000 13:05:15.113135000 2016 年 2 月 3 日 | +000000000 12:06:10.128735000 2016 年 2 月 4 日 | +000000000 18:24:59.557983000

如果您真的想使用字符串,请将字符串转换为最合适的数据类型(TIMESTAMP),然后使用TO_CHAR 将其转换回具有所需格式模型的字符串:

SELECT TO_CHAR( datetime, 'DD-MON-YYYY' ) AS "date",
       TO_CHAR( datetime, 'HH12.MI.SS AM' ) AS "time"
FROM   (
  SELECT TO_TIMESTAMP( original, 'DD-MON-RR HH12.MI.SS.FF6 AM' ) AS datetime
  FROM   table_name
)

哪些输出:

日期 |时间 :------------ | :---------- 2016 年 1 月 21 日 | 01.20.51 下午 2016 年 2 月 2 日 | 01.32.53 PM 2015 年 12 月 1 日 | 06.00.41 AM 2016 年 1 月 14 日 | 04.11.53 下午 2016 年 1 月 21 日 | 01.05.15 PM 2016 年 2 月 3 日 | 12.06.10 下午 2016 年 2 月 4 日 | 06.24.59 PM

回答你提出的问题:

不太清楚我哪里出错了?

当你应该有一个空格并且时间的输出模式应该是\4:\5:\6 \8时,你在最终子午线值之前有一个\.

SELECT REGEXP_REPLACE(
         original,
         '([[:digit:]]2)\-([[:alpha:]]3)\-([[:digit:]]2)\ ([[:digit:]]2)\.([[:digit:]]2)\.([[:digit:]]2)\.([[:digit:]]1, ([[:alpha:]]2))',
         '\1-\2-20\3'
       ) "date",
       REGEXP_REPLACE(
         original,
         '([[:digit:]]2)\-([[:alpha:]]3)\-([[:digit:]]2)\ ([[:digit:]]2)\.([[:digit:]]2)\.([[:digit:]]2)\.([[:digit:]]1, ([[:alpha:]]2))',
         '\4:\5:\6 \8'
       ) "time"
FROM   table_name

输出:

日期 |时间 :------------ | :---------- 2016 年 1 月 21 日 |下午 1 点 20 分 51 秒 2016 年 2 月 2 日 |下午 1 点 32 分 53 秒 2015 年 12 月 1 日 |上午 06:00:41 2016 年 1 月 14 日 |下午 4 点 11 分 53 秒 2016 年 1 月 21 日 | 01:05:15 PM 2016 年 2 月 3 日 |下午 12:06:10 2016 年 2 月 4 日 |下午 06:24:59

您可以通过去掉不必要的转义字符并使用 perl 样式的字符组而不是 POSIX 字符组来简化正则表达式:

^(\d2)-([A-Z]3)-(\d2) (\d2)\.(\d2)\.(\d2)\.(\d1,) ([AP]M)$

db小提琴here

【讨论】:

【参考方案4】:
select 
to_char(to_timestamp(s, 'dd-MON-RR hh12.mi.ss.ff6 PM'), 'dd-MON-yyyy') s1, 
to_char(to_timestamp(s, 'dd-MON-RR hh12.mi.ss.ff6 PM'), 'hh12.mi.ss PM') s2
from t

【讨论】:

【参考方案5】:

如果您的列originalvarchar2,那么您需要将其转换为timestamp amd 然后分别返回日期和时间,如下所示:

select 
to_char(to_timestamp(original,'dd-mon-rr hh.mi.ss.ff PM'), 'dd-mon-rr') as "DATE",
to_char(to_timestamp(original,'dd-mon-rr hh.mi.ss.ff PM'), 'hh:mi:ss PM') as "TIME",
from your_table

干杯!!

【讨论】:

以上是关于(Oracle) SQL 中的正则表达式将日期/时间拆分为单独的日期和时间列的主要内容,如果未能解决你的问题,请参考以下文章

oracle sql查询中用in列表中最大表达式数大于1000的处理

oracle sql查询中用in列表中最大表达式数大于1000的处理

PL/SQL Oracle 正则表达式对于零的出现不起作用

Oracle SQL 查询中的正则表达式 - 仅限数字和句点

Oracle正则表达式

带正则表达式的参数化 SQL、ORACLE 与 SQL Server