打对了

宇宙和生命从哪里来?又要到哪里去呢?

 

对条件子句中带IN的SQL语句使用绑定变量

使用绑定变量带来的好处不用多说,但是遇到SQL条件中条件值不固定的这类SQL在普通方式下就不能使用绑定变量,看到Tom网站上提供了一种可行的办法。

网上有不少的地方也提到了这种方法,那就是,先定义一个表类型,将个数不定的参数转换成这种表类型,再将查询结果传给IN子句,这样就可以实现了SQL语句中的绑定变量方式。
例如:
Select * From 病人费用记录 Where NO IN(Select * From Table(Cast(zlTools.f_Str2List ('A01,A02,A03') as zlTools. t_StrList)));
Select * From 病人费用记录 Where NO IN(Select * From The(Select Cast(zlTools.f_Str2List ('A01,A02,A03') as zlTools. t_StrList) From Dual));
Select A.* From 病人费用记录 A,Table(Cast(zlTools.f_Str2List ('A01,A02,A03') as zlTools. t_StrList)) B Where A.NO=B.Column_Value;

Oracle提供了一个包dbms_utility.comma_to_table可以将参数列表转换为一个类型表,但是由于结果是通过参数传出的,无法用于应用程序的SQL语句中。

于是,我们仍然选择通过创建SQL表类型和相关函数来实现,Type:t_StrList, t_NumList, 函数:f_Str2List, f_Num2List,如下:

Create Or Replace type zltools.t_StrList as table of varchar2(4000)
/
Create Or Replace Function zltools.f_Str2List(Str_In In Varchar2) Return zltools.t_StrList As
 v_Str   Long Default Str_In || ',';
 v_Index Number;
 v_List  zltools.t_StrList := zltools.t_StrList();
--功能:将由逗号分隔的不带引号的字符序列转换为数据表
--参数:Str_In如:G0000123,G0000124,G0000125...
Begin
 Loop
  v_Index := Instr(v_Str, ',');
  Exit When(Nvl(v_Index, 0) = 0);
  v_List.Extend;
  v_List(v_List.Count) := Trim(Substr(v_Str, 1, v_Index - 1));
  v_Str := Substr(v_Str, v_Index + 1);
 End Loop;
 Return v_List;
End;
/
Create Or Replace type zltools.t_NumList as table of number
/
Create Or Replace Function zltools.f_Num2List(Str_In In Varchar2) Return zltools.t_NumList As
 v_Str   Long Default Str_In || ',';
 v_Index Number;
 v_List  zltools.t_NumList := zltools.t_NumList();
--功能:将由逗号分隔的数字序列转换为数据表
--参数:Str_In如:73265,73266,73267....
Begin
 Loop
  v_Index := Instr(v_Str, ',');
  Exit When(Nvl(v_Index, 0) = 0);
  v_List.Extend;
  v_List(v_List.Count) := To_Number(Trim(Substr(v_Str, 1, v_Index - 1)));
  v_Str := Substr(v_Str, v_Index + 1);
 End Loop;
 Return v_List;
End;
/

 
但是这种方法有一个问题:
如果用户使用CBO,使用这种方式的SQL语句,不加处理的话一般(要根据具体SQL分析)会错误的使用Hash连接来产生执行计划,不能正确使用索引从而引发性能问题,
因为Type Table不具备统计信息,在计算成本时,Oracle始终认为它有8168行,在基于成本的优化器模式下,多表连接时一般就会选择Hash连接,一般比使用索引的嵌套连接效率差。

目前,已知的解决办法有两种:
http://www.cnoug.org/viewthread.php?tid=1533&highlight=DROP%2B%2BCONSTRAINT%2Bindex
1. 对构造表的返回记录集加RowNum条件,通过子查询的实体化视图来改变执行计划,例如:
select * from table(Cast(zlTools.f_Str2List (v_str) as zlTools.t_StrList)) where rownum<5
2. 在SQL中加入优化器提示,例如:/*+ rule*/,或者/*+ ordered use_nl(t)*/,或者/*+ index(t ind_object_id)*/

第1种方式,需要对rownum的条件赋值,如果条件值采用动态的值,那么就达不到使用绑定变量的效果,如果采用固定值,有两种方案:
一种是使用一个固定的极限值,例如:99999,另一种是使用绑定变量。
试验结果见文末,这两种方式产生的执行计划采用了Hash连接,执行计划比不使用RowNum实体化视图更差。
 
