拆分分隔符分隔的字符串并插入到oracle 11中的表中

Posted

技术标签:

【中文标题】拆分分隔符分隔的字符串并插入到oracle 11中的表中【英文标题】:Split delimiter separated string and insert into a table in oracle 11 【发布时间】:2018-10-28 14:02:12 【问题描述】:

我有分隔符分隔的输入字符串,它可以有大约 40 个标记(数量可能会增加),我想使用 oracle 11 中的存储过程将这些值插入到表中; 最好的方法是什么

    创建一个包含 40 个 IN 参数的 SP 并使用它来插入。 创建一个带有 1 个 IN 参数的 SP,它将采用该字符串并拆分分隔符分隔的标记并将它们插入到表中

如果第二种方法看起来不错,那么请建议如何实现它??

例如,如果一个字符串类似于"abc,123,xyz,pqr,12"(这里的分隔符是逗号) 所以在运行 SP 我的表 table1(A varchar2, B Number, C varchar2, D varchar2, E number ) 应该有类似的条目

A  | B | C | D | E
abc|123|xys|pqr |12

我想出了以下解决方案,不确定性能,有没有更好的方法来做同样的事情?

declare
  string_to_parse varchar2(2000) := 'abc,123,xyz,pqr,12';
  A varchar2(4);
  B number;
  C varchar2(4);
  D varchar2(4);
  E number;
begin

  string_to_parse := string_to_parse||',';

   A  := REGEXP_SUBSTR(string_to_parse,'[^,]+', 1, 1);
   B  := TO_NUMBER(REGEXP_SUBSTR(string_to_parse,'[^,]+', 1, 2));
   C  := REGEXP_SUBSTR(string_to_parse,'[^,]+', 1, 3);
   D  := REGEXP_SUBSTR(string_to_parse,'[^,]+', 1, 4);
   E  := TO_NUMBER(REGEXP_SUBSTR(string_to_parse,'[^,]+', 1, 5));
   dbms_output.put_line('A ' || A || ' B ' || B || ' c ' || c || ' D ' || D || ' E ' || E);
--insert into table
end;

【问题讨论】:

这和 Java 有什么关系? 移除了 java 标签 您的单位/管理层必须认真考虑将您的 Oracle 数据库升级到最新版本(或至少升级到 Oracle 11)。 Oracle 9 在当今世界是原始且过时的。 对不起,我们将在我的本地使用 oracle 11..9。编辑问题 我想问题是,为什么输入是分隔字符串?在过程中的某个时刻,您必须具有离散数据值。为什么不让它们保持离散并像这样将它们传递到数据库? 【参考方案1】:

在这种特殊情况下,分裂离目标还有很长的路要走。 考虑到一个目标表可能有很多列(是的,5 个列在一个不同的变量中处理每个列都太多了),我建议使用模式字典来增加一些灵活性。

让我们看一个带有两个参数的过程:一个表名和一个包含逗号分隔值列表的字符串。 这里假设该表只有字符串、数字和时间列。要实现完整版本,请在程序开始时添加对所有必需数据类型的处理。

注意,在中间我们使用标准 SQL 方法将一个字符串拆分为一个子字符串表:

select level as column_id, 
       REGEXP_SUBSTR(pi_values_list, '[^,]+', 1, level) as column_val 
  from dual connect by REGEXP_SUBSTR(pi_values_list, '[^,]+', 1, level) is not null;

