【问题标题】:How to process a column that holds a comma-separated or range string values in Oracle如何在 Oracle 中处理包含逗号分隔或范围字符串值的列
【发布时间】:2020-04-07 06:32:31
【问题描述】:

使用 Oracle 12c DB,我有以下表格数据示例,在使用 SQL 和 PL/SQL 时需要帮助。

表格数据如下:

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 表中的上述数据,处理此 ITEM_LOC 的最佳方法是什么,因为我需要将此列中的值用作单个值,即:

0,1 表示 SQL 需要返回 0 或 1 或

范围值,即:

0-48 表示 SQL 需要返回 0 到 48 之间的值。

两种场景的返回值都应该从最低到最高开始,一旦处理就不能重复使用。

基于以上所述,根据我上面的描述,如果有一个函数可以获取 ID 并从 ITEM_LOC 返回一个尚未使用的单独值,那就太好了。这可以是逗号分隔的字符串值或范围字符串值。

ID = 2 的期望结果可能是 7。对于这个 ID = 2,ITEM_LOC = 7 不能再次使用。

ID = 5 的期望结果可能是 31。对于这个 ID = 5,ITEM_LOC = 31 不能再次使用。

对于无法再次使用的 ITEM_LOC 数据,针对该 ID,我正在考虑使用另一个表来保存该 ID,或者可能将所有数据分成单独的行,并使用一个名为 VALUE_USED 的新列。

【问题讨论】:

  • 永远不要将数据存储为逗号分隔的项目。只会给你带来很多麻烦。
  • “不能重复使用”是否意味着该函数还应该删除从存储在列中的列表中返回的 ID(即同时更新表)
  • @jarlh - 完全同意你的看法,但这是预先存在的数据。
  • @a_horse_with_no_name "can't be re-used" 表示 ID 可以重复使用,但 ITEM_LOC 列中的值(如果已使用)不能再次使用。例如,我只能为此 ID 使用 ITEM_LOC 值六次,即 0、1、2、3、4、7。如果我使用了 0,那么我的下一个可用值将是 1 等等。希望这是有道理的。
  • 但是您将使用 ID 的信息存储在哪里?从列表中删除使用过的似乎是唯一的选择(考虑到您损坏的数据库设计)。或者你有另一个存储使用值的表吗?

标签: sql oracle plsql oracle12c


【解决方案1】:

这个查询展示了如何提取 ITEM_LOC 值的列表,基于它们是逗号分隔(这意味着“完全取那些值”)还是破折号分隔(这意味着“查找起点和终点之间的所有值” )。我稍微修改了您的示例数据(如果其中有 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 不能再次使用”是什么意思。 在哪里用过?例如,如果您在游标FOR 循环中使用上述查询,则可以 - 这些值将仅使用一次,因为每次循环迭代都会获取下一个 item_loc 值。

【讨论】:

  • 请参阅上面我对@a_horse_with_no_name 的评论,关于“item_loc 无法再次使用”。
  • 嗨@tonyf,您可以将此答案与以下链接中的答案一起使用,您可以在其中删除不再使用的项目stackoverflow.com/questions/61097906/…
【解决方案2】:

正如其他人所说,以这种方式存储数据是个坏主意。您很可能会有这样的输入,并且您可能需要像这样显示数据,但您不必按照输入的方式存储数据或显示。

我将根据输入将数据存储为单独的LOC 元素。我假设数据仅包含由逗号分隔的整数,或由连字符分隔的整数对。空白被忽略。逗号分隔的列表不必按任何顺序排列。成对,如果左边的整数大于右边的整数,我不返回 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 '.'
);

现在要“使用”一个元素,我只是从表中删除它:

delete from t where rowid = (
  select min(rowid) keep (dense_rank first order by loc)
  from t
  where id = 7
);

要以与输入相同的格式返回数据,请使用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

注意这里的输出不会和输入完全一样,因为任何连续的整数都会被压缩成一对。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2016-05-28
    • 2014-11-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-08-19
    • 1970-01-01
    • 2020-08-01
    相关资源
    最近更新 更多