如何处理 Oracle 中包含逗号分隔或范围字符串值的列
How to process a column that holds a comma-separated or range string values in Oracle
使用 Oracle 12c DB,我有以下 table 数据示例,我在使用 SQL 和 PL/SQL.
时需要帮助
Table数据如下:
Table Name: my_data
ID ITEM ITEM_LOC
------- ----------- ----------------
1 Item-1 0,1
2 Item-2 0,1,2,3,4,7
3 Item-3 0-48
4 Item-4 0,1,2,3,4,5,6,7,8
5 Item-5 1-33
6 Item-6 0,1
7 Item-7 0,1,5,8
在 my_data
table 中使用上面的数据,处理此 ITEM_LOC 的最佳方法是什么,因为我需要将此列中的值用作单独的值,即:
0,1 表示 SQL 需要 return 0 或 1 或
范围值,即:
0-48 表示 SQL 需要 return 0 到 48 之间的值。
这两种情况的 returned 值应该从最低到最高开始,并且一旦处理就不能重复使用。
基于以上所述,根据我上面的描述,如果有一个函数接受 ID 和 returns 来自 ITEM_LOC 的一个尚未使用的单独值,那就太好了.这可以是逗号分隔的字符串值或范围字符串值。
ID = 2 的预期结果可能是 7。对于此 ID = 2,ITEM_LOC = 7 无法再次使用。
ID = 5 的预期结果可能是 31。对于此 ID = 5,ITEM_LOC = 31 无法再次使用。
对于无法再次使用的 ITEM_LOC 数据,针对该 ID,我正在考虑持有另一个 table 来保存此数据,或者可能将所有数据分成具有新列的单独行称为 VALUE_USED.
此查询显示如何根据 ITEM_LOC
值的列表以逗号分隔(表示 "take exactly those values")或破折号分隔(表示 "find all values between starting and end point")来提取它们。我稍微修改了您的示例数据(如果其中 5 个完成工作,我不想显示 ~50 个值)。
- 第 1 - 6 行代表示例数据。
- 第一个
select
(第 7 - 15 行)将逗号分隔值拆分为行
- 第二个
select
(第 17 - 26 行)使用分层查询,将 1
添加到起始值,直到项目的结束值。
SQL> with my_data (id, item, item_loc) as
2 (select 2, 'Item-2', '0,2,4,7' from dual union all
3 select 7, 'Item-7', '0,1,5' from dual union all
4 select 3, 'Item-3', '0-4' from dual union all
5 select 8, 'Item-8', '5-8' from dual
6 )
7 select id,
8 item,
9 regexp_substr(item_loc, '[^,]+', 1, column_value) loc
10 from my_data
11 cross join table(cast(multiset
12 (select level from dual
13 connect by level <= regexp_count(item_loc, ',') + 1
14 ) as sys.odcinumberlist))
15 where instr(item_loc, '-') = 0
16 union all
17 select id,
18 item,
19 to_char(to_number(regexp_substr(item_loc, '^\d+')) + column_value - 1) loc
20 from my_data
21 cross join table(cast(multiset
22 (select level from dual
23 connect by level <= to_number(regexp_substr(item_loc, '\d+$')) -
24 to_number(regexp_substr(item_loc, '^\d+')) + 1
25 ) as sys.odcinumberlist))
26 where instr(item_loc, '-') > 0
27 order by id, item, loc;
ID ITEM LOC
---------- ------ ----------------------------------------
2 Item-2 0
2 Item-2 2
2 Item-2 4
2 Item-2 7
3 Item-3 0
3 Item-3 1
3 Item-3 2
3 Item-3 3
3 Item-3 4
7 Item-7 0
7 Item-7 1
7 Item-7 5
8 Item-8 5
8 Item-8 6
8 Item-8 7
8 Item-8 8
16 rows selected.
SQL>
我不知道你说 "item_loc could not be used again" 是什么意思。用过哪里?例如,如果您在游标 FOR
循环中使用上述查询,那么是的 - 这些值将仅使用一次,因为每个循环迭代都会获取下一个 item_loc
值。
正如其他人所说,以这种方式存储 数据是个坏主意。您很可能会像这样 input,并且您可能需要像这样 display 数据,但您不必存储数据的输入或显示方式。
我将根据输入将数据存储为单独的 LOC
元素。我假设数据仅包含以逗号分隔的整数,或以连字符分隔的整数对。空格被忽略。逗号分隔的列表不必按任何顺序排列。成对地,如果左边的整数大于右边的整数我return没有LOC
个元素。
create table t as
with input(id, item, item_loc) as (
select 1, 'Item-1', ' 0,1' from dual union all
select 2, 'Item-2', '0,1,2,3,4,7' from dual union all
select 3, 'Item-3', '0-48' from dual union all
select 4, 'Item-4', '0,1,2,3,4,5,6,7,8' from dual union all
select 5, 'Item-5', '1-33' from dual union all
select 6, 'Item-6', '0,1' from dual union all
select 7, 'Item-7', '0,1,5,8,7 - 11' from dual
)
select distinct id, item, loc from input, xmltable(
'let $item := if (contains($X,",")) then ora:tokenize($X,"\,") else $X
for $i in $item
let $j := if (contains($i,"-")) then ora:tokenize($i,"\-") else $i
for $k in xs:int($j[1]) to xs:int($j[count($j)])
return $k'
passing item_loc as X
columns loc number path '.'
);
现在 "use" 一个元素,我只是从 table 中删除它:
delete from t where rowid = (
select min(rowid) keep (dense_rank first order by loc)
from t
where id = 7
);
要return 与输入格式相同的数据,请使用MATCH_RECOGNIZE
:
select id, item, listagg(item_loc, ',') within group(order by first_loc) item_loc
from t
match_recognize(
partition by id, item order by loc
measures a.loc first_loc,
a.loc || case count(*) when 1 then null else '-'||b.loc end item_loc
pattern (a b*)
define b as loc = prev(loc) + 1
)
group by id, item;
ID ITEM ITEM_LOC
1 Item-1 0-1
2 Item-2 0-4,7
3 Item-3 0-48
4 Item-4 0-8
5 Item-5 1-33
6 Item-6 0-1
7 Item-7 1,5,7-11
注意这里的输出不会和输入完全一样,因为任何连续的整数都会被压缩成一对。
使用 Oracle 12c DB,我有以下 table 数据示例,我在使用 SQL 和 PL/SQL.
时需要帮助Table数据如下:
Table Name: my_data
ID ITEM ITEM_LOC
------- ----------- ----------------
1 Item-1 0,1
2 Item-2 0,1,2,3,4,7
3 Item-3 0-48
4 Item-4 0,1,2,3,4,5,6,7,8
5 Item-5 1-33
6 Item-6 0,1
7 Item-7 0,1,5,8
在 my_data
table 中使用上面的数据,处理此 ITEM_LOC 的最佳方法是什么,因为我需要将此列中的值用作单独的值,即:
0,1 表示 SQL 需要 return 0 或 1 或
范围值,即:
0-48 表示 SQL 需要 return 0 到 48 之间的值。
这两种情况的 returned 值应该从最低到最高开始,并且一旦处理就不能重复使用。
基于以上所述,根据我上面的描述,如果有一个函数接受 ID 和 returns 来自 ITEM_LOC 的一个尚未使用的单独值,那就太好了.这可以是逗号分隔的字符串值或范围字符串值。
ID = 2 的预期结果可能是 7。对于此 ID = 2,ITEM_LOC = 7 无法再次使用。
ID = 5 的预期结果可能是 31。对于此 ID = 5,ITEM_LOC = 31 无法再次使用。
对于无法再次使用的 ITEM_LOC 数据,针对该 ID,我正在考虑持有另一个 table 来保存此数据,或者可能将所有数据分成具有新列的单独行称为 VALUE_USED.
此查询显示如何根据 ITEM_LOC
值的列表以逗号分隔(表示 "take exactly those values")或破折号分隔(表示 "find all values between starting and end point")来提取它们。我稍微修改了您的示例数据(如果其中 5 个完成工作,我不想显示 ~50 个值)。
- 第 1 - 6 行代表示例数据。
- 第一个
select
(第 7 - 15 行)将逗号分隔值拆分为行 - 第二个
select
(第 17 - 26 行)使用分层查询,将1
添加到起始值,直到项目的结束值。
SQL> with my_data (id, item, item_loc) as
2 (select 2, 'Item-2', '0,2,4,7' from dual union all
3 select 7, 'Item-7', '0,1,5' from dual union all
4 select 3, 'Item-3', '0-4' from dual union all
5 select 8, 'Item-8', '5-8' from dual
6 )
7 select id,
8 item,
9 regexp_substr(item_loc, '[^,]+', 1, column_value) loc
10 from my_data
11 cross join table(cast(multiset
12 (select level from dual
13 connect by level <= regexp_count(item_loc, ',') + 1
14 ) as sys.odcinumberlist))
15 where instr(item_loc, '-') = 0
16 union all
17 select id,
18 item,
19 to_char(to_number(regexp_substr(item_loc, '^\d+')) + column_value - 1) loc
20 from my_data
21 cross join table(cast(multiset
22 (select level from dual
23 connect by level <= to_number(regexp_substr(item_loc, '\d+$')) -
24 to_number(regexp_substr(item_loc, '^\d+')) + 1
25 ) as sys.odcinumberlist))
26 where instr(item_loc, '-') > 0
27 order by id, item, loc;
ID ITEM LOC
---------- ------ ----------------------------------------
2 Item-2 0
2 Item-2 2
2 Item-2 4
2 Item-2 7
3 Item-3 0
3 Item-3 1
3 Item-3 2
3 Item-3 3
3 Item-3 4
7 Item-7 0
7 Item-7 1
7 Item-7 5
8 Item-8 5
8 Item-8 6
8 Item-8 7
8 Item-8 8
16 rows selected.
SQL>
我不知道你说 "item_loc could not be used again" 是什么意思。用过哪里?例如,如果您在游标 FOR
循环中使用上述查询,那么是的 - 这些值将仅使用一次,因为每个循环迭代都会获取下一个 item_loc
值。
正如其他人所说,以这种方式存储 数据是个坏主意。您很可能会像这样 input,并且您可能需要像这样 display 数据,但您不必存储数据的输入或显示方式。
我将根据输入将数据存储为单独的 LOC
元素。我假设数据仅包含以逗号分隔的整数,或以连字符分隔的整数对。空格被忽略。逗号分隔的列表不必按任何顺序排列。成对地,如果左边的整数大于右边的整数我return没有LOC
个元素。
create table t as
with input(id, item, item_loc) as (
select 1, 'Item-1', ' 0,1' from dual union all
select 2, 'Item-2', '0,1,2,3,4,7' from dual union all
select 3, 'Item-3', '0-48' from dual union all
select 4, 'Item-4', '0,1,2,3,4,5,6,7,8' from dual union all
select 5, 'Item-5', '1-33' from dual union all
select 6, 'Item-6', '0,1' from dual union all
select 7, 'Item-7', '0,1,5,8,7 - 11' from dual
)
select distinct id, item, loc from input, xmltable(
'let $item := if (contains($X,",")) then ora:tokenize($X,"\,") else $X
for $i in $item
let $j := if (contains($i,"-")) then ora:tokenize($i,"\-") else $i
for $k in xs:int($j[1]) to xs:int($j[count($j)])
return $k'
passing item_loc as X
columns loc number path '.'
);
现在 "use" 一个元素,我只是从 table 中删除它:
delete from t where rowid = (
select min(rowid) keep (dense_rank first order by loc)
from t
where id = 7
);
要return 与输入格式相同的数据,请使用MATCH_RECOGNIZE
:
select id, item, listagg(item_loc, ',') within group(order by first_loc) item_loc
from t
match_recognize(
partition by id, item order by loc
measures a.loc first_loc,
a.loc || case count(*) when 1 then null else '-'||b.loc end item_loc
pattern (a b*)
define b as loc = prev(loc) + 1
)
group by id, item;
ID ITEM ITEM_LOC
1 Item-1 0-1
2 Item-2 0-4,7
3 Item-3 0-48
4 Item-4 0-8
5 Item-5 1-33
6 Item-6 0-1
7 Item-7 1,5,7-11
注意这里的输出不会和输入完全一样,因为任何连续的整数都会被压缩成一对。