Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
439 views
in Technique[技术] by (71.8m points)

sql - How to skip comma from csv using double quotes

I am getting a string in the below format after reading data from a csv file

v_lastline = '29218368,8062115," Benedict Canyon Equities, Inc",CLS,,FAX';

I just want to convert it into an array while will contain 6 values, the comma before the , Inc needs to be escaped. Can any one please suggest whats the best way to do it in PL/SQL?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

This is similar to this question, but you have empty elements in your list; and a simple translation of one of the patterns I tried there skips those:

var v_lastline varchar2(50);
exec :v_lastline := '29218368,8062115," Benedict Canyon Equities, Inc",CLS,,FAX';

select level as lvl,
  regexp_substr(:v_lastline, '("[^"]*"|[^,]+)', 1, level) as element
from dual
connect by level <= regexp_count(:v_lastline, '("[^"]*"|[^,]+)');

       LVL ELEMENT                                
---------- ----------------------------------------
         1 29218368                                
         2 8062115                                 
         3 " Benedict Canyon Equities, Inc"        
         4 CLS                                     
         5 FAX                                     

If you can identify a special character that will never appear in the data then you can work around that by putting that into the empty elements by changing every comma to comma+character, and then removing it after the split:

select level as lvl,
  replace(regexp_substr(replace(:v_lastline, ',', ',§'),
    '(§"[^"]*"|[^,]+)', 1, level), '§', null) as element
from dual
connect by regexp_substr(replace(:v_lastline, ',', ',§'),
  '(§"[^"]*"|[^,]+)', 1, level) is not null;

       LVL ELEMENT                                
---------- ----------------------------------------
         1 29218368                                
         2 8062115                                 
         3 " Benedict Canyon Equities, Inc"        
         4 CLS                                     
         5                                         
         6 FAX                                     

It's an extension of a common method to split delimited strings, which is explained in detail here.

  • replace(:v_lastline, ',', ',§') changes ...,CLS,,FAX to ...,§CLS,§,§FAX, where § is a character you'll never see.
  • regexp_substr(..., '(§"[^"]*"|[^,]+)', 1, level) tokenises the updated value with a regex that looks for any double-quote-enclosed value (now preceded by the special character too) or a non-comma; the order of the evaluation means commas inside the quoted part are ignored.
  • the level is part of the hierarchical query syntax, where:
  • connect by regexp_substr(<same value and pattern>) is not null just figured out how many tokens there are.
  • and finally replace(regexp_substr(...), , '§', null) removes the special character used in the first step.

You can then remove the double-quotes too with a further level of replace(), and trim whitespace, if you want/need to.

You have't said quite what you mean by an array, but you can run that query in PL/SQL and bulk-collect into a collection if that's what you intend to work with. For example, using the built-in ODCIVARCHAR2LIST collection type:

set serveroutput on
declare
  v_lastline varchar2(50);
  v_array sys.odcivarchar2list;
begin
  v_lastline := '29218368,8062115," Benedict Canyon Equities, Inc",CLS,,FAX';

  select trim(replace(replace(
    regexp_substr(replace(:v_lastline, ',', ',§'),
      '(§"[^"]*"|[^,]+)', 1, level), '§', null), '"', null))
  bulk collect into v_array
  from dual
  connect by regexp_substr(replace(:v_lastline, ',', ',§'),
    '(§"[^"]*"|[^,]+)', 1, level) is not null;

  dbms_output.put_line('Number of elements: ' || v_array.count);
  for i in 1..v_array.count loop
    dbms_output.put_line('Index ' || i || ' has: ' || v_array(i));
  end loop;
end;
/

Number of elements: 6
Index 1 has: 29218368
Index 2 has: 8062115
Index 3 has: Benedict Canyon Equities, Inc
Index 4 has: CLS
Index 5 has: 
Index 6 has: FAX

With multiple empty elements this also (now) works:

exec :v_lastline := '29218368,8062115," Benedict Canyon Equities, Inc",,,,,,,CLS,,,,,FAX,,,,,,,,,,,,,,,,,,INVOICE';
select level as lvl,
  replace(regexp_substr(replace(:v_lastline, ',', ',§'),
    '(§"[^"]*"|[^,]+)', 1, level), '§', null) as element
from dual
connect by regexp_substr(replace(:v_lastline, ',', ',§'),
  '(§"[^"]*"|[^,]+)', 1, level) is not null;

       LVL ELEMENT                                
---------- ----------------------------------------
         1 29218368                                
         2 8062115                                 
         3 " Benedict Canyon Equities, Inc"        
         4                                         
...
         9                                         
        10 CLS                                     
        11                                         
...
        14                                         
        15 FAX                                     
        16                                         
...
        32                                         
        33 INVOICE                                 

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...