第2种方式,如果使用指定嵌套连接提示或指定索引提示则需要对每个SQL针对性处理,需要分析具体的SQL。所以,一般情况建议在SQL中使用/*+ rule*/提示来固定执行计划。


--0.准备
--先创建表类型及转换函数: t_StrList,f_Str2List和t_NumList,f_Num2List
SQL> alter session set optimizer_mode=all_rows;
Session altered
SQL> var v_str varchar2(4000);
SQL> exec :v_str :='H0000005,H0000006,H0000007';

PL/SQL 过程已成功完成。


--1.当类型表不使用Rownum实体化视图时,错误地采用了Hash连接的执行计划,产生了6575块逻辑读+6909块物理读
SQL> Select a.No, a.结算方式, Nvl(b.性质, 1) As 性质, a.金额, a.摘要, a.结算号码
  2  From (Select b.No, Decode(a.记录性质, 3, a.结算方式, Null) As 结算方式, a.摘要, a.结算号码, Sum
(1 * a.冲预交) As 金额
  3      From 病人预交记录 a,
  4        (Select Distinct No, 结帐id
  5         From 病人费用记录
  6         Where 记录性质 = 1 And No In (
  7                Select * From Table(Cast(f_Str2List(:v_str) as t_StrList))
  8                ) And 记录状态 In (1, 3)) b
  9      Where a.结帐id = b.结帐id And a.记录性质 In (1, 11, 3) And Nvl(a.冲预交, 0) <> 0
 10      Group By b.No, Decode(a.记录性质, 3, a.结算方式, Null), a.摘要, a.结算号码) a, 结算方式 b
 11  Where a.结算方式 = b.名称(+);


执行计划
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=ALL_ROWS (Cost=23 Card=1 Bytes=100)
   1    0   NESTED LOOPS (OUTER) (Cost=23 Card=1 Bytes=100)
   2    1     VIEW (Cost=22 Card=1 Bytes=75)
   3    2       SORT (GROUP BY) (Cost=22 Card=1 Bytes=94)
   4    3         VIEW (Cost=21 Card=1 Bytes=94)
   5    4           SORT (UNIQUE) (Cost=21 Card=1 Bytes=149)
   6    5             TABLE ACCESS (BY INDEX ROWID) OF '病人预交记录' (Cost=1 Card=1 Bytes=102)
   7    6               NESTED LOOPS (Cost=19 Card=1 Bytes=149)
   8    7                 HASH JOIN (SEMI) (Cost=17 Card=4 Bytes=188)
   9    8                   INDEX (FAST FULL SCAN) OF '病人费用记录_UQ_NO' (UNIQUE) (Cost=4 Card=399 Bytes=17955)
  10    8                   COLLECTION ITERATOR (PICKLER FETCH) OF 'f_Str2List'
  11    7                 INDEX (RANGE SCAN) OF '病人预交记录_IX_结帐ID' (NON-UNIQUE)
  12    1     TABLE ACCESS (BY INDEX ROWID) OF '结算方式' (Cost=1 Card=1 Bytes=25)
  13   12       INDEX (UNIQUE SCAN) OF '结算方式_UQ_名称' (UNIQUE)

统计信息
----------------------------------------------------------
          0  recursive calls
          0  db block gets
       6575  consistent gets
       6909  physical reads
          0  redo size
        720  bytes sent via SQL*Net to client
        372  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          2  sorts (memory)
          0  sorts (disk)
          3  rows processed

--2.当类型表的查询条件中Rownum使用固定值时,正确地采用了嵌套连接的执行计划,仅仅产生了29块逻辑读
SQL> Select a.No, a.结算方式, Nvl(b.性质, 1) As 性质, a.金额, a.摘要, a.结算号码
  2  From (Select b.No, Decode(a.记录性质, 3, a.结算方式, Null) As 结算方式, a.摘要, a.结算号码, Sum
(1 * a.冲预交) As 金额
  3      From 病人预交记录 a,
  4        (Select Distinct No, 结帐id
  5         From 病人费用记录
  6         Where 记录性质 = 1 And No In (
  7                Select * From Table(Cast(f_Str2List(:v_str) as t_StrList)) Where Rownum<4
  8                ) And 记录状态 In (1, 3)) b
  9      Where a.结帐id = b.结帐id And a.记录性质 In (1, 11, 3) And Nvl(a.冲预交, 0) <> 0
 10      Group By b.No, Decode(a.记录性质, 3, a.结算方式, Null), a.摘要, a.结算号码) a, 结算方式 b
 11  Where a.结算方式 = b.名称(+);


