这里有一个简单的库存表,code代表的是序列号,numformat代表的是序列号的格式。每一条记录代表的是一条库存记录。
现在有这样的一个需求,把连续的序列号给范围给找出来。期望结果如下:
应该怎样来实现该需求?
方案1:
将整个表的数据载入到内存中,通过循环遍历,找到FormatA和FormatB的起始结束序列号。少量库存记录是没有问题的。但是如果库存记录达到了上千万条,显然容易出现内存溢出或者运行缓慢的问题。
方案2:
通过SQL将相同的Format格式的序列号按照顺序合并到一条记录。
Oracle 提供了XMLAGG函数可以将序列号按照一定的顺序拼接到一起,返回的类型是CLOB。LISTAGG 函数也提供类似的功能,但最大字符长度是4000。库存表序列号拼接到一起往往超过4000个字符,所以在这里选择的是XMLAGG函数。具体可以参考:https://blog.csdn.net/zqkwcyx/article/details/88663982
提供如下的SQL例子:
with inventory as(
select 'FormatA' as numFormat, 0001 as code from dual union
select 'FormatA' as numFormat, 0002 as code from dual union
select 'FormatA' as numFormat, 0003 as code from dual union
select 'FormatA' as numFormat, 0005 as code from dual union
select 'FormatA' as numFormat, 0006 as code from dual union
select 'FormatA' as numFormat, 0008 as code from dual union
select 'FormatB' as numFormat, 0001 as code from dual union
select 'FormatB' as numFormat, 0002 as code from dual union
select 'FormatB' as numFormat, 0005 as code from dual union
select 'FormatB' as numFormat, 0006 as code from dual union
select 'FormatB' as numFormat, 0008 as code from dual union
select 'FormatB' as numFormat, 0009 as code from dual
)
select numformat,rtrim(xmlagg(XMLELEMENT(e,orig.code,',').EXTRACT('//text()') order by orig.code).GetClobVal(),',') NumberClob from (
SELECT * FROM inventory
) orig
group by orig.numFormat
;
然后也是通过循环遍历,找到FormatA和FormatB的起始结束序列号。
比较方案1,查询出的记录数要少得多,不会出现方案1的问题。缺点是依然需要载入到内存中,进行循环遍历,对CPU损耗比较大。
方案3:
能否避免在内存中遍历,直接使用SQL找到序列号的范围呢?答案是有的,参考lag,lead函数。
https://blog.csdn.net/weixin_41287692/article/details/80577828
with inventory as(
select 'FormatA' as numFormat, 0001 as code from dual union
select 'FormatA' as numFormat, 0002 as code from dual union
select 'FormatA' as numFormat, 0003 as code from dual union
select 'FormatA' as numFormat, 0005 as code from dual union
select 'FormatA' as numFormat, 0006 as code from dual union
select 'FormatA' as numFormat, 0008 as code from dual union
select 'FormatB' as numFormat, 0001 as code from dual union
select 'FormatB' as numFormat, 0002 as code from dual union
select 'FormatB' as numFormat, 0005 as code from dual union
select 'FormatB' as numFormat, 0006 as code from dual union
select 'FormatB' as numFormat, 0008 as code from dual union
select 'FormatB' as numFormat, 0009 as code from dual
)
select numFormat,code startNum, nvl(lead(previousCode) over(partition BY numFormat order by previousCode nulls first ),maxn) endNum from(
select numFormat,
lag(code, 1) over(partition by numFormat order by code) previousCode, code,
max(code) over(partition by numFormat) maxn
from inventory
)
where nvl(code-previousCode-1,1) <> 0
;
注意SQL里面使用了 nulls first: 字段包含了null值,如果不使用nulls first 该条记录会放在最后最终导致范围查找出错。