Sql 在Oracle中将逗号分隔的值转换为行

Sql 在Oracle中将逗号分隔的值转换为行,sql,oracle,oracle11g,Sql,Oracle,Oracle11g,我的数据库中有一个名为student_info的表。有多个列,其中两列存储逗号分隔的值 class_id student marks ---------------------------------------------- 1 tom,jam,tim 55,65,75 2 rim,gum,ram 33,66,77 我希望输出如下 class_id

我的数据库中有一个名为student_info的表。有多个列,其中两列存储逗号分隔的值

class_id     student                marks  

----------------------------------------------
1             tom,jam,tim            55,65,75
2             rim,gum,ram            33,66,77
我希望输出如下

class_id      student       marks

------------------------------------------------
1              tom            55
1              tom            65
1              tom            75
1              jam            55
1              jam            65
1              jam            75
1              tim            55
1              tim            65
1              tim            75
我的问题如下

SELECT student_id,TRIM(REGEXP_SUBSTR(student, '[^,]+', 1, level)) student_name
FROM STUDENT_INFO

CONNECT BY level <= REGEXP_COUNT(student, '[^,]+')
    AND PRIOR student = student AND marks = marks
    AND PRIOR DBMS_RANDOM.VALUE IS NOT NULL
如何实现所需的输出?有什么建议吗?

规范化表要比找到将csv转换为行的复杂方法容易得多

在这种情况下,您可以使用:

with t(class_id, student, marks) as (
    select 1, 'tom,jam,tim', '55,65,75' from dual union all
    select 2, 'rim,gum,ram', '33,66,77' from dual
)
select
    t.class_id,
    regexp_substr(student,'[^,]+',1,x.column_value) student,
    regexp_substr(marks,'[^,]+',1,y.column_value) marks
from t, table(
    cast(
        multiset(
            select level
            from dual
            connect by level <= regexp_count(t.student,',') + 1
        ) as sys.odcinumberlist
    )
)x,table(
    cast(
        multiset(
            select level
            from dual
            connect by level <= regexp_count(t.marks,',') + 1
        ) as sys.odcinumberlist
    )
)y
分层查询:

使用相关分层查询查找子字符串一次,然后应用叉积将其联接:

SELECT class_id,
       s.COLUMN_VALUE AS student,
       m.COLUMN_VALUE AS mark
FROM   table_name t,
       TABLE(
         CAST(
           MULTISET(
             SELECT REGEXP_SUBSTR( t.students, '[^,]+', 1, LEVEL )
             FROM   DUAL
             CONNECT BY LEVEL <= REGEXP_COUNT( t.students, '[^,]+' )
           ) AS SYS.ODCIVARCHAR2LIST
         )
       ) s,
       TABLE(
         CAST(
           MULTISET(
             SELECT TO_NUMBER( REGEXP_SUBSTR( t.marks, '[^,]+', 1, LEVEL ) )
             FROM   DUAL
             CONNECT BY LEVEL <= REGEXP_COUNT( t.marks, '[^,]+' )
           ) AS SYS.ODCIVARCHAR2LIST
         )
       ) m
递归子查询分解:

使用ora:tokenizer

  with t(class_id, student, marks) as (
        select 1, 'tom,jam,tim', '55,65,75' from dual union all
        select 2, 'rim,gum,ram', '33,66,77' from dual
    )
    select class_id,new_student,new_marks from t
     ,xmltable('for $i in ora:tokenize($students,",") return $i' passing student as "students" columns new_student varchar2(20) path '.')
     ,xmltable('for $j in ora:tokenize($marks,",") return $j' passing marks as "marks" columns new_marks varchar2(20) path '.')
试试这个

 with temp as
(
    select 1 class_id     , 'tom,jam,tim' student, '55,65,75' marks    from dual
    union all
    select 2 class_id     , 'rim,gum,ram' student, '33,66,77' marks    from dual
)
select distinct
  t.class_id,
  trim(regexp_substr(t.student, '[^,]+', 1, students.column_value))  as students,
  trim(regexp_substr(t.marks, '[^,]+', 1, marks.column_value))  as marks
from 
  temp t,
  table(cast(multiset(select level from dual connect by  level <= length (regexp_replace(t.student, '[^,]+'))  + 1) as sys.OdciNumberList)) students,
  table(cast(multiset(select level from dual connect by  level <= length (regexp_replace(t.marks, '[^,]+'))  + 1) as sys.OdciNumberList)) marks
order by class_id;

似乎很容易再次将同样的技术应用到您已经能够获得的输出上。只需将其应用于“标记”列。但是,你确定这个要求是正确的吗?得到所有可能的学生组合后,马克显得异常古怪;你确定你不应该把第一个学生和第一个分数配对,第二个学生和第二个分数配对,等等。?另外-为什么在当前查询中需要and marks=标记?它是做什么的?解释结果。这是一个糟糕的数据库设计,所以你最好的选择是改变它,而不是编写必须应付的查询。
CLASS_ID STUDENT MARKS
-------- ------- -----
       1 tom        55
       1 tom        65
       1 tom        75
       1 jam        55
       1 jam        65
       1 jam        75
       1 tim        55
       1 tim        65
       1 tim        75
       2 rim        33
       2 rim        66
       2 rim        77
       2 gum        33
       2 gum        66
       2 gum        77
       2 ram        33
       2 ram        66
       2 ram        77
WITH cte ( class_id, student, marks, s, m, smax, mmax ) AS (
  SELECT class_id,
         student,
         marks,
         1,
         1,
         REGEXP_COUNT( student, '[^,]+' ),
         REGEXP_COUNT( marks, '[^,]+' )
   FROM  table_name
UNION ALL
  SELECT class_id,
         student,
         marks,
         CASE WHEN m = mmax THEN s + 1 ELSE s END,
         CASE WHEN m = mmax THEN 1 ELSE m + 1 END,
         smax,
         mmax
   FROM  cte
   WHERE m < mmax OR s < smax
)
SELECT class_id,
       REGEXP_SUBSTR( student, '[^,]+', 1, s ) AS student,
       TO_NUMBER( REGEXP_SUBSTR( mark, '[^,]+', 1, m ) ) AS mark
FROM   cte;
  with t(class_id, student, marks) as (
        select 1, 'tom,jam,tim', '55,65,75' from dual union all
        select 2, 'rim,gum,ram', '33,66,77' from dual
    )
    select class_id,new_student,new_marks from t
     ,xmltable('for $i in ora:tokenize($students,",") return $i' passing student as "students" columns new_student varchar2(20) path '.')
     ,xmltable('for $j in ora:tokenize($marks,",") return $j' passing marks as "marks" columns new_marks varchar2(20) path '.')
 with temp as
(
    select 1 class_id     , 'tom,jam,tim' student, '55,65,75' marks    from dual
    union all
    select 2 class_id     , 'rim,gum,ram' student, '33,66,77' marks    from dual
)
select distinct
  t.class_id,
  trim(regexp_substr(t.student, '[^,]+', 1, students.column_value))  as students,
  trim(regexp_substr(t.marks, '[^,]+', 1, marks.column_value))  as marks
from 
  temp t,
  table(cast(multiset(select level from dual connect by  level <= length (regexp_replace(t.student, '[^,]+'))  + 1) as sys.OdciNumberList)) students,
  table(cast(multiset(select level from dual connect by  level <= length (regexp_replace(t.marks, '[^,]+'))  + 1) as sys.OdciNumberList)) marks
order by class_id;