【问题标题】:Oracle: Split delimited string and pick date greater than the input dateOracle:拆分分隔字符串并选择大于输入日期的日期
【发布时间】:2019-01-18 21:40:02
【问题描述】:

我在下面的列中有一个逗号分隔的字符串作为值。

'2015/04/01 11 GG, 2015/08/03 78 KK, 2012/12/12 44 TT, 2015/09/01 77 YY, 2015/09/01 88 ZZ'

其中,每个字符串由三列组合而成,并由空格分隔。

所以这里的要求是选择更大更接近给定输入日期的日期 并获得第二列。

示例: 如果输入日期是 01-AUG-2015,那么我的输出应该是 78,因为它更接近一个。 如果没有日期大于输入日期,则输出应为空。

【问题讨论】:

  • 有了这样的要求,您应该将数据存储在多行多列中。
  • 是的,我完全理解这一点。这是一个现有系统,目前无法更改。

标签: sql regex oracle csv date


【解决方案1】:

这是一个复杂的要求,正如 Gordon Linoff 所说,如果数据已经正确传播,解决起来会简单得多。

这是一种方法:

  • 首先使用带有REGEXP_SUBSTRCONNECT BY 的递归CTE 将字符串拆分为行,使用逗号分隔符
  • 然后将每一行分成 3 列,再次使用 REGEXP_SUBSTR 和空格分隔符
  • 然后使用oracle窗口函数DENSE_RANKKEEP隔离相关行

假设数据来自表my_table 中的列str

WITH 
    cte0 AS (
        SELECT TRIM(REGEXP_SUBSTR(str, '[^,]+', 1, LEVEL)) str
        FROM my_table
        CONNECT BY INSTR(str, ',', 1, LEVEL - 1) > 0
    ),
    cte1 AS (
        SELECT 
            TO_DATE(REGEXP_SUBSTR(str, '\S+', 1, 1), 'yyyy-mm-dd') dt,
            REGEXP_SUBSTR(str, '\S+', 1, 2) val1,
            REGEXP_SUBSTR(str, '\S+', 1, 3) val2
        FROM cte0
        ORDER BY 1 DESC
    )
SELECT 
    MIN(dt)   keep (dense_rank first order by dt) as dt,
    MIN(val1) keep (dense_rank first order by dt) as val1,
    MIN(val2) keep (dense_rank first order by dt) as val2
FROM cte1
WHERE dt > TO_DATE(?, 'yyyy-mm-dd')

...? 是输入日期。

*db<>fiddle here

 with 
     data as  (
         SELECT
             '2015/04/01 11 GG, 2015/08/03 78 KK, 2012/12/12 44 TT, 2015/09/01 77 YY, 2015/09/01 88 ZZ' str
         FROM DUAL
     ),
     cte0 AS (
         SELECT TRIM(REGEXP_SUBSTR(str, '[^,]+', 1, LEVEL)) str
         FROM data
         CONNECT BY INSTR(str, ',', 1, LEVEL - 1) > 0
     ),
     cte1 AS (
         SELECT 
             TO_DATE(REGEXP_SUBSTR(str, '\S+', 1, 1), 'yyyy-mm-dd') dt,
             REGEXP_SUBSTR(str, '\S+', 1, 2) val1,
             REGEXP_SUBSTR(str, '\S+', 1, 3) val2
         FROM cte0
         ORDER BY 1 DESC
     )
 SELECT 
     min(dt) keep (dense_rank first order by dt) as dt,
     min(val1) keep (dense_rank first order by dt) as val1,
     min(val2) keep (dense_rank first order by dt) as val2
 FROM cte1
 WHERE dt > TO_DATE('2015-08-01', 'yyyy-mm-dd')


-------------------------
 DT        | VAL1 | VAL2
 :-------- | :--- | :---
 03-AUG-15 | 78   | KK  

【讨论】:

    【解决方案2】:

    这是一个选项,基于您提供的示例数据。

    SQL> with test (col) as
      2    (select '2015/04/01 11 GG, 2015/08/03 78 KK, 2012/12/12 44 TT, 2015/09/01 77 YY, 2015/09/01 88 ZZ' from dual),
      3  t_comma as
      4    (select trim(regexp_substr(col, '[^,]+', 1, level)) col2
      5     from test
      6     connect by level <= regexp_count(col, ',') + 1
      7    ),
      8  t_diff as
      9    (select col2,
     10         substr(col2, 1, 10) c_date,
     11         regexp_substr(col2, '\d+', 1, 4) c_num,
     12         regexp_substr(col2, '\w+$') c_let ,
     13         --
     14         abs(to_date(substr(col2, 1, 10), 'yyyy/mm/dd') -
     15             to_date('&&:par_date', 'yyyy/mm/dd')) diff_days,
     16         --
     17         row_number() over (order by abs(to_date(substr(col2, 1, 10), 'yyyy/mm/dd') -
     18                                         to_date('&&par_date', 'yyyy/mm/dd'))) rn
     19     from t_comma
     20    )
     21  select c_num
     22  from t_diff
     23  where rn = 1;
    Enter value for par_date: 2015-08-01
    
    C_NUM
    --------------------------------------------------------------------------------
    78
    
    SQL>
    

    它有什么作用?

    • TEST 是您的示例表格(由 CTE 表示)
    • T_COMMA 将逗号分隔的值字符串拆分为行(因此您可以从样本数据中获得 5 行)
    • T_DIFF 提取子字符串的每一部分(即每一行),计算样本日期和参数化日期之间的差异,按差异的绝对值对它们进行排名 - RN = 1 是“最接近”的日期
    • 最后的 SELECT 只返回那个“最接近”的值

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-01-06
      • 1970-01-01
      • 2012-08-30
      • 1970-01-01
      相关资源
      最近更新 更多