【问题标题】:Splitting one row into multiple rows by breaking down the text after fixed length in oracle通过在oracle中将文本分解为固定长度后将一行拆分为多行
【发布时间】:2020-12-12 20:11:44
【问题描述】:

我有一个表,其中包含类似这样的数据:

CREATE TABLE UDA_DATA 
( uda VARCHAR2(20), 
value_text VARCHAR2(4000) 
); 

Insert into UDA_DATA values('Material_ID','PBL000129 PBL000132 PBL000130 PBL000131 PBL000133'); 
Insert into UDA_DATA values('Material_ID','PBL0001341 PBL0001381 PBL0001351 PBL0001361 PBL0001371'); 
   commit;

现在如果我们从这个表中选择数据,它会给出如下结果:

select * from UDA_DATA;

它给出的结果是这样的:

但是我期待这样的事情:

意味着如果字符长度超过 30,它应该将 value_text 分成两行或多行。此外,uda 列的后缀应该是 1,2..n,并且它不应该打破中间的文本。

写了一个递归 CTE 来获得结果:

with rcte (rn, uda, value, chunk_num, value_text) as (
  select rownum,
    uda,
    substr(value_text, 1, 30),
    1,
    substr(value_text, 31)
  from uda_data
  union all
  select rn,
    uda,
    substr(value_text, 1, 30),
    chunk_num + 1,
    substr(value_text, 31)
  from rcte
  where value_text is not null
)
select uda || chunk_num as uda, value
from rcte
order by rn, chunk_num;

结果如下:

在第三行,它打破了不正确的文本,想要这样的结果:

任何帮助将不胜感激。

【问题讨论】:

  • regexp_replace(value_text,'(.{1,30}+)\s','\1,') 在需要的地方用逗号替换空格

标签: sql oracle split


【解决方案1】:

这是一种方法:

  • 将字符串拆分为单词
  • 重新组合最多 30 个字符的单词

uda 的行具有相同的值,因此我还先为每个行指定了一个 row_number,以便您将它们区分开来。

我使用了 match_recogonizegroup together the rows 最多 30 个字符。然后listagg重新组合每个组中的单词。

你也可以使用递归来做到这一点

这给出了:

with ranks as (
  select u.*,
         row_number () over (
           order by uda, value_text
         ) rk
  from   uda_data u
), rws as (
  select rk, uda, rn,
         regexp_substr ( value_text, '[^ ]+', 1, rn ) || ' ' str
  from   ranks, lateral (
    select level rn from dual
    connect by level <= regexp_count ( value_text, ' ' ) + 1
  ) 
), grps as (
  select *
  from   rws
    match_recognize (
      partition by rk
      order by rn
      measures
        match_number() as grp,
        sum ( length ( str ) ) as len
      all rows per match 
      pattern ( thirty+ )
      define 
        thirty as sum ( length ( str ) ) <= 30
    )
)
  select uda || grp, 
         listagg ( str ) 
           within group ( order by rn ) strs 
  from   grps
  group by rk, uda || grp;
  
UDA||GRP        STRS                             
Material_ID1    PBL000129 PBL000132 PBL000130     
Material_ID2    PBL000131 PBL000133               
Material_ID1    PBL0001341 PBL0001381             
Material_ID2    PBL0001351 PBL0001361             
Material_ID3    PBL0001371 

注意:拆分-重组技巧会增加您处理的行数。如果输入字符串很长,这种方法可能会很慢。如果大多数人会分成 2-3 组,这可能没问题 - 尽管显然是对您的数据进行测试!

【讨论】:

  • 嗨,克里斯,非常感谢您的时间和帮助。你的解决方案对我有用。
【解决方案2】:

您的查询的问题是,当您想将字符串拆分为空白时,您甚至没有尝试这样做。您正在使用 substr(value_text, 1, 30) 代替,它不关心空格在字符串中的位置。

您可以使用INSTR 查找拆分位置。下面的查询工作正常,只要里面没有超过 30 个字符的代码(在这种情况下,INSTR 返回 -1,这会导致递归查询中的循环)。您可能需要针对这种情况调整查询。

with rcte (rn, uda, value, chunk_num, value_text) as (
  select rownum,
    uda,
    substr(trim(value_text), 1, instr(substr(value_text || ' ', 1, 31), ' ', -1) - 1),
    1,
    trim(substr(trim(value_text), instr(substr(value_text || ' ', 1, 31), ' ', -1) + 1))
  from uda_data
  union all
  select rn,
    uda,
    substr(value_text, 1, instr(substr(value_text || ' ', 1, 31), ' ', -1) - 1),
    chunk_num + 1,
    trim(substr(value_text, instr(substr(value_text || ' ', 1, 31), ' ', -1) + 1))
  from rcte
  where value_text is not null
)
select uda || rn || '/' || chunk_num as uda, value
from rcte
order by rn, chunk_num;

演示:https://dbfiddle.uk/?rdbms=oracle_18&fiddle=8dfc8e55a12c4666b4bc7bfcaceea2d2

【讨论】:

  • 您好,Thorsten,非常感谢您的宝贵时间和帮助。你的解决方案对我有用。
【解决方案3】:
  1. 用最长宽度的逗号替换空格
select
  u.*
 ,regexp_replace(value_text,'(.{1,30}+)\s','\1,') modified
from uda_data u;

结果:

UDA                  VALUE_TEXT                                                   MODIFIED
-------------------- ------------------------------------------------------------ --------------------------------------------------------------------------------
Material_ID          PBL000129 PBL000132 PBL000130 PBL000131 PBL000133            PBL000129 PBL000132 PBL000130 PBL000131,PBL000133
Material_ID          PBL0001341 PBL0001381 PBL0001351 PBL0001361 PBL0001371       PBL0001341 PBL0001381 PBL0001351 PBL0001361,PBL0001371
  1. 用逗号分隔:
select *
from uda_data u, 
     xmltable(
         'ora:tokenize(concat(",",.),",")[position()>1]'
         passing regexp_replace(value_text,'(.{1,30}+)\s','\1,')
         columns 
            n for ordinality,
            v varchar2(100) path '.'
         );

结果:

UDA                  VALUE_TEXT                                                            N V
-------------------- ------------------------------------------------------------ ---------- ---------------------------------------------
Material_ID          PBL000129 PBL000132 PBL000130 PBL000131 PBL000133                     1 PBL000129 PBL000132 PBL000130 PBL000131
Material_ID          PBL000129 PBL000132 PBL000130 PBL000131 PBL000133                     2 PBL000133
Material_ID          PBL0001341 PBL0001381 PBL0001351 PBL0001361 PBL0001371                1 PBL0001341 PBL0001381 PBL0001351 PBL0001361
Material_ID          PBL0001341 PBL0001381 PBL0001351 PBL0001361 PBL0001371                2 PBL0001371

【讨论】:

  • 很酷的解决方案。如果 Oracle 有像 PostgreSQL 那样的 regexp_split_to_table 函数,那就太好了。目前还没有,很高兴可以按照您的说明解决此问题。
  • @ThorstenKettner 在 oracle 中拆分字符串有很多不同的解决方案。它甚至具有 dbms_utility.comma_to_table 或 xmltable/xmlquery(tokenize) 等内部函数,但通常最好为此创建自己的自定义流水线函数
猜你喜欢
  • 1970-01-01
  • 2013-04-25
  • 2011-12-20
  • 1970-01-01
  • 1970-01-01
  • 2021-07-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多