【问题标题】:How do I copy a row and split one of the columns based on certain criteria in Oracle SQL?如何根据 Oracle SQL 中的某些条件复制一行并拆分其中一列?
【发布时间】:2020-05-17 18:44:16
【问题描述】:

我有一个包含 250k 数据的表,在这些数据中,我有 1000 行,每列都有相同的数据,一个参考列是不同的。下面是一个例子:

Name   | Tel_No     | Post_Code | Ref_No
Damian | 7900123456 | ME1 2BC   | 12345678 1234567891234
Graeme | 7900789012 | ME1 2DE   | 12 345 5678901234567
Sarah  | 7900456789 | ME1 2FG   | 90123456 890123456789

现在我想做的是对于引用有一个 8 位数字、一个空格和一个 13 位数字的每一行,我想复制这一行但拆分 ref_no 以便我有一行8 位 ref_no 和 1 与 13 位。

在上面的例子中,代码应该只适用于第一行,因为 Graeme 不符合这个标准,因为 ref_no 有 2 位数字,然后是一个空格,因此代码应该直接失败。对于 Sarah,代码将失败,因为第二部分的 ref_no 只有 12 位而不是 13 位。对于 Graeme 和 Sarah,他们的数据仍将保留在具有 ref_no 的单行中。

我猜这必须通过正则表达式来找到匹配的模式,不幸的是我还没有尝试任何代码,因为它对我来说是新的,并且行的复制让我有点失望以前做过类似的事情,因此希望得到一些关于推动这一发展的最佳实践的建议。

顺便说一句,这就是它在表格中的样子(我确实有一个自动 ID 列,因此新行也将分配新的 ID):

Name   | Tel_No     | Post_Code | Ref_No
Damian | 7900123456 | ME1 2BC   | 12345678
Damian | 7900123456 | ME1 2BC   | 1234567891234

提前致谢。

编辑 04/02/2020 -----------

抱歉耽搁了,很遗憾不是很好。谢谢大家的回复。 Yunnosch,Wiktor,Rob 应要求以下是我的创建表和插入语句,正是我正在寻找的内容,只是为了让我更清楚地了解我的要求。一开始就应该道歉。

创建表:

CREATE TABLE "BU_TABLE" 
   (    "ID" NUMBER NOT NULL ENABLE, 
    "NAME" VARCHAR2(255 BYTE), 
    "TEL_NO" VARCHAR2(255 BYTE), 
    "POST_CODE" VARCHAR2(8 BYTE), 
    "REF_NO" VARCHAR2(255 BYTE), 
     CONSTRAINT "TABLE1_PK" PRIMARY KEY ("ID")
;

我将 Tel_No 和 Ref_No 列设置为 Varchar2 的原因是因为空格,因为当数字有空格时 a) 它会提示 Invalid Number 错误 b) Tel_No 目前与示例数据没有这个问题,但随着更多数据的添加,我可能会遇到这个问题。

我的插入语句:

Insert into BU_TABLE (ID,NAME,TEL_NO,POST_CODE,REF_NO) values (1,'Damian','7900123456','ME1 2BC','12345678 1234567891234');
Insert into BU_TABLE (ID,NAME,TEL_NO,POST_CODE,REF_NO) values (2,'Graeme','7900789012','ME1 2DE','12 345 5678901234567');
Insert into BU_TABLE (ID,NAME,TEL_NO,POST_CODE,REF_NO) values (3,'Sarah','7900456789','ME1 2FG','90123456 890123456789');

现在我希望 BU_TABLE 保持原样,保留原始数据。我想在这个表的后面创建另一个表来拆分 Ref_No,所以我有一个由 8 个数字组成的序列,一个空格,然后是 13 个数字。无论出现这种模式,我都想复制该行,创建一个新 ID(下一个可用)并给我以下信息:

我想要什么:

Name   | Tel_No     | Post_Code | Ref_No
Damian | 7900123456 | ME1 2BC   | 12345678
Damian | 7900123456 | ME1 2BC   | 1234567891234

所需代码未找到序列的行,数据行将在表中保持不变,因此我的最终表将如下所示:

决赛桌

ID | Name   | Tel_No     | Post_Code | Ref_No
2 | Graeme | 7900789012 | ME1 2DE   | 12 345 5678901234567
3 | Sarah  | 7900456789 | ME1 2FG   | 90123456 890123456789
4 | Damian | 7900123456 | ME1 2BC   | 12345678
5 | Damian | 7900123456 | ME1 2BC   | 1234567891234

希望这能让我更清楚我想要做什么。

