【问题标题】:Finding out the highest number in a comma separated string using Oracle SQL使用 Oracle SQL 找出逗号分隔字符串中的最大数字
【发布时间】:2017-03-06 07:35:30
【问题描述】:

我有一个包含两列的表格:

OLD_REVISIONS   |NEW_REVISIONS
-----------------------------------
1,25,26,24      |1,26,24,25
1,56,55,54      |1,55,54
1               |1
1,2             |1
1,96,95,94      |1,96,94,95
1               |1
1               |1
1               |1
1               |1
1,2             |1,2
1               |1
1               |1
1               |1
1               |1
  • 对于每一行,都会有一个文档的修订列表(逗号分隔)

  • 逗号分隔的列表在两列中可能相同,但顺序/排序可能不同 - 例如

    2,1 |1,2

我想找出 OLD_REVISIONS 列中的最高版本低于 NEW_REVISIONS 中的最高版本的所有实例

以下内容符合该标准

OLD_REVISIONS   |NEW_REVISIONS
-----------------------------------
1,2             |1
1,56,55,54      |1,55,54
  • 我尝试了使用 MINUS 选项的解决方案(将表连接到自身),但即使列表相同但顺序错误,它也会返回差异

  • 我尝试了函数 GREATEST(即greatest(new_Revisions) < greatest(old_revisions)),但我不确定为什么最大(OLD_REVISIONS)总是只返回逗号分隔值。它不返回最大值。我怀疑它正在比较字符串,因为列是 VARCHAR。

另外,MAX 函数需要一个数字。

还有其他方法可以实现上述目标吗?我正在寻找一个纯 SQL 选项,以便我可以打印出结果(或可以打印出结果的 PL/SQL 选项)

编辑

很抱歉没有提及这一点,但对于 NEW_REVISIONS,我确实将数据放在一个表中,其中每个修订版都位于单独的行中:

"DOCNUMBER" "REVISIONNUMBER"
67          1
67          24
67          25
67          26
75          1
75          54
75          55
75          56
78          1
79          1
79          2
83          1
83          96
83          94

只是为了提供一些内容,几周前我怀疑有修订消失了。 为了对此进行调查,我决定对所有文档的所有修订进行计数,并拍摄快照以便稍后进行比较,以查看是否确实缺少修订。

我拍摄的快照包含以下列:

docnumber, count, revisions

使用 listagg 函数将修订存储在逗号分隔的列表中。

我现在遇到的问题是实时表,已经添加了新的修订,所以当我使用减号比较主表和快照时,我得到了不同,因为 主表中的新修订。

尽管在实际表中修订是单独的行,但在快照表中我没有单独的行。

我正在考虑以相同格式重新创建快照并比较它们的唯一方法,以了解主表中的最大修订是否低于快照表中的最大修订(因此为什么我试图找出如何找到以逗号分隔的字符串中的最大值)

【问题讨论】:

  • 这是表示这些值的错误方式。每个修订版应该有一行,而不是将一堆修订版塞进一个字符串中。
  • 如果正确规范化数据模型,查询将非常简单
  • 将此表拆分为两个表(旧表和新表),您还应该将逗号分隔的值拆分为行。之后就很简单了。
  • 你尝试的一切都失败了,原因很简单——你目前在表中的是字符串;它们是用逗号分隔的数字这一事实无关紧要。您所做的任何比较(包括与max()greatest())只会将它们作为字符串进行比较。您需要将字符串分解为其组件编号。
  • 在您修改后,我已经修改了我的答案。

标签: sql oracle plsql oracle10g aggregate-functions


【解决方案1】:

享受吧。

select    xmlcast(xmlquery(('max((' || OLD_REVISIONS || '))') RETURNING CONTENT) as int) as OLD_REVISIONS_max
         ,xmlcast(xmlquery(('max((' || NEW_REVISIONS || '))') RETURNING CONTENT) as int) as NEW_REVISIONS_max

from      t
;

【讨论】:

  • @ziggy,请注意我添加了 xmlcast 函数。它适用于您在 Oracle 10g 上吗?
【解决方案2】:

您可以编写一个 PL/SQL 函数来解析字符串并返回最大数

