如何优化子字符串和字符串或替换函数进行调试

Posted

技术标签:

【中文标题】如何优化子字符串和字符串或替换函数进行调试【英文标题】:How to optimize the substring & instring or substitute the functions for debugging 【发布时间】:2015-11-03 15:25:21 【问题描述】:

我编写了下面的存储过程,用于读取移动到服务器上的逗号分隔文件(“DIR”文件夹),并且在执行脚本时,它基本上解析文件(.csv),并将数据分配给它相应的变量(xJOB_ID、xCTRL_ID、xACCT_SEC、xCREATEDON_DATE),以便我可以将数据插入表中。 我在 Windows 7 环境中使用 Oracle SQL Developer 版本 4.0.0.13。幸运的是,在将我的头撞在桌子上几次之后,代码可以正常工作,并且我在运行脚本时没有遇到任何问题。

文件格式示例: 1111, 2, T, 10/10/2000 2222、12345、U、2001 年 10 月 10 日 5555, 123, S, 10/10/1999

我的问题: 我发现使用 SUBSTRING 和 INSTRING 函数来解析数据有点困难,我想知道如何改进脚本,以便在需要进行一些调试时,对于没有编写存储过程的人来说可以轻松解决。 请让我知道这是否有意义。我给了你整个脚本,这样你就可以理解我想要完成的工作,以便我可以改进代码以进行调试。

create or replace PROCEDURE SP_INSERT_INTO_TABLE(xFILE_NAME IN VARCHAR2)

IS 


--UTL_FILE is an oracle package that allows you to read and write operating system files. 

TEXT_DATA UTL_FILE.FILE_TYPE;

v_ROW_LENGTH    NUMBER := 1024;
v_TEXTSTRING    VARCHAR2(4000);
cLINE           VARCHAR2(100);
xJOB_ID         NUMBER;
xCTRL_ID        NUMBER;
xACCT_SEC       VARCHAR2(1);
xCREATEDON_DATE DATE;
xCOUNT          NUMBER := 0;

BEGIN
    BEGIN
    --Streams in the file data and assigns it to TEXT_DATA variable. 
    TEXT_DATA := UTL_FILE.FOPEN('DIR', xFILE_NAME, 'R', v_ROW_LENGTH);      
END;

--Begin LOOP to get each line and assign to cLINE to extract, assign to each variable, and insert into the table
LOOP    
    BEGIN
        --Gets each string/line up to the line terminator
        UTL_FILE.GET_LINE(TEXT_DATA, v_TEXTSTRING);
    EXCEPTION
        WHEN NO_DATA_FOUND THEN
            EXIT;
    END;

    --Each line is assigned to the variable cLINE.
    cLINE := v_TEXTSTRING;

    --Begin to parse data using SUBSTRING and INSTRING functions
    BEGIN        
        --Extracts string from cLINE position 1 up to the first occurrence, converts it to a number, and assigns it to the variable. 
        xJOB_ID         := TO_NUMBER(SUBSTR(cLINE, 1,INSTR(cLINE, ',', 1, 1)-1));

        --Extracts string from cLINE between the 1st and 2nd occurrence, converts it to a number, and assigns it to the variable.
        xCTRL_ID        := TO_NUMBER(SUBSTR(cLINE, INSTR(cLINE, ',', 1, 1)+1, INSTR(cLINE, ',', 1,2)-INSTR(cLINE, ',', 1,1)-1));

        --Extracts string from cLINE between the 2nd and 3rd occurrence and assigns it to the variable.
        xACCT_SEC       := SUBSTR(cLINE, INSTR(cLINE, ',', 1, 2) +1, INSTR(cLINE, ',', 1,3)-INSTR(cLINE, ',', 1,2) -1);  

        --Extracts string from cLINE after the last occurrence, converts it to a date, and assigns it the variable.
        xCREATEDON_DATE := TO_DATE(SUBSTR(cLINE, INSTR(cLINE, ',', 1, 3)+1), 'MM/DD/YYYY');

        INSERT INTO TABLE(JOB_ID, CTRL_ID, ACCT_SEC, CREATEDON_DATE)
        VALUES(xJOB_ID, xCTRL_ID, xACCT_SEC, xCREATEDON_DATE);
        COMMIT;

        --Counter to count the amount of inserts
        xCOUNT := xCOUNT + 1;

    EXCEPTION
        --Exception to handle the conversion of a string to a NUMBER or value is longer than the declared length of the variable.
        WHEN VALUE_ERROR THEN 
        NULL;

    END;        
END LOOP;

DBMS_OUTPUT.PUT_LINE('RECORDS INSERTED: ' || xCOUNT);    
UTL_FILE.FCLOSE(TEXT_DATA);

END;

【问题讨论】:

