如何使用正则表达式作为 CSV 分隔符创建将 CSV 值转换为 table 的函数?
How create a function for converting a CSV value into a table, using regular expression as a CSV separator?
我需要一个通用的 Oracle 函数,它将 CSV 字符串作为第一个参数和一个正则表达式字符串,它将 CSV 分隔符定义为第二个参数,returns table 已解析的字符串如下所示:
输入数据:
NAME PROJECT ERROR
108 test string-1, string-2 ; string-3
109 test2 single string
110 test3 ab, ,c
输出数据:
NAME PROJECT ERROR
108 test string-1
108 test string-2
108 test string-3
109 test2 single string
110 test3 ab
110 test3 NULL
110 test3 c
不同来源 table 中的分隔符可能不同,因此我希望能够将它们动态指定为正则表达式。
如何使用以下代码创建通用函数:
with temp as
(
select 108 Name, 'test' Project, 'string-1 , string-2 ; string-3' Error from dual
union all
select 109, 'test2', 'single string' from dual
)
select distinct
t.name, t.project,
trim(regexp_substr(t.error, '[^,;]+', 1, levels.column_value)) as error
from
temp t,
table(cast(multiset(select level from dual connect by level <= length (regexp_replace(t.error, '[^,;]+')) + 1) as sys.OdciNumberList)) levels
order by name;
所以我在想一个函数,它接受以下参数和 returns 一个 table 字符串
CREATE OR REPLACE FUNCTION csvstr2tab(
p_str IN VARCHAR2,
p_sep_re IN VARCHAR2 DEFAULT '\s*[,;]\s*'
)
PS我用过this answer
更新: 请注意,我在这里使用缩写“CSV”只是为了解释输入字符串有多个值,由不同的分隔符分隔。我正在处理由人类编写的使用不同分隔符的自由文本。因此,在我的例子中,输入字符串 不必是正确的 CSV - 它只是一个由多个不同分隔符分隔的字符串。
也许是这样的。按照您的要求,我将其写为 PL/SQL 函数,但请注意,如果输入数据驻留在数据库中,则可以直接在 SQL.
中完成
为了说明,我调用了带有默认分隔符的函数。
如果您不熟悉流水线 table 函数,您可以在文档中阅读它们。
另请注意,在 Oracle 12.2 中,但在 12.1 中,您可以省略 table( )
运算符 - 您可以 select 直接“从函数”。
create type str_t as table of varchar2(4000);
/
create or replace function csvstr2tab(
p_str in varchar2,
p_sep_re in varchar2 default '\s*[,;]\s*'
)
return str_t
pipelined
as
begin
for i in 1 .. regexp_count(p_str, p_sep_re) + 1 loop
pipe row (regexp_substr(p_str, '(.*?)(' || p_sep_re || '|$)', 1, i, null, 1));
end loop;
return;
end;
/
select *
from table(csvstr2tab('blue ;green,,brown;,yellow;'))
;
COLUMN_VALUE
--------------------
blue
green
[NULL]
brown
[NULL]
yellow
[NULL]
再测试一次(注意输出中的第一行也有两个尾随空格):
select *
from table(csvstr2tab('blue ;green,,brown;,yellow;', ';'))
;
COLUMN_VALUE
-----------------
blue
green,,brown
,yellow
编辑
以下是当输入位于 table 中(例如,由 ID 标识的行)时如何使用该函数将输入字符串分解为标记,并跟踪标记顺序的方法。
with
sample_data(id, str) as (
select 1201, 'blue ;green,,brown;,yellow;' from dual union all
select 1202, 'tinker, tailor, soldier, ...' from dual
)
select sd.id, sd.str, tf.ord, tf.token
from sample_data sd,
lateral ( select rownum as ord, column_value as token
from table(csvstr2tab(sd.str))
) tf
order by id, ord
;
ID STR ORD TOKEN
------ ---------------------------- ------ --------
1201 blue ;green,,brown;,yellow; 1 blue
1201 blue ;green,,brown;,yellow; 2 green
1201 blue ;green,,brown;,yellow; 3
1201 blue ;green,,brown;,yellow; 4 brown
1201 blue ;green,,brown;,yellow; 5
1201 blue ;green,,brown;,yellow; 6 yellow
1201 blue ;green,,brown;,yellow; 7
1202 tinker, tailor, soldier, ... 1 tinker
1202 tinker, tailor, soldier, ... 2 tailor
1202 tinker, tailor, soldier, ... 3 soldier
1202 tinker, tailor, soldier, ... 4 ...
您可以使用REGEXP_INSTR
来跟踪正则表达式匹配的开始和结束,这样,在每次迭代中,正则表达式不需要从字符串的开头重新开始匹配。
CREATE FUNCTION regexp_split(
value IN VARCHAR2,
regexp_separator IN VARCHAR2 DEFAULT ','
) RETURN string_list PIPELINED DETERMINISTIC
AS
position PLS_INTEGER := 1;
next_position PLS_INTEGER;
BEGIN
IF value IS NULL THEN
RETURN;
END IF;
LOOP
next_position := REGEXP_INSTR( value, regexp_separator, position, 1, 0 );
IF next_position = 0 THEN
PIPE ROW ( SUBSTR( value, position ) );
EXIT;
ELSE
PIPE ROW ( SUBSTR( value, position, next_position - position ) );
position := REGEXP_INSTR( value, regexp_separator, next_position, 1, 1 );
END IF;
END LOOP;
RETURN;
END;
/
(注:也可以使函数DETERMINISTIC
.)
那么,对于测试数据:
CREATE TABLE table_name ( NAME, PROJECT, ERROR ) AS
SELECT 108, 'test1', 'string-1, string-2 ; string-3' FROM DUAL UNION ALL
SELECT 109, 'test2', 'single string' FROM DUAL UNION ALL
SELECT 110, 'test3', 'ab, ,c' FROM DUAL UNION ALL
SELECT 111, 'test4', '1,2,;5,,,9' FROM DUAL;
您可以使用带有 CROSS APPLY
(或 LATERAL
连接)的函数来拆分字符串:
SELECT t.name,
t.project,
s.COLUMN_VALUE AS error
FROM table_name t
CROSS APPLY TABLE( regexp_split( error, '\s*[,;]\s*' ) ) s
输出:
NAME | PROJECT | ERROR
---: | :------ | :------------
108 | test1 | string-1
108 | test1 | string-2
108 | test1 | string-3
109 | test2 | single string
110 | test3 | ab
110 | test3 | null
110 | test3 | c
111 | test4 | 1
111 | test4 | 2
111 | test4 | null
111 | test4 | 5
111 | test4 | null
111 | test4 | null
111 | test4 | 9
db<>fiddle here
尝试下面的可重现示例。方案编制:
create table prjerr as
select 108 Name, 'test' Project, 'string-1 , string-2 ; string-3' Error from dual
union all
select 109, 'test2', 'single string' from dual
/
create or replace type tokenList is table of varchar2 (32767)
/
函数实现:
create or replace function csvstr2tab (
str varchar2, delimiter char := '\s*[,;]\s*') return tokenList is
pattern constant varchar2 (64) := '(.*?)(('||delimiter||')|($))';
tokens tokenList := tokenList ();
s varchar2 (96);
c int := 0;
begin
<<split>> loop c := c + 1;
s := regexp_substr (str, pattern, 1, c, null, 1);
exit split when s is null;
tokens.extend;
tokens(tokens.last) := s;
end loop;
return tokens;
end csvstr2tab;
/
函数的用法和结果:
select distinct name, project, t.column_value error
from prjerr p, csvstr2tab (p.error) t
order by name
/
NAME PROJE ERROR
---------- ----- ----------------
108 test string-1
108 test string-2
108 test string-3
109 test2 single string
PS 在版本 12.2.0.1.0
上测试
如果您的数据库中安装了 APEX,则有一个名为 APEX_STRING.SPLIT 的函数可以完全满足您的需求。您可以传递可用于拆分字符串的单个字符或正则表达式。该函数还有一个重载版本,因此可以使用相同的调用来拆分 VARCHAR2
或 CLOB
.
WITH
test_data (NAME, PROJECT, ERROR)
AS
(SELECT 108, 'test', 'string-1, string-2 ; string-3' FROM DUAL
UNION ALL
SELECT 109, 'test2', 'single string' FROM DUAL
UNION ALL
SELECT 110, 'test3', 'ab, ,c' FROM DUAL)
SELECT name,
project,
error,
TRIM (s.COLUMN_VALUE) as split_value
FROM test_data td, TABLE (apex_string.split (error, '[,;]')) s;
NAME PROJECT ERROR SPLIT_VALUE
_______ __________ ________________________________ ________________
108 test string-1, string-2 ; string-3 string-1
108 test string-1, string-2 ; string-3 string-2
108 test string-1, string-2 ; string-3 string-3
109 test2 single string single string
110 test3 ab, ,c ab
110 test3 ab, ,c
110 test3 ab, ,c c
我需要一个通用的 Oracle 函数,它将 CSV 字符串作为第一个参数和一个正则表达式字符串,它将 CSV 分隔符定义为第二个参数,returns table 已解析的字符串如下所示:
输入数据:
NAME PROJECT ERROR
108 test string-1, string-2 ; string-3
109 test2 single string
110 test3 ab, ,c
输出数据:
NAME PROJECT ERROR
108 test string-1
108 test string-2
108 test string-3
109 test2 single string
110 test3 ab
110 test3 NULL
110 test3 c
不同来源 table 中的分隔符可能不同,因此我希望能够将它们动态指定为正则表达式。
如何使用以下代码创建通用函数:
with temp as
(
select 108 Name, 'test' Project, 'string-1 , string-2 ; string-3' Error from dual
union all
select 109, 'test2', 'single string' from dual
)
select distinct
t.name, t.project,
trim(regexp_substr(t.error, '[^,;]+', 1, levels.column_value)) as error
from
temp t,
table(cast(multiset(select level from dual connect by level <= length (regexp_replace(t.error, '[^,;]+')) + 1) as sys.OdciNumberList)) levels
order by name;
所以我在想一个函数,它接受以下参数和 returns 一个 table 字符串
CREATE OR REPLACE FUNCTION csvstr2tab(
p_str IN VARCHAR2,
p_sep_re IN VARCHAR2 DEFAULT '\s*[,;]\s*'
)
PS我用过this answer
更新: 请注意,我在这里使用缩写“CSV”只是为了解释输入字符串有多个值,由不同的分隔符分隔。我正在处理由人类编写的使用不同分隔符的自由文本。因此,在我的例子中,输入字符串 不必是正确的 CSV - 它只是一个由多个不同分隔符分隔的字符串。
也许是这样的。按照您的要求,我将其写为 PL/SQL 函数,但请注意,如果输入数据驻留在数据库中,则可以直接在 SQL.
中完成为了说明,我调用了带有默认分隔符的函数。
如果您不熟悉流水线 table 函数,您可以在文档中阅读它们。
另请注意,在 Oracle 12.2 中,但在 12.1 中,您可以省略 table( )
运算符 - 您可以 select 直接“从函数”。
create type str_t as table of varchar2(4000);
/
create or replace function csvstr2tab(
p_str in varchar2,
p_sep_re in varchar2 default '\s*[,;]\s*'
)
return str_t
pipelined
as
begin
for i in 1 .. regexp_count(p_str, p_sep_re) + 1 loop
pipe row (regexp_substr(p_str, '(.*?)(' || p_sep_re || '|$)', 1, i, null, 1));
end loop;
return;
end;
/
select *
from table(csvstr2tab('blue ;green,,brown;,yellow;'))
;
COLUMN_VALUE
--------------------
blue
green
[NULL]
brown
[NULL]
yellow
[NULL]
再测试一次(注意输出中的第一行也有两个尾随空格):
select *
from table(csvstr2tab('blue ;green,,brown;,yellow;', ';'))
;
COLUMN_VALUE
-----------------
blue
green,,brown
,yellow
编辑
以下是当输入位于 table 中(例如,由 ID 标识的行)时如何使用该函数将输入字符串分解为标记,并跟踪标记顺序的方法。
with
sample_data(id, str) as (
select 1201, 'blue ;green,,brown;,yellow;' from dual union all
select 1202, 'tinker, tailor, soldier, ...' from dual
)
select sd.id, sd.str, tf.ord, tf.token
from sample_data sd,
lateral ( select rownum as ord, column_value as token
from table(csvstr2tab(sd.str))
) tf
order by id, ord
;
ID STR ORD TOKEN
------ ---------------------------- ------ --------
1201 blue ;green,,brown;,yellow; 1 blue
1201 blue ;green,,brown;,yellow; 2 green
1201 blue ;green,,brown;,yellow; 3
1201 blue ;green,,brown;,yellow; 4 brown
1201 blue ;green,,brown;,yellow; 5
1201 blue ;green,,brown;,yellow; 6 yellow
1201 blue ;green,,brown;,yellow; 7
1202 tinker, tailor, soldier, ... 1 tinker
1202 tinker, tailor, soldier, ... 2 tailor
1202 tinker, tailor, soldier, ... 3 soldier
1202 tinker, tailor, soldier, ... 4 ...
您可以使用REGEXP_INSTR
来跟踪正则表达式匹配的开始和结束,这样,在每次迭代中,正则表达式不需要从字符串的开头重新开始匹配。
CREATE FUNCTION regexp_split(
value IN VARCHAR2,
regexp_separator IN VARCHAR2 DEFAULT ','
) RETURN string_list PIPELINED DETERMINISTIC
AS
position PLS_INTEGER := 1;
next_position PLS_INTEGER;
BEGIN
IF value IS NULL THEN
RETURN;
END IF;
LOOP
next_position := REGEXP_INSTR( value, regexp_separator, position, 1, 0 );
IF next_position = 0 THEN
PIPE ROW ( SUBSTR( value, position ) );
EXIT;
ELSE
PIPE ROW ( SUBSTR( value, position, next_position - position ) );
position := REGEXP_INSTR( value, regexp_separator, next_position, 1, 1 );
END IF;
END LOOP;
RETURN;
END;
/
(注:也可以使函数DETERMINISTIC
.)
那么,对于测试数据:
CREATE TABLE table_name ( NAME, PROJECT, ERROR ) AS
SELECT 108, 'test1', 'string-1, string-2 ; string-3' FROM DUAL UNION ALL
SELECT 109, 'test2', 'single string' FROM DUAL UNION ALL
SELECT 110, 'test3', 'ab, ,c' FROM DUAL UNION ALL
SELECT 111, 'test4', '1,2,;5,,,9' FROM DUAL;
您可以使用带有 CROSS APPLY
(或 LATERAL
连接)的函数来拆分字符串:
SELECT t.name,
t.project,
s.COLUMN_VALUE AS error
FROM table_name t
CROSS APPLY TABLE( regexp_split( error, '\s*[,;]\s*' ) ) s
输出:
NAME | PROJECT | ERROR ---: | :------ | :------------ 108 | test1 | string-1 108 | test1 | string-2 108 | test1 | string-3 109 | test2 | single string 110 | test3 | ab 110 | test3 | null 110 | test3 | c 111 | test4 | 1 111 | test4 | 2 111 | test4 | null 111 | test4 | 5 111 | test4 | null 111 | test4 | null 111 | test4 | 9
db<>fiddle here
尝试下面的可重现示例。方案编制:
create table prjerr as
select 108 Name, 'test' Project, 'string-1 , string-2 ; string-3' Error from dual
union all
select 109, 'test2', 'single string' from dual
/
create or replace type tokenList is table of varchar2 (32767)
/
函数实现:
create or replace function csvstr2tab (
str varchar2, delimiter char := '\s*[,;]\s*') return tokenList is
pattern constant varchar2 (64) := '(.*?)(('||delimiter||')|($))';
tokens tokenList := tokenList ();
s varchar2 (96);
c int := 0;
begin
<<split>> loop c := c + 1;
s := regexp_substr (str, pattern, 1, c, null, 1);
exit split when s is null;
tokens.extend;
tokens(tokens.last) := s;
end loop;
return tokens;
end csvstr2tab;
/
函数的用法和结果:
select distinct name, project, t.column_value error
from prjerr p, csvstr2tab (p.error) t
order by name
/
NAME PROJE ERROR
---------- ----- ----------------
108 test string-1
108 test string-2
108 test string-3
109 test2 single string
PS 在版本 12.2.0.1.0
上测试如果您的数据库中安装了 APEX,则有一个名为 APEX_STRING.SPLIT 的函数可以完全满足您的需求。您可以传递可用于拆分字符串的单个字符或正则表达式。该函数还有一个重载版本,因此可以使用相同的调用来拆分 VARCHAR2
或 CLOB
.
WITH
test_data (NAME, PROJECT, ERROR)
AS
(SELECT 108, 'test', 'string-1, string-2 ; string-3' FROM DUAL
UNION ALL
SELECT 109, 'test2', 'single string' FROM DUAL
UNION ALL
SELECT 110, 'test3', 'ab, ,c' FROM DUAL)
SELECT name,
project,
error,
TRIM (s.COLUMN_VALUE) as split_value
FROM test_data td, TABLE (apex_string.split (error, '[,;]')) s;
NAME PROJECT ERROR SPLIT_VALUE
_______ __________ ________________________________ ________________
108 test string-1, string-2 ; string-3 string-1
108 test string-1, string-2 ; string-3 string-2
108 test string-1, string-2 ; string-3 string-3
109 test2 single string single string
110 test3 ab, ,c ab
110 test3 ab, ,c
110 test3 ab, ,c c