这是整个过程:

  create or replace procedure myInsertInto(pi_table_name  char,
                                           pi_values_list char)
  is
    v_statement     varchar2(30000) := 'INSERT INTO %TABLE_NAME% (%COLUMNS_LIST%) VALUES (%VALUES_LIST%)';
    v_columns_list  varchar2(10000);
    v_values_list   varchar2(10000);
  begin

    SELECT LISTAGG(T.column_name, ',') within group (order by T.column_id) ,
           LISTAGG( -- implement specific types handling here
                    CASE
                    WHEN S.column_val IS NULL
                      THEN 'NULL'
                    WHEN T.data_type = 'NUMBER'
                      THEN S.column_val
                    WHEN T.data_type IN ('DATE', 'TIMESTAMP') 
                      THEN 'TIMESTAMP ''' || S.column_val || ''''
                    WHEN T.data_type like '%CHAR%' 
                      THEN '''' || S.column_val || ''''                    
                    ELSE 'NULL'
                    END, 
           ',') within group (order by T.column_id)
    into v_columns_list,
         v_values_list
    from user_tab_cols T,
         (select level as column_id, REGEXP_SUBSTR(pi_values_list, '[^,]+', 1, level) as column_val 
            from dual connect by REGEXP_SUBSTR(pi_values_list, '[^,]+', 1, level) is not null) S
   where T.table_name = pi_table_name
     and T.column_id = S.column_id;

    if v_columns_list IS NULL then
      raise_application_error(-20000, 'Not found columns for table ' || pi_table_name);
    end if;

    -- finalizing the statement
    v_statement := replace(v_statement, '%TABLE_NAME%', pi_table_name);      
    v_statement := replace(v_statement, '%COLUMNS_LIST%', v_columns_list);      
    v_statement := replace(v_statement, '%VALUES_LIST%', v_values_list);

    execute immediate v_statement;
  end;
  /

那就这样用吧

create table MY_TABLE (
  col_a VARCHAR2(10),
  col_b NUMBER,
  col_c VARCHAR2(10),
  col_d DATE,
  col_E VARCHAR2(10) default 'DEFAULT'
);



begin
  myInsertInto('MY_TABLE', 'abc,123,xyz,2018-01-02 23:01:10,pqr' );
  myInsertInto('MY_TABLE', 'def,345,mkr' );
  myInsertInto('MY_TABLE', 'fgh' );
end;
/

【讨论】:

【参考方案2】:

第一种方法是禁止的。

第二个可能有效。 简单地说:

    将输入字符串分配给变量 s。

现在,循环中:

    如果 s 的长度为 0,则退出循环 使用 instr 查找第一次出现的分隔符 (',')。将其分配给 X 如果 X = 0,则 X := len(string) + 1 X := X - 1 如果 X > 0,则将 substr(s, 1, X) 插入表中 如果 X > 0,则 s := substr(s, X+1, len(s))

我没有对其进行测试,并且有明显的优化方法(例如 - 您可以存储当前解析部分的“左端索引”,而不是将子字符串分配回 s。

但是有更好的方法——用纯 sql 来做。 不幸的是,我不知道你的oracle版本是否支持所有功能,但试试这个选择:

with 
my_input_string as (
   select 'my,delimited,,,,,,input,string' s from dual
),
string_to_rows as (
   select trim(regexp_substr(s, '[^,]+', 1, LEVEL)) col 
    from my_input_string
 connect by instr(my_input_string.s, ',', 1, LEVEL - 1) > 0
)
select *
  from string_to_rows
 where col is not null

如果它有效(我的意思是“有效” - 返回四行),只需在插入中使用它。 用过程的参数替换硬编码的字符串,就是这样。

【讨论】:

感谢您的回复,但代码 sn-p 在 oracle 中无法正常工作 显然您的版本不支持分层查询和/或 with 子句。 with 子句很容易解决 - 只需将查询更改为使用子查询而不是 CTE。这里有一些其他技巧,如何将分隔字符串映射到行中:lalitkumarb.wordpress.com/2014/12/02/… "'第一种方法是禁止的。" 比传递连接的字符串要少。 @APC 如果您的输入首先是串联的字符串,那么无论如何您都不会拯救世界。您只委派了在过程外部拆分字符串的责任(您可以在其中犯所有与内部相同的错误),现在您有一个包含 40 个参数、40 组插入值的过程,并且您有每当参数数量增加时(过程调用、过程定义和过程主体中的任何内容),至少在 3 个位置修改您的代码。我同意这种观点,但世界并不完美。 但是世界不是以逗号分隔的字符串开始的。数据库处理离散数据值,应用程序也是如此。有些东西连接了那些以前离散的值。这就是问题的根源。

以上是关于拆分分隔符分隔的字符串并插入到oracle 11中的表中的主要内容,如果未能解决你的问题,请参考以下文章

拆分'$'分隔的字符串并插入到表中

Oracle PL/SQL 程序在源表中拆分逗号分隔的数据并推送到目标表中

使用 Oracle SQL 将可变长度分隔字符串拆分为列

使用oracle SQL按分隔符位置拆分字符串

Oracle 过程使用现有过程中使用的数据拆分逻辑将数据反透视到目标

使用 regexp_substr 在 Oracle 中按空格和字符拆分字符串作为分隔符