您是否有理由创建自己的解决方案,而不是使用SQL*Loader 或external table 将文件加载到表中?该文件已经在服务器上并且在一个可识别的目录中,因此外部表似乎是最简单的方法。 @AlexPoole - 老实说,不知道 SQL*Loader 并且不确定我将如何使用它,我确实想到了一个外部表,但是当我运行 ' 时似乎总是遇到问题SELECT * FROM EXTERNAL_TABLE' 针对 .CSV 文件(也许我会很快再次解决这个问题,并进一步敲击我的头)。做SP让我的生活变得困难了吗?谢谢! 好点,@AlexPoole!外部表 FTW! \o/ (并且插入变为insert into table_name (...) select ... from external_table_name; 的额外好处非常容易调试......) 外部表通常不需要任何头疼...尽管基于外部表的答案不适合您的要求。我建议您尝试这种方法,如果您遇到困难,请单独提出一个问题? @NewComer - 如果您需要有关外部表的帮助,我认为您应该提出一个新问题; boneist 已经为您最初提出的问题提供了答案,您可能还会得到其他人的答案;它们可能对未来的访问者有所帮助。 【参考方案1】:

您可以改用 REGEXP_SUBSTR,因为这只需要一个函数调用,而不是一系列 SUBSTR 和 INSTR 调用,例如:

(由 Gary_W 提供):

declare
  v_str varchar2(20) := 'a,,bcd';
  v_substr1 varchar2(10);
  v_substr2 varchar2(10);
  v_substr3 varchar2(10);
begin
  v_substr1 := regexp_substr(v_str, '([^,]*)(,|$)', 1, 1, NULL, 1);
  v_substr2 := regexp_substr(v_str, '([^,]*)(,|$)', 1, 2, NULL, 1);
  v_substr3 := regexp_substr(v_str, '([^,]*)(,|$)', 1, 3, NULL, 1);
  dbms_output.put_line(v_substr1||':'||v_substr2||':'||v_substr3);
end;
/

a::bcd

如所示,上述内容适用于具有空部分的字符串。搜索模式分为两组(也称为子表达式):[^,]*,|$

第一组说:出现 0 次或多次 (*) 的非逗号 ([^,]) 的任何字符。

第二组说:逗号或行尾。

因此,整个模式正在寻找一组任何字符,不包括可能存在或不存在的逗号,后面跟一个逗号或行尾。

regexp_substr 中的最后一个参数表示我们要从搜索模式中选择第一个子表达式来显示 - 如果我们没有包含它,那么您最终会以逗号作为字符串的一部分显示被退回。


如果您绝对确定字符串的任何元素都不会为空,那么以下方法将起作用:

declare
  v_str varchar2(20) := 'a,123,bcd';
  v_substr1 varchar2(10);
  v_substr2 varchar2(10);
  v_substr3 varchar2(10);
begin
  v_substr1 := regexp_substr(v_str, '[^,]+', 1, 1);
  v_substr2 := regexp_substr(v_str, '[^,]+', 1, 2);
  v_substr3 := regexp_substr(v_str, '[^,]+', 1, 3);
  dbms_output.put_line(v_substr1||':'||v_substr2||':'||v_substr3);
end;
/

a:123:bcd

这只是寻找指定出现的非逗号字符,这比前面示例中使用的搜索模式更容易理解(恕我直言!),但健壮性要低得多。

【讨论】:

警告!如果列表中有一个 NULL 元素,那么[^,]+ 形式的正则表达式恰好是您在搜索如何解析列表时会看到的最常见的正则表达式!它将默默地返回错误的元素,从而导致返回错误的数据。尝试将v_str 设置为'a,,bcd' 并查看结果。请改用此调用(第 4 个参数是您想要的元素):regexp_substr(v_str, '(.*?)(,|$)', 1, 1, NULL, 1)。请参阅此处了解更多信息:***.com/questions/25648653/… 感谢@Gary_W 的警告。我明天必须调查一下!如果我使用您提到的模式,我仍然会得到逗号(例如,使用 v_str = 'a,,bcd' 我会从上述过程中得到 a,:,:bcd 的输出);有没有一种很好的方法可以将它们作为正则表达式的一部分删除,而不必添加 rtrim()? 我没有,也许你有错字?我用你发布的原始代码得到'a:bcd:'。根据我的建议,我得到了 'a::bcd'. 嗯!明天去看看,看看能不能搞清楚是怎么回事! @Gary_W 好的,我找出了我做错了什么来获得额外的逗号(基本上,已经更新了搜索模式,但没有在最后两个参数中添加,d'oh!) .我还学到了更多关于正则表达式的知识,非常感谢! *:-) (那个子表达式很方便;不知道为什么我以前从未玩过它!)我会更新我的答案以包含您的建议。再次欢呼。

以上是关于如何优化子字符串和字符串或替换函数进行调试的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Netezza 中替换完整的子字符串

如何替换字符串的多个子串?

R语言应用substr函数和substring函数抽取(extract)删除(Remove)替换匹配(Match)特定的字符串并对比两个函数的异同grepl检查子字符串是否存在于字符串中

如何确保 replaceAll 将替换整个单词而不是子字符串

如何在JAVA中用不同的子字符串替换字符串的子字符串?

如何通过引用传递子字符串?