Sql 在Oracle中将逗号分隔的值转换为行
我的数据库中有一个名为student_info的表。有多个列,其中两列存储逗号分隔的值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
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;