select  max_num( '1,26,24,25') max_num from dual;
   MAX_NUM
----------
        26

查询很简单:

select OLD_REVISIONS  NEW_REVISIONS
from revs
where max_num(OLD_REVISIONS) < max_num(NEW_REVISIONS);

没有验证和错误处理的原型函数

create or replace function max_num(str_in VARCHAR2) return NUMBER as 
i number;
x varchar2(1);
n number := 0;
max_n number := 0;
pow number := 0;
begin
 for i in 0.. length(str_in)-1 loop
  x := substr(str_in,length(str_in)-i,1);
  if x = ',' then 
    -- check max number
    if n > max_n then 
       max_n := n;
    end if;   
    -- reset
    n := 0;
    pow := 0;
  else
    n := n + to_number(x)*power(10,pow);
    pow := pow +1;
  end if;
 end loop;
 return(max_n);
end;
/

【讨论】:

    【解决方案3】:

    假设您的基表有一个 id 列(what 的版本?) - 这是基于拆分行的解决方案。

    编辑:如果您喜欢这个解决方案,请查看 vkp 的解决方案,它比我的要好。我在对他的回答的评论中解释了为什么他的解决方案更好。

    with
         t ( id, old_revisions, new_revisions ) as (
           select 101, '1,25,26,24', '1,26,24,25' from dual union all
           select 102, '1,56,55,54', '1,55,54'    from dual union all
           select 103, '1'         , '1'          from dual union all
           select 104, '1,2'       , '1'          from dual union all
           select 105, '1,96,95,94', '1,96,94,95' from dual union all
           select 106, '1'         , '1'          from dual union all
           select 107, '1'         , '1'          from dual union all
           select 108, '1'         , '1'          from dual union all
           select 109, '1'         , '1'          from dual union all
           select 110, '1,2'       , '1,2'        from dual union all
           select 111, '1'         , '1'          from dual union all
           select 112, '1'         , '1'          from dual union all
           select 113, '1'         , '1'          from dual union all
           select 114, '1'         , '1'          from dual
           )
    --   END of TEST DATA; the actual solution (SQL query) begins below.
    select id, old_revisions, new_revisions
    from (
        select id, old_revisions, new_revisions, 'old' as flag,
               to_number(regexp_substr(old_revisions, '\d+', 1, level)) as rev_no
          from t
          connect by level <= regexp_count(old_revisions, ',') + 1
             and  prior id = id
              and prior sys_guid() is not null
        union all
        select id, old_revisions, new_revisions, 'new' as flag,
               to_number(regexp_substr(new_revisions, '\d+', 1, level)) as rev_no
          from t
          connect by level <= regexp_count(new_revisions, ',') + 1
             and  prior id = id
              and prior sys_guid() is not null
         )
    group by id, old_revisions, new_revisions
    having max(case when flag = 'old' then rev_no end) !=
           max(case when flag = 'new' then rev_no end)
    order by id           --   ORDER BY is optional
    ;
    
    
     ID OLD_REVISION NEW_REVISION
    --- ------------ ------------
    102 1,56,55,54   1,55,54   
    104 1,2          1         
    

    【讨论】:

      【解决方案4】:

      评论说规范化数据。我同意,但我也理解这可能是不可能的。我会尝试以下查询:

      select greatest(val1, val2), t1.r from (
      select max(val) val1, r from (
      select regexp_substr(v1,'[^,]+', 1, level) val, rowid r from tab1
        connect by regexp_substr(v1, '[^,]+', 1, level) is not null
        ) group by r) t1
        inner join (
      select max(val) val2, r from (
      select regexp_substr(v2,'[^,]+', 1, level) val, rowid r from tab1
        connect by regexp_substr(v2, '[^,]+', 1, level) is not null
        ) group by r) t2
        on (t1.r = t2.r);
      

      测试日期:

      create table tab1 (v1 varchar2(100), v2 varchar2(100));
      insert into tab1 values ('1,3,5','1,4,7');
      insert into tab1 values ('1,3,5','1,2,9');
      insert into tab1 values ('1,3,5','1,3,5');
      insert into tab1 values ('1,3,5','1,4');
      

      并且似乎工作正常。我留下了rowid以供参考。我猜你的表中有一些 id。

      编辑后,我会将查询更改为:

      select greatest(val1, val2), t1.r from (
      select max(val) val1, r from (
      select regexp_substr(v1,'[^,]+', 1, level) val, DOCNUMBER r from tab1
        connect by regexp_substr(v1, '[^,]+', 1, level) is not null
        ) group by DOCNUMBER) t1
        inner join (
      select max(DOCNUMBER) val2, DOCNUMBER r from NEW_REVISIONS) t2
        on (t1.r = t2.r);
      

      【讨论】:

        【解决方案5】:

        一种方法是使用regexp_substr 以逗号分隔分隔列,并检查最大值和最小值是否不同。

        Sample Demo

        with rownums as (select t.*,row_number() over(order by old_revisions) rn from t)
        select old_revisions,new_revisions 
        from rownums 
        where rn in (select rn
                     from rownums
                     group by rn
                     connect by regexp_substr(old_revisions, '[^,]+', 1, level) is not null 
                     or regexp_substr(new_revisions, '[^,]+', 1, level) is not null
                     having max(cast(regexp_substr(old_revisions,'[^,]+', 1, level) as int)) 
                      <> max(cast(regexp_substr(new_revisions,'[^,]+', 1, level) as int))
            )
        

        【讨论】:

        • 好点!拆分仅在 HAVING 子句中需要,其他任何地方都不需要。所以最好将拆分移动到HAVING 子句中,该子句仅在评估GROUP BY 之后才评估。因此,您无需担心prior rn = rn 并避免分层查询中的循环。记住的好技巧 - 脱帽并 +1!
        【解决方案6】:

        这可能是一种方式:

        select 
          OLD_REVISIONS,
          NEW_REVISIONS
        from 
          REVISIONS t,
          table(cast(multiset(
                                select level
                                from dual
                                connect by  level <= length (regexp_replace(t.OLD_REVISIONS, '[^,]+'))  + 1
                              ) as sys.OdciNumberList
                     )
               ) levels_old,
          table(cast(multiset(
                                select level
                                from dual
                                connect by  level <= length (regexp_replace(t.NEW_REVISIONS, '[^,]+'))  + 1
                             )as sys.OdciNumberList
                    )
               ) levels_new
        group by t.ROWID,
          OLD_REVISIONS,
          NEW_REVISIONS
        having max(to_number(trim(regexp_substr(t.OLD_REVISIONS, '[^,]+', 1, levels_old.column_value)))) >
               max(to_number(trim(regexp_substr(t.new_REVISIONS, '[^,]+', 1, levels_new.column_value))))
        

        这使用双字符串拆分从每个字段中选择值,然后简单地找到两个集合中的最大值符合您的要求的行。 您应该通过在 GROUP BYclause 中添加一些唯一键来编辑它,或者如果您的表上没有任何唯一键,则添加一个 rowid。

        【讨论】:

          【解决方案7】:

          您可以使用 listagg 函数以相同的顺序将修订放在一起来比较每个值。

          SELECT listagg(o,',') WITHIN GROUP (ORDER BY o) old_revisions,
                 listagg(n,',')  WITHIN GROUP (ORDER BY n) new_revisions
          FROM (
               SELECT DISTINCT  rowid r,
                      regexp_substr(old_revisions, '[^,]+', 1, LEVEL) o,
                      regexp_substr(new_revisions, '[^,]+', 1, LEVEL) n
               FROM   table
               WHERE  regexp_substr(old_revisions, '[^,]+', 1, LEVEL) IS NOT NULL
               CONNECT BY LEVEL<=(SELECT greatest(MAX(regexp_count(old_revisions,',')),MAX(regexp_count(new_revisions,',')))+1 c FROM table)
               )
          GROUP BY r
          HAVING listagg(o,',') WITHIN GROUP (ORDER BY o)<>listagg(n,',') WITHIN GROUP (ORDER BY n); 
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2017-12-24
            • 1970-01-01
            • 2021-10-09
            • 2023-01-03
            • 2016-07-19
            • 1970-01-01
            • 2020-08-30
            • 1970-01-01
            相关资源
            最近更新 更多