`
nikoloss
  • 浏览: 33342 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
社区版块
存档分类
最新评论

oracle过程参数可能为空或多个值的查询(二)

阅读更多
    上一篇,完成了对这些参数的判空查询,比如我要CPU为i3的,屏幕为15寸的,等等。
    这次要解决的问题就比较棘手了,比如我在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更加灵活多变也更为透明,好处和坏处都很明显,逻辑、
算法、等都是要考虑仔细的,你不小心就会写出臃肿冗余的代码,但是你也更有可能
写出高性能的存储过程!

15
18
分享到:
评论

相关推荐

    oracle 存储过程的基本语法

    - **`参数列表`**:参数列表可以为空,也可以包含多个参数。参数可以定义为输入参数(IN)或输出参数(OUT)等。 - **`局部变量声明`**:在存储过程中定义局部变量,用于存储中间结果或临时数据。 #### 三、SELECT INTO...

    oracle11g导出空表、少表的解决方案

    当这个参数的值为 TRUE 时,Oracle 会在表中有数据时才创建 segment,以节省空间。而当这个参数的值为 FALSE 时,Oracle 会在创建表时就分配 segment。 allocate extent 语句 allocate extent 语句用于分配 ...

    oracle存储过程基础教程

    2. **OUT参数**:从存储过程传出数据,初始值为空,过程内部赋值后返回。 3. **IN OUT参数**:既可读又可写,过程内部可以改变其值。 五、存储过程的返回值 1. **RETURN语句**:通过RETURN关键字,可以指定一个...

    oracle存储过程详解-开发技术

    4. **代码重用性**:存储过程可以被多个应用程序共享,避免了重复编写相同的代码。 #### 三、Oracle存储过程的基本语法 ##### 1. 创建存储过程 ```sql CREATE OR REPLACE PROCEDURE 存储过程名字 ( 参数1 IN ...

    oracle存储过程

    例如,提供的示例中展示了一个名为`pro_name`的存储过程,它接受多个参数,并构建动态SQL语句进行查询。在这个过程中,首先声明了一个变量`@S1`用于存储最终的SQL查询字符串。通过添加初始的`1=1`条件,后续可以根据...

    Oracle提高查询效率的方法

    6. 参数设置:在表中定义字段或者存储过程、函数中定义参数时,将参数的大小设置为合适即可,勿设置太大。 7. Between 和 IN 的选择:Between 在某些时候比 IN 速度更快,Between 能够更快地根据索引找到范围。 8....

    Oracle9i的init.ora参数中文说明

    否则就会启动一个值为双重货币符号的新会话。 值范围: 任何有效的格式名。。 默认值: 双重货币符号 nls_iso_currency: 说明: 为 C 数字格式元素指定用作国际货币符号的字符串。该参数的默认值由 NLS_TERRITORY ...

    oracle非数字合计,将字段中含有汉子的数据自动转换为 数字0

    根据提供的文件信息,本文将详细介绍如何实现“oracle非数字合计”,即将字段中含有汉字或其他非数字字符的数据自动转换为数字0的过程。 ### 一、问题背景 在实际工作中,可能会遇到这样的情况:某个字段中本应...

    oracle基础--表、视图、过程、函数、游标、异常

    视图是虚拟表,它基于一个或多个表的查询结果。视图可以简化复杂查询,隐藏底层表的结构,提供安全访问控制,并且可以作为更新数据的接口。创建视图时,可以使用SELECT语句定义其结构,用户通过操作视图来间接操作...

    oracle某个字段多行记录转化为一行

    这种方法在处理大量数据时非常有用,特别是在需要将多个值汇总到一个字段中的场景下。 ### 标题理解:“oracle某个字段多行记录转化为一行” 此标题明确指出了解决的问题:在Oracle数据库中,如何将特定字段的多行...

    Oracle经典教程1——走进Oracle

    Oracle数据库管理系统自诞生以来经历了多个版本的更新,包括从最早的版本到Oracle 7、Oracle 8i、Oracle 9i,直至最新的Oracle 11g等。尽管各个版本之间存在一定的差异,但其核心功能和操作方式基本遵循了SQL标准,...

    Oracle存储过程

    2. OUT参数:仅用于传出值,初始值为空。 3. IN OUT参数:既可作为输入,又可传出值。 五、存储过程的返回值 除了使用OUT参数,还可以定义一个RETURN语句来直接返回一个值。此时,存储过程不需要显式声明返回类型,...

    oracle9i初始化参数中文说明

    - **说明**:此参数决定多个监听器地址是单独指定还是使用一个 `ADDRESS_LIST` 字符串来指定。自 8.1.3 版本起,该参数已被废弃。 - **值范围**:`TRUE` 或 `FALSE` - **默认值**:`FALSE` #### mts_servers 参数 ...

    sqlserver中创建类似oracle序列的存储过程

    - 在实际应用中,可能还需要考虑并发问题,因为多个用户同时调用该存储过程可能会导致序列值重复或跳过。 - 可以考虑使用事务和锁定机制来确保数据的一致性和准确性。 - 另外,为了提高性能,可以考虑使用表变量或...

    oracle存储过程学习经典入门.doc

    Oracle 存储过程是数据库管理系统Oracle中的一个重要特性,它允许开发者编写一组SQL语句和PL/SQL代码,形成一个可重复使用的程序单元。存储过程可以提高数据库应用的性能,减少网络流量,并且提供更好的安全性。以下...

    判断记录是否存在,取某个特定字段值,取多个字段值

    本文将深入讲解如何在Delphi中判断记录是否存在以及如何获取特定或多个字段的值,这些技巧是开发常用函数的一部分。 首先,我们来看`GetFieldsValue`这个函数。它的主要任务是根据提供的SQL查询语句来获取一个记录...

    java调用oracle存储过程并得到结果集

    Oracle存储过程可以返回多个结果集,因此我们需要使用`CallableStatement`而不是`PreparedStatement`。调用存储过程的语法是`{call procedure_name(参数列表)}`。例如: ```java CallableStatement cs = connection....

    oracle常见报错

    如果尝试获取全表DML锁定但`DML_LOCKS`参数的值为0,则会触发此错误。确保`DML_LOCKS`参数值大于0。 #### ORA-00063: 超出LOG_FILES的最大数 当重做日志文件的数量超过了`LOG_FILES`参数定义的最大值时,会引发此...

Global site tag (gtag.js) - Google Analytics