执行计划
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=ALL_ROWS (Cost=23 Card=4 Bytes=400)
   1    0   NESTED LOOPS (OUTER) (Cost=23 Card=4 Bytes=400)
   2    1     VIEW (Cost=21 Card=4 Bytes=300)
   3    2       SORT (GROUP BY) (Cost=21 Card=4 Bytes=376)
   4    3         VIEW (Cost=20 Card=4 Bytes=376)
   5    4           SORT (UNIQUE) (Cost=20 Card=4 Bytes=8596)
   6    5             TABLE ACCESS (BY INDEX ROWID) OF '病人预交记录' (Cost=1 Card=1 Bytes=102)
   7    6               NESTED LOOPS (Cost=17 Card=4 Bytes=8596)
   8    7                 NESTED LOOPS (Cost=13 Card=12 Bytes=24564)
   9    8                   VIEW OF 'VW_NSO_1' (Cost=11 Card=3 Bytes=6006)
  10    9                     COUNT (STOPKEY)
  11   10                       COLLECTION ITERATOR (PICKLER FETCH) OF 'f_Str2List'
  12    8                   INLIST ITERATOR
  13   12                     INDEX (RANGE SCAN) OF '病人费用记录_UQ_NO' (UNIQUE) (Cost=1 Card=4 Bytes=180)
  14    7                 INDEX (RANGE SCAN) OF '病人预交记录_IX_结帐ID' (NON-UNIQUE)
  15    1     TABLE ACCESS (BY INDEX ROWID) OF '结算方式' (Cost=1 Card=1 Bytes=25)
  16   15       INDEX (UNIQUE SCAN) OF '结算方式_UQ_名称' (UNIQUE)

统计信息
----------------------------------------------------------
          0  recursive calls
          0  db block gets
         29  consistent gets
          0  physical reads
          0  redo size
        720  bytes sent via SQL*Net to client
        372  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          2  sorts (memory)
          0  sorts (disk)
          3  rows processed

--3.当类型表的查询条件中Rownum使用变量时,执行计划虽然有变化,但是仍然认为类型表返回的记录集数为8168,
--  错误地采用了Hash连接的执行计划,产生了374369块逻辑读和11192块物理读
SQL> var v_count number;
SQL>  exec :v_count:=4;

PL/SQL 过程已成功完成。

SQL> Select a.No, a.结算方式, Nvl(b.性质, 1) As 性质, a.金额, a.摘要, a.结算号码
  2  From (Select b.No, Decode(a.记录性质, 3, a.结算方式, Null) As 结算方式, a.摘要, a.结算号码, Sum
(1 * a.冲预交) As 金额
  3      From 病人预交记录 a,
  4        (Select Distinct No, 结帐id
  5         From 病人费用记录
  6         Where 记录性质 = 1 And No In (
  7                Select * From Table(Cast(f_Str2List(:v_str) as t_StrList)) Where Rownum<:v_count
  8                ) And 记录状态 In (1, 3)) b
  9      Where a.结帐id = b.结帐id And a.记录性质 In (1, 11, 3) And Nvl(a.冲预交, 0) <> 0
 10      Group By b.No, Decode(a.记录性质, 3, a.结算方式, Null), a.摘要, a.结算号码) a, 结算方式 b
 11  Where a.结算方式 = b.名称(+);


执行计划
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=ALL_ROWS (Cost=16306 Card=10945 Bytes=1094500)
   1    0   HASH JOIN (OUTER) (Cost=16306 Card=10945 Bytes=1094500)
   2    1     VIEW (Cost=16264 Card=10945 Bytes=820875)
   3    2       SORT (GROUP BY) (Cost=16264 Card=10945 Bytes=1028830)
   4    3         VIEW (Cost=16148 Card=10945 Bytes=1028830)
   5    4           SORT (UNIQUE) (Cost=16148 Card=10945 Bytes=23520805)
   6    5             HASH JOIN (Cost=238 Card=10945 Bytes=23520805)
   7    6               TABLE ACCESS (BY INDEX ROWID) OF '病人预交记录' (Cost=1 Card=1 Bytes=102)
   8    7                 NESTED LOOPS (Cost=164 Card=134 Bytes=19698)
   9    8                   INDEX (FAST FULL SCAN) OF '病人费用记录_UQ_NO' (UNIQUE) (Cost=4 Card=399 Bytes=17955)
  10    8                   INDEX (RANGE SCAN) OF '病人预交记录_IX_结帐ID' (NON-UNIQUE)
  11    6               VIEW OF 'VW_NSO_1' (Cost=11 Card=8168 Bytes=16352336)
  12   11                 COUNT (STOPKEY)
  13   12                   COLLECTION ITERATOR (PICKLER FETCH) OF 'f_Str2List'
  14    1     TABLE ACCESS (FULL) OF '结算方式' (Cost=2 Card=82 Bytes=2050)

