【问题标题】:Converting Varchar2 to Time in Oracle Sql在 Oracle Sql 中将 Varchar2 转换为时间
【发布时间】:2021-06-16 01:52:17
【问题描述】:

我有一个数据加载到一个表中,我有两列 Time In 和 Time Out,它们都是 Varchar2 类型。加载到这些列中的数据是带时间的日期,我想排除日期并仅用时间填充列。我的问题是我尝试将列中的所有日期格式化为 on 格式,它给了我错误“不是有效的月份”或“无效的数字”。我有一个查询用于尝试选择格式中的数据。如果我能得到正确的查询,那么我可以修复我的数据。

查询包括

SELECT
    to_date(to_char(time_in, 'HH:MI:SS AM'), 'HH:MI:SS AM') time_in,
    to_date(to_char(time_out, 'HH:MI:SS AM'), 'HH:MI:SS AM') time_out
FROM
    loadingtable

回顾一下,我有一个表,其中包含两个类型为 varchar2 的列,它们具有两种格式的日期和时间的数据。

我想格式化它以单独获得时间。

提前致谢!

【问题讨论】:

  • 你有这两种格式还是有其他可能的?
  • 我正在从 csv 及其大量文件中加载记录,到目前为止,这些是仅有的两种格式,并且将继续是仅有的两种格式
  • DATE 转换为字符串然后再转换回DATE 似乎很愚蠢。在字符值上使用 TO_CHAR() 也是没有意义的。你想达到什么目标?

标签: sql oracle oracle19c


【解决方案1】:

你可以这样做:

WITH 
function convert_date(str_i VARCHAR2) RETURN DATE
AS
  l_date DATE;
  TYPE format_mask_t IS TABLE OF VARCHAR2(128);
   l_format_masks format_mask_t := format_mask_t(
     'DD/MM/YY HH:MI AM'
    ,'DD-MM-YYYY HH:MI AM' -- add more if needed
   );
BEGIN
  FOR i IN l_format_masks.FIRST .. l_format_masks.LAST LOOP
    BEGIN
      l_date := TO_DATE(str_i, l_format_masks(i));
      RETURN l_date;
    EXCEPTION WHEN OTHERS THEN
      NULL;
    END;  
  END LOOP;
  RETURN NULL;
END;
source_tab (time_in, time_out) AS
(
  SELECT '12/2/21 7:03 AM', '12/2/21 4:24 PM' FROM DUAL UNION ALL
  SELECT '12-02-2021 7:01 AM', '15-02-2021 4:00 PM' FROM DUAL 
)
SELECT TO_CHAR(convert_date(time_in),'HH:MI AM') as time_in,
       TO_CHAR(convert_date(time_out),'HH:MI AM') as time_out
 FROM source_tab

这是使用内联函数 - 您可以创建一个函数而不是使用此内联函数。如果遇到与列出的 2 种格式中的任何一种都不匹配的格式,则返回 NULL。如果需要,您可以轻松添加其他格式。 “source_tab” CTE 只是为了生成一些测试数据,将其从查询中删除以获取您自己的数据。

【讨论】:

  • 温馨提示:使用此代码更新您的数据(即转换为DATE 数据类型)- 不仅用于选择。
  • 该功能的蛮力太多了。如果出现由格式模型不匹配以外的原因引起的异常怎么办?他们会被when others then吞噬。
  • @mathguy 好点,我从来没有使用过像你这样的方法 - 请记住这一点
【解决方案2】:

您可以直接在格式模型中使用case 表达式。如果您确定只有这两个确切的模型,则可以区分两种格式:字符串包含正斜杠,或者不包含 - 并在每种情况下使用相应的格式模型。这可以进一步推广到更多“混合”格式模型——只要没有歧义。

像这样:

with
  source_tab (time_in, time_out) as (
    select '12/2/21 7:03 AM'   , '12/2/21 4:24 PM'    from dual union all
    select '12-02-2021 7:01 AM', '15-02-2021 4:00 PM' from dual 
  )
select to_date(time_in , case when time_in like '%/%' then 'dd/mm/rr hh:mi AM'
                              else 'dd-mm-yyyy hh:mi AM' end) as time_in,
       to_date(time_out, case when time_in like '%/%' then 'dd/mm/rr hh:mi AM'
                              else 'dd-mm-yyyy hh:mi AM' end) as time_out
from   source_tab
;

TIME_IN             TIME_OUT           
------------------- -------------------
2021-02-12 07:03:00 2021-02-12 16:24:00
2021-02-12 07:01:00 2021-02-15 16:00:00

(当然,上面显示的输出取决于我显示日期时间的会话设置。)

如果你有很多这样的表,或者如果你必须针对这些数据编写许多类似的查询,你可以编写一个 PL/SQL 函数来将“string”转换为“date”(其中“string”可以使用各种格式模型),但它应该遵循相同的总体思路。在“正确答案”中尝试不同模型直到不抛出异常的建议似乎不是一个好习惯。

