问题描述
我有一个Oracle数据库表,其中包含一个名为分类的字段,即VARCHAR。 VARCHAR是CSV(使用半冒号)。示例:
;CHR;
;OTR;CHR;ROW;
;CHA;ROW;
;OTR;ROW;
我想拉出所有行,并且CSV中的行仅具有与其他行不同的值。只要行具有新的不同值,就可以具有先前找到的值。
例如,上述数据集中的数据为:
;CHR;
;OTR;CHR;ROW;
;CHA;ROW;
如果我只是这样做:
Select DISTINCT Classification from Table1
由于总体VARCHAR不同,我得到的行重叠了不同的值。
我可以使用以下方法获取所有不同的值:
select LISTAGG(val,',') WITHIN GROUP ( ORDER BY val ) as final
FROM
(
select distinct trim(regexp_substr("Classification",'[^;]+',1,level) ) as val
from Table1
connect by regexp_substr("Classification",'[^,]+',level) is not null
ORDER BY val
)
给我的
FINAL
CHA,CHR,OTR,ROW
但是无法建立链接以为每个唯一值提取一条记录
SQL可能吗?
编辑:这是由大公司创建的数据库,我的公司购买了该产品。现在,我负责为BI挖掘后端数据库的数据,并且绝对无法控制数据库结构。
没有冒犯,但我在研究的问题中看到许多答案,指出“做更好的数据库设计/规范化”,虽然我同意大多数,但我无法控制数据库,并且由于以下原因而寻求SO帮助:这,而不是对不良数据库设计的嘲笑。
如果我冒犯了任何人,我深表歉意
没有父母/子女关系。我看不到对象层,但是我假定在传播给客户端之前,这些值在对象层中已更改,因为在实际数据库中没有指向它们的链接
说明:
我看到两种解决方法:
1:一个基于VARCHAR CSV(分类)中新的唯一值提取1行的select语句
2:使用我的select语句循环遍历并在VARCHAR CSV(Classification)中拉出包含该值的一行
谢谢大家的投入。我赞成那些为我工作的人。最后,我将使用我开发的代码,因为我可以轻松地为分析师希望的结果操纵输出(到CSV)。
解决方法
这是一种处理方法:
- 将行号分配给原始CSV数据
- 拆分CSV->行
- 现在分配拆分的CSV值行号,并按照第一步中的CSV顺序进行排序
- 返回上一步的行号= 1的所有行
- 返回CSV的唯一列表
例如:
with tab as (
select ';CHR;' str from dual union all
select ';OTR;CHR;ROW;' str from dual union all
select ';CHA;ROW;' str from dual union all
select ';OTR;ROW;' str from dual
),ranks as (
select row_number() over ( order by str ) rn,tab.* from tab
),rws as (
select trim ( regexp_substr(str,'[^;]+',1,level ) ) as val,rn,str
from ranks
connect by regexp_substr ( str,level ) is not null
and prior rn = rn
and prior sys_guid () is not null
),rns as (
select row_number () over (
partition by val
order by rn
) val_rn,r.*
from rws r
)
select distinct str
from rns
where val_rn = 1;
STR
;CHA;ROW;
;OTR;CHR;ROW;
;CHR;
,
这是一个临时解决方案建议,如果通用答案产生了次优的性能并且某些限制已得到满足:
- 所有按键的长度都是固定的
- 键的最大数量是已知的
除了解析CSV字符串外,您还可以使用此查询(为更长的字符串再添加UNION ALL
)
with tab as (
select ';CHR;' str from dual union all
select ';OTR;CHR;ROW;' str from dual union all
select ';CHA;ROW;' str from dual union all
select ';OTR;ROW;' str from dual
),tab2 as (
select str,substr(str,2,3) val from tab union all
select str,6,3) val from tab where substr(str,3) is not null union all
select str,10,3) is not null)
select * from tab2;
结果
STR VAL
------------- ------------
;CHR; CHR
;OTR;CHR;ROW; OTR
;CHA;ROW; CHA
;OTR;ROW; OTR
;OTR;CHR;ROW; CHR
;CHA;ROW; ROW
;OTR;ROW; ROW
;OTR;CHR;ROW; ROW
现在,您只需查找每个键的首次出现,并获得所有与第一次出现的不同的字符串。
我正在重用Chris Saxon解决方案中的方法
with tab as (
select ';CHR;' str from dual union all
select ';OTR;CHR;ROW;' str from dual union all
select ';CHA;ROW;' str from dual union all
select ';OTR;ROW;' str from dual
),3) is not null),tab3 as (
select STR,VAL,row_number() over (partition by val order by str) rn
from tab2)
select distinct str
from tab3
where rn = 1
,
您已经非常接近,因为您已经获得了不同值的列表。您可以使用该列表查找包含该唯一值的行,而不是将它们与LISTAGG组合在一起。下面是两个单独的查询,它们将为每个唯一值返回一个分类。您可以对它们进行尝试,然后根据表中的数据查看哪种方法效果更好。
查询选项1
WITH
table1 (classification)
AS
(SELECT ';CHR;' FROM DUAL
UNION ALL
SELECT ';OTR;CHR;ROW;' FROM DUAL
UNION ALL
SELECT ';CHA;ROW;' FROM DUAL
UNION ALL
SELECT ';OTR;ROW;' FROM DUAL),dist_vals (val)
AS
( SELECT DISTINCT TRIM (REGEXP_SUBSTR (classification,LEVEL)) AS val
FROM Table1
CONNECT BY LEVEL < REGEXP_COUNT (classification,';'))
SELECT val,classification
FROM (SELECT dv.val,t.classification,ROW_NUMBER () OVER (PARTITION BY dv.val ORDER BY t.classification) AS occurence
FROM dist_vals dv,table1 t
WHERE t.classification LIKE '%;' || dv.val || ';%')
WHERE occurence = 1;
查询选项2
WITH
table1 (classification)
AS
(SELECT ';CHR;' FROM DUAL
UNION ALL
SELECT ';OTR;CHR;ROW;' FROM DUAL
UNION ALL
SELECT ';CHA;ROW;' FROM DUAL
UNION ALL
SELECT ';OTR;ROW;' FROM DUAL),';'))
SELECT dv.val,(SELECT classification
FROM table1
WHERE classification LIKE '%;' || dv.val || ';%' AND ROWNUM = 1)
FROM dist_vals dv;
,
我以这种方式解决了这个问题,并且运行速度很快(即使添加了我对其他表的所有联接)。将尽我所能测试其他答案并决定最佳答案(如果其他方法可以正常工作,则其他方法比我的看起来更好,因为我宁愿不使用dbms_output)
DECLARE
v_search_string varchar2(4000);
v_classification varchar2(4000);
BEGIN
select LISTAGG(val,',') WITHIN GROUP ( ORDER BY val ) as final
INTO v_search_string
FROM
(
select distinct trim(regexp_substr("Classification",level) ) as val
from mytable
connect by regexp_substr("Classification",'[^,]+',level) is not null
ORDER BY val
);
FOR i IN
(SELECT trim(regexp_substr(v_search_string,LEVEL)) l
FROM dual
CONNECT BY LEVEL <= regexp_count(v_search_string,')+1
)
LOOP
SELECT "Classification"
INTO v_classification
FROM mytable
WHERE "Classification" LIKE '%' || i.l || '%'
FETCH NEXT 1 ROWS ONLY;
dbms_output.put_line(v_classification);
END LOOP;
END;