统计信息
----------------------------------------------------------
         28  recursive calls
          0  db block gets
     374369  consistent gets
      11192  physical reads
          0  redo size
        720  bytes sent via SQL*Net to client
        372  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          2  sorts (memory)
          0  sorts (disk)
          3  rows processed

--4.当类型表的查询条件中Rownum使用一个固定的极限值时(例如:99999),情况与使用变量一样遭,错误地采用了Hash连接的执行计划,产生了374354块逻辑读和10756块物理读
SQL> Select a.No, a.结算方式, Nvl(b.性质, 1) As 性质, a.金额, a.摘要, a.结算号码
  2  From (Select b.No, Decode(a.记录性质, 3, a.结算方式, Null) As 结算方式, a.摘要, a.结算号码, Sum
(1 * a.冲预交) As 金额
  3      From 病人预交记录 a,
  4        (Select Distinct No, 结帐id
  5         From 病人费用记录
  6         Where 记录性质 = 1 And No In (
  7                Select * From Table(Cast(f_Str2List(:v_str) as t_StrList)) Where Rownum<99999
  8                ) And 记录状态 In (1, 3)) b
  9      Where a.结帐id = b.结帐id And a.记录性质 In (1, 11, 3) And Nvl(a.冲预交, 0) <> 0
 10      Group By b.No, Decode(a.记录性质, 3, a.结算方式, Null), a.摘要, a.结算号码) a, 结算方式 b
 11  Where a.结算方式 = b.名称(+);


执行计划
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=ALL_ROWS (Cost=16306 Card=10945 Bytes=1094500)
   1    0   HASH JOIN (OUTER) (Cost=16306 Card=10945 Bytes=1094500)
   2    1     VIEW (Cost=16264 Card=10945 Bytes=820875)
   3    2       SORT (GROUP BY) (Cost=16264 Card=10945 Bytes=1028830)
   4    3         VIEW (Cost=16148 Card=10945 Bytes=1028830)
   5    4           SORT (UNIQUE) (Cost=16148 Card=10945 Bytes=23520805)
   6    5             HASH JOIN (Cost=238 Card=10945 Bytes=23520805)
   7    6               TABLE ACCESS (BY INDEX ROWID) OF '病人预交记录' (Cost=1 Card=1 Bytes=102)
   8    7                 NESTED LOOPS (Cost=164 Card=134 Bytes=19698)
   9    8                   INDEX (FAST FULL SCAN) OF '病人费用记录_UQ_NO' (UNIQUE) (Cost=4 Card=399 Bytes=17955)
  10    8                   INDEX (RANGE SCAN) OF '病人预交记录_IX_结帐ID' (NON-UNIQUE)
  11    6               VIEW OF 'VW_NSO_1' (Cost=11 Card=8168 Bytes=16352336)
  12   11                 COUNT (STOPKEY)
  13   12                   COLLECTION ITERATOR (PICKLER FETCH) OF 'f_Str2List'
  14    1     TABLE ACCESS (FULL) OF '结算方式' (Cost=2 Card=82 Bytes=2050)


统计信息
----------------------------------------------------------
          0  recursive calls
          0  db block gets
     374354  consistent gets
      10756  physical reads
          0  redo size
        720  bytes sent via SQL*Net to client
        372  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          2  sorts (memory)
          0  sorts (disk)
          3  rows processed

 