【讨论】:

  • 嗨,我同意你的观点,这两种方法都有效。谢谢你的替代品。我想问你另一个问题。你能检查一下oracle sql中的时间,看看它是在上午还是下午?
  • @BasudevSingh - “在 Oracle 中的时间”是什么意思? Oracle 不支持time 数据类型,它只有date,它总是同时具有“日期”和“时间”组件。 (Oracle 有一个 undocumented time 数据类型,如果你不知道这是最好的。)你的意思是,如果你有一个 date 值,如何查看它的时间组件是上午还是下午?这很简单,将to_char() 与格式模型'AM' 一起使用(就像那样!)试试看是否是您需要的。
【解决方案3】:

使用您拥有的两种日期格式,您无需使用CASE 语句或多种格式,只需使用string-to-date conversion rules,其中:

  • 您可以在日期字符串中使用任何非字母数字字符来匹配格式字符串中的标点符号。
  • 'RR' 也匹配 'RRRR'

这给了你:

SELECT TO_CHAR( TO_DATE( time_in, 'DD-MM-RR HH12:MI AM' ), 'HH24:MI' ) AS time_in,
       TO_CHAR( TO_DATE( time_out, 'DD-MM-RR HH12:MI AM' ), 'HH24:MI' ) AS time_out
FROM   table_name;

其中,对于样本数据:

CREATE TABLE table_name ( time_in, time_out ) AS
SELECT '12/2/21 7:03 AM', '12/2/21 4:24 PM' FROM DUAL UNION ALL
SELECT '15-02-2021 7:01 AM', '15-02-2021 04:00 PM' FROM DUAL

输出:

TIME_IN |暂停 :-------- | :-------- 07:03 | 16:24 07:01 | 16:00

db小提琴here

【讨论】:

    【解决方案4】:

    我想排除日期并仅用时间填充列

    问题是:为此使用什么数据类型?让我们一步一步来看看......

    首先,将日期时间存储为字符串是个坏主意。在您的情况下,您必须考虑至少两种不同的格式。你能保证所有字符串都是两种格式之一的有效日期时间吗?

    第一步应该是将字符串转换为真实的日期时间。使用 COALESCETO_DATEON CONVERSION ERROR 子句来获取日期时间。两种格式都不匹配的字符串会导致 null:

    with mytable as (select '12/2/21 7:03 am' as time_in, '15-02-2021 04:00 pm' as time_out from dual)
    select 
      coalesce
      (
        to_date(time_in default null on conversion error, 'dd.mm.rr hh:mi am'), 
        to_date(time_in default null on conversion error, 'dd-mm-yyyy hh:mi am')
      ) as start_time,
      coalesce
      (
        to_date(time_out default null on conversion error, 'dd.mm.rr hh:mi am'), 
        to_date(time_out default null on conversion error, 'dd-mm-yyyy hh:mi am')
      ) as end_time
    from mytable;
    

    然后,Oracle 没有TIME 数据类型(就像它们也没有单独的DATE 数据类型一样),它们只有一个日期时间类型,他们不恰当地称为DATE。所以,你不能真正摆脱使用这种类型的日期部分。

    三个选项:

    1. 改为存储一个字符串(通过在上述表达式上应用to_char(___, 'hh24:mi'))。
    2. 使用INTERVAL 存储时间(例如,通过在上述表达式上应用cast(___ as timestamp)- trunc(___)。我们需要从DATETIMESTRING 的转换才能获得INTERVAL 而不仅仅是一小部分减法的数字。)
    3. 将日期部分替换为常数,例如 1990-01-01(通过将其添加到我们刚刚获得的时间间隔中,例如 date '1990-01-01' + (cast(___ as timestamp)- trunc(___)))。

    以下演示显示了第二个选项,这是我的偏好:https://dbfiddle.uk/?rdbms=oracle_18&fiddle=189496f4b97e87e57ed51a20f3696134

    【讨论】:

    • 您无需转换为时间戳即可获取间隔。只需使用(date_column - TRUNC( date_column)) DAY TO SECONDdb<>fiddle 明确声明您希望将减法作为间隔(而不是默认数字类型)
    • @MT0:哇,谢谢。我不知道。在将DAY TO SECOND 用作运算符之前,我什至从未见过这种语法。我也无法在 Oracle 文档中找到它。你知道这是否记录在某处吗?我刚试过select 0.5 day to second from dual; 并得到一个语法错误。我觉得这很奇怪。
    • 它在 this AskTom question 中被提及,我确信它在 Oracle 文档中的某处被提及,但很难找到(我无法再次找到它)。
    • @MT0:谢谢。这是一个有趣的链接,表明 Oracle 开发人员不知何故为了让这个工作而蒙混过关,因为他们没有将表达式从数字转换为区间,而是将表达式的操作数强制转换为另一种表达式结果类型。在我看来,这不是处理这个问题的正确方法,但它确实有效:-)
    猜你喜欢
    • 2020-12-20
    • 1970-01-01
    • 2015-12-13
    • 2011-11-28
    • 2012-10-03
    • 1970-01-01
    • 2021-03-18
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多