使用Oracle SQL查找逗号分隔字符串中的最大数字

时间:2016-10-24 13:35:28

标签: sql oracle plsql oracle10g aggregate-functions

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

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函数将修订存储在逗号分隔列表中。

我现在遇到的麻烦就是现场表,新版本已经添加,所以当我使用MINUS比较主表和快照时,我会有所不同,因为 主表中的新修订版。

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

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

7 个答案:

答案 0 :(得分:3)

享受。

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
;

答案 1 :(得分:2)

一种方法是使用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))
    )

答案 2 :(得分:2)

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

修改:如果你喜欢这个解决方案,请查看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         

答案 3 :(得分:1)

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

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作为参考。我猜你桌上有一些内容。

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

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);

答案 4 :(得分:1)

您可以使用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); 

答案 5 :(得分:1)

这可能是一种方式:

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。

答案 6 :(得分:0)

您可以编写解析字符串并返回最大数字的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;
/