这次要解决的问题就比较棘手了,比如我在CPU上面同时勾选了i3,i5,这种情况要怎么查询呢。假设我这四个参数,每个参数有四种选项,那么我就有可能组合256种不同的查询。这么多种查询,我们不可能一个一个的判断然后写出来。
不过首先,我们要解决的是一个参数怎么传递多个值,最容易让人想到的是分隔字符串。比如从前台拿到了参数“i3”和“i5”,那么我们在后台就把它以某种分隔符(比如“|”)把他们拼接成“i3|i5”然后就可以传递进去了。所以我们要解决的第一个难题就是怎么在存储过程中把这个字符串拆开?拆开了之后保存到哪里?保存了之后,怎么一个一个取出来使用?
我们首先会想,拆开了之后放在哪的问题,如果拆开了,不知道放在哪,那么拆分这步工作就没有意义。所以我们要建一个全局类型,一维数组。
CREATE OR REPLACE TYPE TYPE_VARCHAR IS TABLE OF VARCHAR2(2000);
然后就要写这个拆分函数,返回这个数组。
我们还是写在PKG_DEMO包里面
FUNCTION SPLIT(V_SRC IN VARCHAR2,V_CHAR IN VARCHAR2) RETURN TYPE_VARCHAR AS V_ARRAY TYPE_VARCHAR; SRC VARCHAR2(30000); IDX NUMBER; POSITION NUMBER; TEMP VARCHAR2(30000); BEGIN --如果参数为空,那么直接返回空。 IF V_SRC IS NULL THEN RETURN NULL; END IF; IDX:=1; SRC:=V_SRC; --初始化数组 V_ARRAY:=NEW TYPE_VARCHAR(); --INSTR函数是返回V_CHAR在SRC中第一次出现的位置 LOOP POSITION:=INSTR(SRC,V_CHAR); --如果在SRC中找不到V_CHAR了就要退出循环了 EXIT WHEN POSITION=0; --SRC截取0到POSITION TEMP:=SUBSTR(SRC,0,POSITION-1); --把截取的这一部分放到数组里面 V_ARRAY.EXTEND; V_ARRAY(IDX):=TEMP; --SRC把自己截去掉先前截取的元素和分隔符本身,在下一次循环中便排除掉第一个被截取出来的元素了。 SRC:=SUBSTR(SRC,POSITION+LENGTH(V_CHAR)); --测试打印出元素 DBMS_OUTPUT.PUT_LINE('ARRAY('||IDX||')='||V_ARRAY(IDX)); IDX:=IDX+1; END LOOP; --由于在循环里面SRC把自己截断的只剩下最后一个元素了,还没有加入到数组里面,便跳出了循环。所以我们需要在循环外面把最后一个元素补上。 V_ARRAY.EXTEND; V_ARRAY(IDX):=SRC; DBMS_OUTPUT.PUT_LINE('ARRAY('||IDX||')='||V_ARRAY(IDX)); RETURN V_ARRAY; END;
接下来,测试一下,看看能否打印出每一个元素。
DECLARE VY TYPE_VARCHAR; BEGIN VY:=PKG_DEMO.SPLIT('AAA||BBB||CCC||DDD','||'); END;
查看控制台。
ARRAY(1)=AAA
ARRAY(2)=BBB
ARRAY(3)=CCC
ARRAY(4)=DDD
看来达到了我们想要的效果,我们已经实现了拆分函数split了。
接下来我们要考虑怎么使用这个数组去做查询了。
--通常一个参数如果有多个值的查询是这样的 SELECT * FROM TEST WHERE CPU_ IN ('i3','i5'); ---------------------------------------------- --那么这个('i3','i5')其实就是我们的数组 --我们不妨大胆的利用table()函数,就来使用我们的数组 DECLARE RC SYS_REFCURSOR; REC TEST%ROWTYPE; VY TYPE_VARCHAR; BEGIN VY:=PKG_DEMO.SPLIT('i3||i5','||'); --我们之前定义的一维数组其实就是一个table of varchar, --他本质上就是一个table,我们可以强转它为table, --这样我们就利用到了它里面的所有元素。 OPEN RC FOR 'SELECT * FROM TEST WHERE CPU_ IN (SELECT * FROM TABLE(:X))' USING VY; LOOP FETCH RC INTO REC; EXIT WHEN RC%NOTFOUND; DBMS_OUTPUT.PUT_LINE('CPU='||REC.CPU_||' SCREEN='||REC.SCREEN||' PRICE='||REC.PRICE||' ERA='||REC.ERA); END LOOP; END;
结果:
CPU=i3 SCREEN=14 PRICE=4399 ERA=2012
CPU=i3 SCREEN=13 PRICE=3999 ERA=2011
CPU=i5 SCREEN=13 PRICE=5499 ERA=2012
CPU=i5 SCREEN=15 PRICE=5200 ERA=2011
很显然,我们已经达到了目的了。大功告成还差最后一步。我们需要把它整合到上一篇的判空机制里面去,也就是如果我们什么都不传的话,意味着无条件完全查询。
于是仿照 上一篇中的那个TEST_SELECT我们新做了一个过程TEST_SELECT2
PROCEDURE TEST_SELECT2(V_CPU IN VARCHAR2, V_SCREEN IN VARCHAR2, V_PRICE IN VARCHAR2, V_ERA IN VARCHAR2, CR OUT P_CURSOR) AS CPU_ARRAY TYPE_VARCHAR; SCREEN_ARRAY TYPE_VARCHAR; PRICE_ARRAY TYPE_VARCHAR; ERA_ARRAY TYPE_VARCHAR; BEGIN CPU_ARRAY:=SPLIT(V_CPU,'||'); SCREEN_ARRAY:=SPLIT(V_SCREEN,'||'); PRICE_ARRAY:=SPLIT(V_PRICE,'||'); ERA_ARRAY:=SPLIT(V_ERA,'||'); OPEN CR FOR 'SELECT * FROM TEST WHERE CPU_ IN COALESCE((SELECT * FROM TABLE(:X)),CPU_) AND SCREEN IN COALESCE((SELECT * FROM TABLE(:X)),SCREEN) AND PRICE IN COALESCE((SELECT * FROM TABLE(:X)),PRICE) AND ERA IN COALESCE((SELECT * FROM TABLE(:X)),ERA) ' USING CPU_ARRAY, SCREEN_ARRAY, PRICE_ARRAY, ERA_ARRAY; END; --于是我们来测试一下 DECLARE RC SYS_REFCURSOR; REC TEST%ROWTYPE; BEGIN PKG_DEMO.TEST_SELECT2('i3||i5','','','',RC); LOOP FETCH RC INTO REC; EXIT WHEN RC%NOTFOUND; DBMS_OUTPUT.PUT_LINE('CPU='||REC.CPU_||' SCREEN='||REC.SCREEN||' PRICE='||REC.PRICE||' ERA='||REC.ERA); END LOOP; END; --结果报错了!“一行记录返回了多行数据”,同样尝试了decode,nvl,nvl2函数之后 --还是会报错“类型不匹配”或者“返回了多行数据”。 --其实这都是因为类型不匹配造成的,oracle的函数不支持我们定义的type_varchar这种 --类型,所以无法在我们定义的类型上使用它。
肯定很痛苦,但是这个过程也是探究的乐趣所在,这个过程越痛苦,一旦我们能成功突破
难点的时候,兴奋、开心、自豪就会被放大。如果我们不能在自定义类型上使用oracle函
数,那么我们至少可以在能被他支持的类型上使用。什么意思呢?想想我们条件判断通常
是where cpu_ = coalesce(???,???)我们可以倒过来考虑where coalesce(cpu_,???)=???
这样不就能成功的使用判空函数了吗?
有了这个思路,我们实现它的道路就清晰了,先来测试一条语句看看能不能执行。
SELECT * FROM TEST WHERE -1 IN -1;
轻而易举的看到了结果,证明了我们的想法是可行的,于是我们想到split函数需要改进一
下,在参数为空的时候,我们不能直接返回空了,返回一个只有一个元素的数组,这个元素
是-1,或者任意的,逻辑上达不到那个值的值,你喜欢的都可以。
--改进一下function split FUNCTION SPLIT(V_SRC IN VARCHAR2,V_CHAR IN VARCHAR2) RETURN TYPE_VARCHAR AS V_ARRAY TYPE_VARCHAR; SRC VARCHAR2(30000); IDX NUMBER; POSITION NUMBER; TEMP VARCHAR2(30000); BEGIN V_ARRAY:=NEW TYPE_VARCHAR(); --如果为空,返回一个元素为-1的数组 IF V_SRC IS NULL THEN V_ARRAY.EXTEND; V_ARRAY(1):=-1; RETURN V_ARRAY; END IF; IDX:=1; SRC:=V_SRC; LOOP POSITION:=INSTR(SRC,V_CHAR); EXIT WHEN POSITION=0; TEMP:=SUBSTR(SRC,0,POSITION-1); V_ARRAY.EXTEND; V_ARRAY(IDX):=TEMP; SRC:=SUBSTR(SRC,POSITION+LENGTH(V_CHAR)); DBMS_OUTPUT.PUT_LINE('ARRAY('||IDX||')='||V_ARRAY(IDX)); IDX:=IDX+1; END LOOP; V_ARRAY.EXTEND; V_ARRAY(IDX):=SRC; DBMS_OUTPUT.PUT_LINE('ARRAY('||IDX||')='||V_ARRAY(IDX)); RETURN V_ARRAY; END; --然后重写TEST_SELECT2过程 PROCEDURE TEST_SELECT2(V_CPU IN VARCHAR2, V_SCREEN IN VARCHAR2, V_PRICE IN VARCHAR2, V_ERA IN VARCHAR2, CR OUT P_CURSOR) AS CPU_ARRAY TYPE_VARCHAR; SCREEN_ARRAY TYPE_VARCHAR; PRICE_ARRAY TYPE_VARCHAR; ERA_ARRAY TYPE_VARCHAR; BEGIN CPU_ARRAY:=SPLIT(V_CPU,'||'); SCREEN_ARRAY:=SPLIT(V_SCREEN,'||'); PRICE_ARRAY:=SPLIT(V_PRICE,'||'); ERA_ARRAY:=SPLIT(V_ERA,'||'); --这样在输入参数为空的时候条件变成了 -1 in ('-1') OPEN CR FOR 'SELECT * FROM TEST WHERE NVL2(:X,CPU_,''-1'') IN (SELECT * FROM TABLE(:X)) AND NVL2(:X,SCREEN,''-1'') IN (SELECT * FROM TABLE(:X)) AND NVL2(:X,PRICE,''-1'') IN (SELECT * FROM TABLE(:X)) AND NVL2(:X,ERA,''-1'') IN (SELECT * FROM TABLE(:X)) ' USING V_CPU,CPU_ARRAY, V_SCREEN,SCREEN_ARRAY, V_PRICE,PRICE_ARRAY, V_ERA,ERA_ARRAY; END; --接下来,我们再次进行测试。。。 DECLARE RC SYS_REFCURSOR; REC TEST%ROWTYPE; BEGIN PKG_DEMO.TEST_SELECT2('i3||i5','13||15','','',RC); LOOP FETCH RC INTO REC; EXIT WHEN RC%NOTFOUND; DBMS_OUTPUT.PUT_LINE('CPU='||REC.CPU_||' SCREEN='||REC.SCREEN||' PRICE='||REC.PRICE||' ERA='||REC.ERA); END LOOP; END;
结果如下
CPU=i3 SCREEN=13 PRICE=3999 ERA=2011
CPU=i5 SCREEN=15 PRICE=5200 ERA=2011
CPU=i5 SCREEN=13 PRICE=5499 ERA=2012
至此,大功告成。
总结:虽然oracle的plsql编程不像java,有那么多的api可供调用,但是也正因为如此,plsql编程才显得比java更加灵活多变也更为透明,好处和坏处都很明显,逻辑、
算法、等都是要考虑仔细的,你不小心就会写出臃肿冗余的代码,但是你也更有可能
写出高性能的存储过程!