【问题标题】:How to parse varchar2 and make it into multiple rows?如何解析 varchar2 并将其分成多行?
【发布时间】:2016-04-08 14:21:31
【问题描述】:

这是这个问题的后续问题:How do I need to change my sql to get what I want in this case?

我的两张表如下:

      Table 1
id  id2    date
1    2   2015-01-10
2    5   2015-06-13
3    9   2015-09-05
4    10  2015-02-11
5    26  2015-01-10
6    65  2015-01-25

      Table 2
id  id2    data(varchar2)
1    2       A
2    5       A
3    9       A
4    10      B
5    26      B
6    65      B

Table 2 中的数据是 varchar2 类型,其中包含N 数字,由, 分隔,其中N 可能依赖于id2。例如,A 可能是这样的:

1.0,1.1,1.2,1.3,1.4,1.5,2.6,2.7,2.8,2.9, ...(ommitted)..., 9.5,9.9

我想编写一个查询,返回 Table 2 中唯一的 data,它在 Table 1 中具有最大日期,对于上表来说如下:

id2    date         number
2   2015-01-10        1.0
2   2015-01-10        1.1
2   2015-01-10        1.2
2   2015-01-10        1.3
      ...
2   2015-01-10        9.5
2   2015-01-10        9.9
10  2015-02-11        ***
10  2015-02-11        ***
      ...
10  2015-02-11        ***

每个唯一的id2 在查询输出中出现N 次。

根据 Fuzzy 的回答,我可以得到唯一的 id2s,如下所示:

select * from (
    select 
        t2.id2, t1.date, t2.data, 
        row_number() over (partition by t2.data order by t1.date desc) rn
    from table1 t1
    join table2 t2 on t1.id2 = t2.id2
) t where rn = 1;

但我不知道如何从那里继续。非常感谢。