--5.强制使用基于规则的优化器模式
SQL> Select /*+ rule*/ a.No, a.结算方式, Nvl(b.性质, 1) As 性质, a.金额, a.摘要, a.结算号码
  2  From (Select b.No, Decode(a.记录性质, 3, a.结算方式, Null) As 结算方式, a.摘要, a.结算号码, Sum
(1 * a.冲预交) As 金额
  3      From 病人预交记录 a,
  4        (Select Distinct No, 结帐id
  5         From 病人费用记录
  6         Where 记录性质 = 1 And No In (
  7                Select * From Table(Cast(f_Str2List(:v_str) as t_StrList))
  8                ) And 记录状态 In (1, 3)) b
  9      Where a.结帐id = b.结帐id And a.记录性质 In (1, 11, 3) And Nvl(a.冲预交, 0) <> 0
 10      Group By b.No, Decode(a.记录性质, 3, a.结算方式, Null), a.摘要, a.结算号码) a, 结算方式 b
 11  Where a.结算方式 = b.名称(+);


执行计划
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=HINT: RULE
   1    0   NESTED LOOPS (OUTER)
   2    1     VIEW
   3    2       SORT (GROUP BY)
   4    3         VIEW
   5    4           SORT (UNIQUE)
   6    5             TABLE ACCESS (BY INDEX ROWID) OF '病人预交记录'
   7    6               NESTED LOOPS
   8    7                 NESTED LOOPS
   9    8                   COLLECTION ITERATOR (PICKLER FETCH) OF 'f_Str2List'
  10    8                   INDEX (RANGE SCAN) OF '病人费用记录_UQ_NO' (UNIQUE)
  11    7                 INDEX (RANGE SCAN) OF '病人预交记录_IX_结帐ID' (NON-UNIQUE)
  12    1     TABLE ACCESS (BY INDEX ROWID) OF '结算方式'
  13   12       INDEX (UNIQUE SCAN) OF '结算方式_UQ_名称' (UNIQUE)


统计信息
----------------------------------------------------------
          0  recursive calls
          0  db block gets
         25  consistent gets
          0  physical reads
          0  redo size
        720  bytes sent via SQL*Net to client
        372  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          2  sorts (memory)
          0  sorts (disk)
          3  rows processed

posted on 2007-01-26 19:49 知道得越多知道的越少 阅读(887) 评论(3)  编辑 收藏 网摘

评论

#1楼  2007-01-30 01:07 guoadou      

请问...
Select a.No, a.结算方式, Nvl(b.性质, 1) As 性质, a.金额, a.摘要, a.结算号码
这些汉字是伪代码还是...   回复  引用  查看    

#2楼 [楼主] 2007-03-30 17:53 知道得越多知道的越少      

表名和字段名都是真实的汉字,实践表明没有什么不可以。   回复  引用  查看    

#3楼 [楼主] 2007-04-02 10:36 知道得越多知道的越少      

目前发现一个问题,在Oracle 817上使用需要注意一个BUG。
在使用In子句一个记录集表时,如果使用了Group by则会出现“通信信道结束”的错误。改为表联接方式就不会出现这个问题。
Where NO In (Select * From Table(Cast(f_Str2list('A01,A02,A03') As zlTools.t_Strlist)));

改为:
From 病人费用记录 A, Table(Cast(f_Str2list('A01,A02,A03') As zlTools.t_Strlist)) B
Where A.NO = B.Column_Value;
  回复  引用  查看    


标题  
姓名  
主页
Email (博主才能看到) 
验证码 *  看不清,换一张 [登录][注册]
内容(请不要发表任何与政治相关的内容)  
  登录  使用高级评论  新用户注册  返回页首  恢复上次提交      
Google站内搜索

相关文章:

相关链接:


 

导航

统计

公告

对你说打错了 我不是你那个什么
你想找的那个 就算我跟她同名同姓又如何
都说你打错了 我要欺骗你干什么
你们多久没见连 我跟她的声音你都不认得
你怎么样过 什么样的生活 是否难耐寂寞
你到底是谁 总是阴差阳错 擦过我的耳朵
第几次打错了 这是注定还是巧合
谁是玛格列特 她知道你的着急一定很快乐
你们发生什么 还是你欠了她什么
有什么舍不得 她不住这里你却非找她不可
你怎么样过 什么样的生活 是否难耐寂寞
你到底是谁 总是阴差阳错 擦过我的耳朵
你怎么样过 什么样的生活 是否难耐寂寞
你到底是谁 总是阴差阳错 擦过我的耳朵
你们会讲什么口气会不会软软的
你紧张得想哭 多年后想起今天值得不值得

与我联系

搜索

 

常用链接

留言簿(4)

我参与的团队

我的标签

随笔分类

随笔档案

文章分类

文章档案

收藏夹

音乐

有价值的blog

最新评论

阅读排行榜

评论排行榜