【问题标题】:PL/SQL regex match commas not inside quotesPL/SQL 正则表达式匹配逗号不在引号内
【发布时间】:2020-05-21 01:57:43
【问题描述】:

我有一个逗号分隔的字符串,我需要匹配该字符串中的所有逗号,但双引号内的逗号除外。我正在为此使用正则表达式。

,,,,"8000000,B767-200","B767-200","Boeing 767-200","ACFT",,,,,,,,,,,,,,,,,,,,,,,,,,

我尝试了以下正则表达式模式,但它们都不能在 PL/SQL 中工作,而是在在线正则表达式测试器中工作。

,(?=(?:[^\"]*\"[^\"]*\")*(?![^\"]*\"))

(?!\B"[^"]*),(?![^"]*"\B)

我在 PL/SQL 的过程中使用 REGEXP_INSTR 函数来识别逗号的索引。有人可以为此目的建议我在 PL/SQL 中工作的正则表达式模式或帮助我编写一个。

谢谢。

【问题讨论】:

    标签: regex oracle plsql split


    【解决方案1】:

    Oracle 不支持前瞻组和非捕获组,因此您需要匹配引号。

    假设您可以使用不带引号的字符串或带引号的字符串(可能包含转义引号),那么您可以使用正则表达式:

    ([^",]*|"(\\"|[^"])*"),
    

    你可以这样使用:

    WITH matches ( id, csv, start_pos, comma_pos, idx,  num_matches ) AS (
      SELECT id,
             csv,
             1,
             REGEXP_INSTR( csv, '([^",]*|"(\\"|[^"])*"),', 1, 1, 1, NULL ) - 1,
             1,
             REGEXP_COUNT( csv, '([^",]*|"(\\"|[^"])*"),' )
      FROM   test_data
    UNION ALL
      SELECT id,
             csv,
             REGEXP_INSTR( csv, '([^",]*|"(\\"|[^"])*"),', 1, idx + 1, 0, NULL ),
             REGEXP_INSTR( csv, '([^",]*|"(\\"|[^"])*"),', 1, idx + 1, 1, NULL ) - 1,
             idx + 1,
             num_matches
      FROM   matches
      WHERE  idx < num_matches
    )
    SELECT id,
           idx,
           start_pos,
           comma_pos,
           SUBSTR( csv, start_pos, comma_pos - start_pos ) AS value
    FROM   matches
    

    所以对于您的测试数据:

    CREATE TABLE test_data ( id, csv ) AS
    SELECT 1, ',,,,"8000000,B767-200","B767-200","Boeing 767-200","ACFT",,,,,,,,,,,,,,,,,,,,,,,,,,' FROM DUAL
    

    哪个输出:

    身份证 | IDX | START_POS | COMMA_POS |价值 -: | --: | --------: | --------: | :----------------- 1 | 1 | 1 | 1 | 1 | 2 | 2 | 2 | 1 | 3 | 3 | 3 | 1 | 4 | 4 | 4 | 1 | 5 | 5 | 23 | “8000000,B767-200” 1 | 6 | 24 | 34 | “B767-200” 1 | 7 | 35 | 51 | “波音 767-200” 1 | 8 | 52 | 58 | “ACFT” 1 | 9 | 59 | 59 | 1 | 10 | 60 | 60 | 1 | 11 | 61 | 61 | 1 | 12 | 62 | 62 | 1 | 13 | 63 | 63 | 1 | 14 | 64 | 64 | 1 | 15 | 65 | 65 | 1 | 16 | 66 | 66 | 1 | 17 | 67 | 67 | 1 | 18 | 68 | 68 | 1 | 19 | 69 | 69 | 1 | 20 | 70 | 70 | 1 | 21 | 71 | 71 | 1 | 22 | 72 | 72 | 1 | 23 | 73 | 73 | 1 | 24 | 74 | 74 | 1 | 25 | 75 | 75 | 1 | 26 | 76 | 76 | 1 | 27 | 77 | 77 | 1 | 28 | 78 | 78 | 1 | 29 | 79 | 79 | 1 | 30 | 80 | 80 | 1 | 31 | 81 | 81 | 1 | 32 | 82 | 82 | 1 | 33 | 83 | 83 |

    db小提琴here

    (注意:你想匹配逗号,这个正则表达式完全符合你的要求;它不匹配逗号分隔列表中的任何最终值,因为没有终止逗号。如果你想做然后使用正则表达式([^",]*|"(\\"|[^"])*")(,|$)db<>fiddle。)

    如果你想在程序中使用它:

    CREATE PROCEDURE extract_csv_value(
      i_csv   IN VARCHAR2,
      i_index IN INTEGER,
      o_value OUT VARCHAR2
    )
    IS
    BEGIN
      o_value := REGEXP_SUBSTR( i_csv, '([^",]*|"(\\"|[^"])*")(,|$)', 1, i_index, NULL, 1 );
    
      IF SUBSTR( o_value, 1, 1 ) = '"' THEN
        o_value := REPLACE( SUBSTR( o_value, 2, LENGTH( o_value ) - 2 ), '\"', '"' );
      END IF;
    END;
    /
    

    然后:

    DECLARE
      csv   VARCHAR2(4000) := ',,,,"8000000,B767-200","B767-200","Boeing 767-200","ACFT",,,,,,,,,,,,,,,,,,,,,,,,,,';
      value VARCHAR2(100);
    BEGIN
      FOR i IN 1 .. 10 LOOP
        extract_csv_value( csv, i, value );
        DBMS_OUTPUT.PUT_LINE( LPAD( i, 2, ' ' ) || ' ' || value );
      END LOOP;
    END;
    /
    

    输出:

    1 2 3 4 5 8000000,B767-200 6 B767-200 7 架波音 767-200 8 ACFT 9 10

    db小提琴here

    【讨论】:

      【解决方案2】:

      我尝试在不使用 REGEX 的情况下解决它,因此请检查以下 PROCEDURE 是否按预期工作。

      CREATE OR REPLACE PROCEDURE p_extract(p_string IN VARCHAR) AS
          TYPE table_result IS TABLE OF VARCHAR2(255) INDEX BY PLS_INTEGER;
          t_retval table_result;
      
          opening BOOLEAN := FALSE;
          cnt INTEGER := 1;
          I INTEGER := 1;
          j INTEGER := 1;
      BEGIN
      
          WHILE cnt <= LENGTH(p_string) AND cnt <> 0 LOOP
              IF substr(p_string, cnt, 1) = '"'THEN
                  opening := NOT opening;
              END IF;
      
              IF opening THEN
                  I := instr(p_string, '"', cnt + 1, 1);
                  t_retval(t_retval.COUNT + 1) := substr(p_string, cnt, I - cnt + 1);
              END IF;
      
              cnt := instr(p_string, '"', cnt + 1, 1);
          END LOOP;
      
          FOR K IN t_retval.FIRST..t_retval.LAST LOOP
              dbms_output.put_line(t_retval(K));
          END LOOP;
      END;
      

      测试一下。

      BEGIN
          p_extract(',,,,"8000000,B767-200","B767-200","Boeing 767-200","ACFT",,,,,,,,,,,,,,,,,,,,,,,,,,');
      END;
      
      --OUTPUT
      /*
      "8000000,B767-200"
      "B767-200"
      "Boeing 767-200"
      "ACFT"
      */
      

      但是,如果您错过了最后一个或第一个“

      ,这将不起作用

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2021-07-31
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-07-01
        相关资源
        最近更新 更多