【问题讨论】:

  • 80是固定值还是可以逐行变化?
  • @Aleksej 我已更新问题描述以使其更清晰。
  • 问题是这种可怕的桌子设计。没关系,只要data 只包含一些字符串给你。但这种情况并非如此;您对存储在data 中的子字符串感兴趣。但是,如果您对单独的数据感兴趣,请单独存储!不要编写查询来解决这个混乱;更改您的数据库。
  • 我第二个@ThorstenKettner。您的数据列违反了数据库规范化的[原子性原则](en.wikipedia.org/wiki/First_normal_form#Atomicity)。与其编写一个聪明的查询,您的解决方案实际上应该是从您的 data 列中提取所有单独的值,并将它们作为单独的行存储在不同的表中。
  • @ThorstenKettner 和 DanK 尽管我对数据库设计还很陌生,但我同意你们俩的观点。但我不负责设计表格。实际上,我什至无法要求他们对当前的设计进行任何更改。我被赋予了这项任务并且正在努力完成它:-(再次感谢你们。

标签: sql database oracle


【解决方案1】:

您可以使用管道功能:

CREATE or replace TYPE test_type AS TABLE OF varchar2(40)

CREATE or replace FUNCTION test_func (d VARCHAR2)
   RETURN test_type
   PIPELINED
IS
BEGIN
   FOR C1 IN (    SELECT REGEXP_SUBSTR (d, '[^,]+', 1, LEVEL) x
                    FROM DUAL
              CONNECT BY REGEXP_SUBSTR (d, '[^,]+', 1, LEVEL) IS NOT NULL)
   LOOP
      PIPE ROW (c1.x);
   END LOOP;
END;
/

WITH test
     AS ( your_query_here )
SELECT x.id2, x.mydate, y.*
  FROM test x, TABLE (test_func (x.d)) y

我在这里使用 mydate 代替 date(保留字)和 d 代替数据。示例:

WITH test
     AS (SELECT 2 AS id2, SYSDATE AS mydate, '1.0,1.1,1.2,11,1.4,1.5,2.6,2.7,2.8,2.9,44,55' AS d FROM DUAL
         UNION ALL
         SELECT 3 AS id2, SYSDATE + 1 AS mydate, '19.5,19.9,11.5,11.1,21.2,33,1.4,1.5,2.6,2.7,2.8,2.9' AS d
           FROM DUAL
         UNION ALL
         SELECT 4 AS id2, SYSDATE + 1 AS mydate, '9.5,9.9,1.5,1.1,1.2,66,1.4,1.5,2.6,2.7,2.8,2.9' AS d
           FROM DUAL)
SELECT x.id2, x.mydate, y.*
  FROM test x, TABLE (test_func (x.d)) y

部分链接:

How to split comma separated string and pass to IN clause of select statement

Using Pipelined and Parallel Table Functions

【讨论】:

    【解决方案2】:

    正如其他人所说,如果桌子设计得当,您就不必这样做。如果这是一个经常使用的对象,最好解决真正的问题。但是,如果这只是一次性的事情,您可以使用递归查询。这在 Teradata 中有效,Oracle 可能有类似的类似物。

    WITH RECURSIVE processDelimited(ID2,dataVarchar2,parsedElement,indexElement) AS (
        SELECT 
         ID2
        ,dataVarchar2
        ,strtok(dataVarchar2,',',1)
        ,0
        FROM tableWithDelimitedColumns
        UNION ALL
        SELECT 
         ID2
        ,dataVarchar2
        ,strtok(dataVarchar2,',',indexElement + 1)    
        ,indexElement + 1
        FROM processDelimited
        WHERE indexElement < 120
        )
    
    SELECT 
     processDelimited.ID2
    ,parsedElement
    ,indexElement
    FROM processDelimited
    INNER JOIN (
        SELECT 
         ID2
        ,DATE_COL
        FROM table1
        QUALIFY row_number() OVER(PARTITION BY ID2 ORDER BY DATE_COL DESC) = 1) AS id2MaxDate
        ON processDelimited.ID2 = id2MaxDate.ID2
    WHERE parsedElement IS NOT NULL
    ORDER BY processDelimited.ID2
    

    基本上,您只需从空字符串开始,然后循环添加行。

    使用的 TD 函数只是从分隔字段中提取索引。例如。

    strtok('1,2,3,4,5',',',2) = 2
    

    当然,这在计算上非常昂贵,因此对于大表来说并不是很好。

    【讨论】:

      【解决方案3】:

      Oracle 设置

      CREATE TYPE VARCHAR2_TABLE AS TABLE OF VARCHAR2(4000);
      /
      
      CREATE OR REPLACE FUNCTION split_String(
        i_str    IN  VARCHAR2,
        i_delim  IN  VARCHAR2 DEFAULT ','
      ) RETURN VARCHAR2_TABLE DETERMINISTIC
      AS
        p_result       VARCHAR2_TABLE := VARCHAR2_TABLE();
        p_start        NUMBER(5) := 1;
        p_end          NUMBER(5);
        c_len CONSTANT NUMBER(5) := LENGTH( i_str );
        c_ld  CONSTANT NUMBER(5) := LENGTH( i_delim );
      BEGIN
        IF c_len > 0 THEN
          p_end := INSTR( i_str, i_delim, p_start );
          WHILE p_end > 0 LOOP
            p_result.EXTEND;
            p_result( p_result.COUNT ) := SUBSTR( i_str, p_start, p_end - p_start );
            p_start := p_end + c_ld;
            p_end := INSTR( i_str, i_delim, p_start );
          END LOOP;
          IF p_start <= c_len + 1 THEN
            p_result.EXTEND;
            p_result( p_result.COUNT ) := SUBSTR( i_str, p_start, c_len - p_start + 1 );
          END IF;
        END IF;
        RETURN p_result;
      END;
      /
      
      CREATE TABLE Table1 ( id, id2, "date" ) AS
      SELECT 1,    2,   DATE '2015-01-10' FROM DUAL UNION ALL
      SELECT 2,    5,   DATE '2015-06-13' FROM DUAL UNION ALL
      SELECT 3,    9,   DATE '2015-09-05' FROM DUAL UNION ALL
      SELECT 4,    10,  DATE '2015-02-11' FROM DUAL UNION ALL
      SELECT 5,    26,  DATE '2015-01-10' FROM DUAL UNION ALL
      SELECT 6,    65,  DATE '2015-01-25' FROM DUAL;
      
      CREATE TABLE Table2 ( id, id2, data ) AS
      SELECT 1,    2,       '1.0,1.1,1.2' FROM DUAL UNION ALL
      SELECT 2,    5,       '1.0,1.1,1.2' FROM DUAL UNION ALL
      SELECT 3,    9,       '1.0,1.1,1.2' FROM DUAL UNION ALL
      SELECT 4,    10,      '3.2,3.3,4.5,6.7' FROM DUAL UNION ALL
      SELECT 5,    26,      '3.2,3.3,4.5,6.7' FROM DUAL UNION ALL
      SELECT 6,    65,      '3.2,3.3,4.5,6.7' FROM DUAL;
      

      查询

      SELECT t.id,
             t.id2,
             t."date",
             d.column_value AS data
      FROM   (
        SELECT MAX( t1.id  ) KEEP ( DENSE_RANK LAST ORDER BY t1."date" ) AS id,
               MAX( t1.id2 ) KEEP ( DENSE_RANK LAST ORDER BY t1."date" ) AS id2,
               MAX( t1."date" ) AS "date",
               t2.data
        FROM   Table1 t1
               INNER JOIN
               Table2 t2
               ON ( t1.id = t2.id AND t1.id2 = t2.id2 )
        GROUP BY t2.data
      ) t,
      TABLE( SPLIT_STRING( t.data, ',' ) ) d;
      

      输出

              ID        ID2 date                DATA
      ---------- ---------- ------------------- ----
               3          9 2015-09-05 00:00:00 1.0  
               3          9 2015-09-05 00:00:00 1.1  
               3          9 2015-09-05 00:00:00 1.2  
               4         10 2015-02-11 00:00:00 3.2  
               4         10 2015-02-11 00:00:00 3.3  
               4         10 2015-02-11 00:00:00 4.5  
               4         10 2015-02-11 00:00:00 6.7  
      

      【讨论】:

        猜你喜欢
        • 2016-02-28
        • 2012-10-11
        • 1970-01-01
        • 1970-01-01
        • 2020-09-15
        • 2019-05-20
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多