【问题讨论】:

  • meta.stackoverflow.com/questions/333952/…(考虑使用 SQLite 玩具数据库来提供命令行可用的数据库)。
  • ref_no 的两部分是两行还是两列?在您描述的情况下,后者似乎更有可能。考虑通过展示您想象的结果来回答。只需手动输入即可。
  • 您的描述听起来像是想要的结果是Damian | 7900123456 | ME1 2BC | 12345678Damian | 7900123456 | ME1 2BC | 1234567891234。但我想你的意思可能是Damian | 7900123456 | ME1 2BC | 12345678 | 1234567891234
  • 你想要哪里的结果?作为现有表中的附加行(行对)?我认为这会破坏 ref_no 列的语义。也许作为单独表格中的新行(行对)?这将允许用两列 ref_no8 和 ref_no13 替换单列 ref_no。
  • 您好 Yunnosch,感谢您的回复。我想要 2 个单独的行,只需编辑我的帖子以显示最终答案的样子。非常感谢你。

标签: sql regex oracle


【解决方案1】:

这个方法怎么拆分我会加完整代码:
这是前 8 位数字:

select substr(ref_no,0,8) from stack where instr(ref_no,' ') >= 9;

完整代码 =>

with 
tb_split8 as (
select Name, substr(ref_no,0,8) as split8 from stack where instr(ref_no,' ') >= 9),
tb_split13 as( 
select Name, substr(ref_no,10,13) as split13 from stack where instr(ref_no,' ',10) = 0 and length(substr(ref_no,10,13)) = 13)
select a.Name, Post_code, Tel_no, split8, split13 from stack a join tb_split8 b on a.Name = b.Name join tb_split13 c on b.Name = c.Name ;

我签入了 Toad,它工作正常。

【讨论】:

  • 这不注意 13 位数的要求。
  • 它还会复制 8 个包含字母的非空白字符。 IE。这假设只有数字。 (我也认为只能保证数字,但 OP 没有确认。)
  • @Yunnosch 请再试一次 我修复了一些字符串。
  • 尝试很难,因为 OP 和您都没有提供玩具数据库。但我很确定你的结果只有一行,而 OP 想要两行。
【解决方案2】:

即使没有正则表达式,您也可以按照以下条件获得两行

with data
  as (
        select '12345678 1234567890123' as x /*This matches the 8<SPACE>13*/
         from dual
        union all
        select '1234567 1234567890123' as x
         from dual
     ) 
   ,criteria_data
    as (
        select t.*
               ,substr(x,1,instr(x,' ')) as first_part
               ,substr(x,instr(x,' ')+1,length(x)) as second_part
          from data t
         where length(x)-length(replace(x,' ',''))=1  /*Check for exactly one space*/
           and length(substr(x,1,instr(x,' ')))=8 /*check for first segment with 8 char*/
           and length(substr(x,instr(x,' ')+1,length(x)))=13 /*Check for second segment with 13 char length*/
       )
select a.*,case when lvl=1 then first_part when lvl=2 then second_part end as ref_no 
  from criteria_data a
  join (select level as lvl 
          from dual 
        connect by level<=2) b
    on 1=1

【讨论】:

    【解决方案3】:

    您可以将regexp_substr()substr()函数一起使用,相互比较分割的部分是否相等。然后为满足where条件的每一行再生成一行:

    with t as
    (
    select regexp_substr(Ref_No,'[[:digit:]]{8}+') as Ref_No1_0,
           regexp_substr(Ref_No,'[^[:space:]]+$') as Ref_No2_0,
           substr(Ref_No,1,8) as Ref_No1_1 , substr(Ref_No,-13) as Ref_No2_1,
           t.*
      from tab t
    )
    select Name , Tel_No , Post_Code , 
           case when lvl = 1 then Ref_No1_0 
                when lvl = 2 then Ref_No2_0 end as Ref_No       
      from t
     cross join (select level as lvl from dual connect by level <= 2) 
     where Ref_No1_0 = Ref_No1_1 
       and Ref_No2_0 = Ref_No2_1 
    

    或者在where条件下直接使用regexp_like()(连同substr())函数:

    select Name , Tel_No , Post_Code , 
           case when lvl = 1 then substr(Ref_No,1,8) 
                when lvl = 2 then substr(Ref_No,-13) end as Ref_No
      from tab
     cross join (select level as lvl from dual connect by level <= 2)   
     where regexp_like(substr(Ref_No,1,8),'[[:digit:]]{8}') 
       and regexp_like(substr(Ref_No,-13),'[[:digit:]]{13}') 
    

    Demo

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-01-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多