SELECT 行,其中包含来自包含 CSV 的 VARCHAR 的新 DISTINCT

SELECT rows with a new DISTINCT from a VARCHAR with CSV in it

我有一个 Oracle 数据库 table,其中有一个名为 Classification 的字段,它是 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", '[^,]+', 1, level) is not null
  ORDER BY val
 )

哪个给我

FINAL
CHA,CHR,OTR,ROW

但我无法让 link 为每个唯一值提取一条记录

SQL可以吗?

编辑:这是一个由一家大公司创建的数据库,我购买了该产品。现在我负责BI后台数据库的数据挖掘,完全无法控制数据库结构。

没有冒犯,但我在我研究过的问题中看到很多答案 'Do better database design/normalization' 虽然我同意大多数我已经阅读过无法控制数据库并且因此寻求 SO 帮助解决问题, 不是嘲笑糟糕的数据库设计。

如有冒犯,请见谅

没有parent/child关系。我看不到对象层,但我假设这些值在传播到客户端之前已在对象层中更改,因为在实际数据库中没有 link

澄清:

我看到有 2 种方法可以解决这个问题:

1:一个 select 语句根据 VARCHAR CSV(分类)

中的新唯一值提取 1 行

2:使用我的 select 语句遍历并在 VARCHAR CSV(Classification)

中提取包含该值的一行

感谢大家的投入。我赞成那些对我有用的。最后,我将使用我开发的那个,因为我可以轻松地按照分析师的意愿操作输出(到 csv)。

这是一种处理方法:

  • 为原始 CSV 数据分配行号
  • 拆分 CSV -> 行
  • 现在分配拆分的 CSV 值行号,按第一步中的 CSV 顺序排序
  • Return 上一步的行号 = 1 的任何行
  • Return 不同的 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, '[^;]+', 1, 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;      

您已经非常接近了,因为您已经获得了不同值的列表。您可以使用该列表来查找包含该唯一值的行,而不是将它们与 LISTAGG 组合。下面是两个单独的查询,它们将为每个唯一值 return 分类。您可以同时尝试它们,并根据 table.

中的数据查看哪个表现更好

查询选项 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,
                                                  '[^;]+',
                                                  1,
                                                  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),
    dist_vals (val)
    AS
        (    SELECT DISTINCT TRIM (REGEXP_SUBSTR (classification,
                                                  '[^;]+',
                                                  1,
                                                  LEVEL))    AS val
               FROM Table1
         CONNECT BY LEVEL < REGEXP_COUNT (classification, ';'))
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",'[^;]+', 1, level) ) as val
  from mytable
   connect by regexp_substr("Classification", '[^,]+', 1, level) is not null
  ORDER BY val
 );
 
 FOR i IN
     (SELECT trim(regexp_substr(v_search_string, '[^,]+', 1, 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;

如果通用答案产生次优性能并且满足一些限制,则这是一个临时解决方案提案:

  • 所有键的长度都是固定的
  • 最大键数已知

除了解析 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, substr(str,6,3) val  from tab where substr(str,6,3) is not null union all
select str, substr(str,10,3) val  from tab where substr(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  

现在您只需要找到每个键的 第一次出现,并获得所有具有第一次出现的不同字符串

我正在重用

的解决方案中的方法
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, substr(str,6,3) val  from tab where substr(str,6,3) is not null union all
select str, substr(str,10,3) val  from tab where substr(str,10,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