借助内存表处理复杂的 oracle 查询要求 . 在日常业务处理过程中 , 我们经常会碰到一些非常规的查询需求 , 这些需求我们或者可以借助动态语句 , 或者其他现有的 oracle 函数完成查询结果 , 但效率往往差强人意 . 假设我们有一个客户订单业务表 { 订单号 , 订
借助内存表处理复杂的 oracle 查询要求 .
在日常业务处理过程中 , 我们经常会碰到一些非常规的查询需求 , 这些需求我们或者可以借助动态语句 , 或者其他现有的 oracle 函数完成查询结果 , 但效率往往差强人意 .
假设我们有一个客户订单业务表 { 订单号 , 订单客户 , 订单日期 , 数量 , 金额 } 存储了订单的往来明细数据 , 订单表中保存最近 3 个月的往来明细共 1000w 条记录 , 其中客户总量约 500000. 并假定在订单表上有针对日期和客户的单独索引 .
现在要求提供对任意集合的多个客户的某段时间的订单明细数据 .
Select 订单号 , 订单客户 , 订单日期 , 数量 , 金额
From 订单业务表
Where 订单日期 between 开始日期 and 结束日期
And 订单客户 in ( 客户 1, 客户 2, 客户 3…)
面对这种需求 , 我们可以要求前台程序传回三个参数 , 开始日期 , 结束日期 , 客户列表 ( 类似于 客户 1, 客户 2, 客户 3, 客户 4…)
一 , 创建测试用表 .
Create table t_order_cust(
O_id varchar2(20),
O_customer varchar2(20),
O_date date,
O_qty numeric(18,2),
O_amount numeric(18,2)
);
Create index ind_t_order_cust_01 on t_order_cust(o_customer);
Create index ind_t_order_cust_02 on t_order_cust(o_date);
二 , 方法 1 , 使用动态语句拼凑实现 .
针对上述查询 , 我们可以拼凑动态语句实现 , 如下代码所示 .
Declare
V_beg_date date := trunc(sysdate,’month’);
V_end_date date := trunk(sysdate);
V_cust_str varchar2(1000) := ‘’’ 客户 1’ ’, ’’ 客户 2’ ’, ’’ 客户 3’ ’…’;
V_sql_str varchar2(2000) ;
Begin
V_sql_str := ‘select * from t_order_cust
Where o_date between ’ || v_beg_date || ‘ and ’ || v_end_date ||’
And o_customer in (’||v_cust_str||’)’;
Execute immediate v_sql_str;
End;
根据表明细数据的特点我们知道 , 客户索引的选择性为 1000w/50w= 20, 而日期索引的选择性为 1000w/(3*30) = 10w, 明显使用日期索引效率极差 , 我们只能选择使用客户上的索引 , 使用这种处理方法的优势是可以用到客户上的索引 , 但 in 使用索引的效率相对较差 , 并且这种处理方式下 , oracle 每次执行查询都需要重新建立查询执行树 , 也是需要一定的额外开销 .
三 , 方法 2 使用 like 查询
除了上面的拼凑动态执行语句的方法之外 , 我们可以设想的到的第二种方法就是借助于 oracle 提供的 like 功能 . 如下代码所示 . 这种处理方式下对客户列表字符串的要求跟方法一少有区别 .
Declare
V_beg_date date := trunc(sysdate,’month’);
V_end_date date := trunk(sysdate);
V_cust_str varchar2(1000) := ‘ 客户 1, 客户 2, 客户 3…’;
Begin
select * from t_order_cust
Where o_date between v_beg_date and v_end_date
And v_cust_str like ‘%’||o_customer||’%’;
End;
这种处理方式的优点在于代码书写简单 , 但由于对客户索引所在字段 o_customer 做了拼接处理 ||, 所以将导致客户索引 ind_t_order_cust_01 无效 , 而只能使用效率较差的日期索引 . 在数据量较小 , 对效率无法造成影响时这种方法可以接受 , 但数据量较大时 , 这种方法的缺点将是致命的 .
四 , 方法 3 使用 instr 函数处理
Declare
V_beg_date date := trunc(sysdate,’month’);
V_end_date date := trunk(sysdate);
V_cust_str varchar2(1000) := ‘ 客户 1, 客户 2, 客户 3…’;
Begin
select * from t_order_cust
Where o_date between v_beg_date and v_end_date
And instr(v_cust_str like ,o_custome) >0
End;
这种处理方式的优缺点跟使用 like 相似 , 同样由于对 o_customer 使用了函数 , 导致该索引不可用 , 函数索引同样也不适用于这种情况 .
五 , 方法 4 使用内存表处理
我们知道 , oracle, sqlserver 等关系数据库最善于处理的数据类型是集合 , 而不是单独的记录 . 同样的 100 条记录 , 如果逐条循环处理和批量处理其效率的差别将是几何单位的 .
所以 , 为了提高查询效率 , 我们这里考虑将给定的客户字符串转变为一个集合或者临时表来处理 . Oracle 使用全局临时表和复杂数据类型集合来支持这一点 .
这里我们介绍一下使用复杂数据类型集合来处理的方式 .
首先我们定义一个复杂类型 .
create or replace type ctl.type_jax_varc2tab is table of varchar2(2000);
然后定义一个函数实现将给定的字符串转换为嵌套内存表 .
CREATE OR REPLACE FUNCTION f_jax_str2tab(p_str IN VARCHAR2 ,
p_sep varchar2 default ','
) RETURN ctl.type_jax_varc2tab IS
/******************************************************************
Ver1.0 Created by jaxzhang on 2009-06-08
把字符串 (1*2*3*4*5) 转换为内存表形式
create or replace type type_jax_varc2tab is table of varchar2(2000);
测试用例 :SELECT * FROM TABLE(f_jax_str2tab('1*2*3*4*5','*'));
******************************************************************/
v_str varchar2 ( 2000 );
v_cnt NUMBER ;
v_numtab type_jax_varc2tab := type_jax_varc2tab(); -- 返回内存表
BEGIN
select decode(substr(p_str,- 1 ),p_sep,p_str,p_str || p_sep) into v_str from dual;
select length(v_str) - length( REPLACE (v_str, p_sep)) into v_cnt from dual;
FOR i IN 1 .. v_cnt LOOP
v_numtab. EXTEND ;
v_numtab(i) := substr(v_str, 1 , instr(v_str, p_sep) - 1 );
v_str := substr(v_str, instr(v_str,p_sep) + 1 );
END LOOP ;
RETURN v_numtab;
EXCEPTION
WHEN OTHERS THEN
v_numtab. DELETE ;
END ;
上述函数的功能就是要将类似于 ’ 客户 1, 客户 2, 客户 3’ 的字符串转换为如下形式 .
SELECT * FROM TABLE (f_jax_str2tab( ' 客户 1, 客户 2, 客户 3' , ',' ));
COLUMN_VALUE
客户 1
客户 2
客户 3
得到上述的内存表之后 , 我们就可以使用类似于一个表或者视图的方式来与正式表 t_order_cust 关联得到我们需要的查询结果 .
Select /*+ ordered use_nl(a b)*/
From TABLE (f_jax_str2tab( ' 客户 1, 客户 2, 客户 3' , ',' )) a,
T_order_cust b
Where b.o_customer = a.column_value
查看更多关于借助内存表处理复杂的oracle查询要求的详细内容...