如何强制where子句中的函数在oracle中执行一次?

时间:2013-02-19 18:17:44

标签: oracle oracle11g

我正在尝试根据IP地址查询相当大的IP / City(3,000,000+行)表。我的源IP地址是点缀符号,如127.0.0.1,表有两个字段存储为整数,如2130706433.所以我需要将点分表示法转换为我的where子句中的整数,如下所示;

select get_ip_integer('74.253.103.98') ,icb.*,icl.* 
from ip_city_block icb, ip_city_location icl 
where get_ip_integer('74.253.103.98') between  icb.startipnum and  icb.endipnum  and
      icl.locid = icb.locid;

此查询在相当快的DB上占用4~(4.33)秒。以下查询需要.062秒;

select 1258121058,icb.*,icl.* 
from ip_city_block icb, ip_city_location icl 
where icb.startipnum <= 1258121058 and  icb.endipnum >= 1258121058 and
      icl.locid = icb.locid;

唯一的区别是我将函数get_ip_integer替换为函数返回的值。如果我所做的只是一次查找,我会执行第二次查询并完成它,但我不是。

我实际上想要加入另一个包含许多虚线格式的ip地址的表,当我这样做时需要永远。为了好玩,我也试过了;

select ip_integer ,icb.*,icl.* 
from (select get_ip_integer('74.253.103.98') ip_integer from dual),ip_city_block icb, ip_city_location icl 
where icb.startipnum <= ip_integer and  icb.endipnum >= ip_integer and
      icl.locid = icb.locid;

这也花了大约4.33秒。

所以问题是如何强制get_ip_integer函数只执行一次并使用结果进行比较?

我将我的函数更新为确定性,它似乎有助于原始查询,但更复杂的查询仍然无法使用,性能明智。这是;

SELECT COUNTRY, REGION,CITY, WEBLOG_USERID, WEBLOG_IP, WEBLOG_COUNT 
FROM (
  select WEBLOG_USERID,WEBLOG_IP,get_ip_integer(WEBLOG_IP) ip_integer,count(*) WEBLOG_COUNT 
  from weblog 
  where weblog_date > '20130217' 
  group by WEBLOG_USERID,weblog_ip
),ip_city_block icb, ip_city_location icl 
where ip_integer between icb.startipnum and icb.endipnum and icl.locid = icb.locid
ORDER BY 1,2,3;

对此有何想法?

经过一番思考,我想出了这个,虽然不是很快就可以接受;

SELECT COUNTRY, REGION,CITY, WEBLOG_USERID, WEBLOG_IP, WEBLOG_COUNT 
FROM (
  select WEBLOG_USERID,WEBLOG_IP, count(*) WEBLOG_COUNT 
  from weblog 
  where weblog_date > '20130000' 
  group by WEBLOG_USERID,weblog_ip
),ip_city_block icb, ip_city_location icl 
where get_ip_integer(WEBLOG_IP) between icb.startipnum and icb.endipnum and icl.locid = icb.locid
ORDER BY 1,2,3;

2 个答案:

答案 0 :(得分:1)

为什么要使用PL / SQL呢?从你所说的你正在做一些数学,为什么不在SQL中这样做呢?这也可以通过INSTR和SUBSTR的组合实现,但使用REGEXP_SUBSTR可以更漂亮。

select to_number(regexp_substr(ip, '[^.]+', 1, 1)) * power(2,24)
        + to_number(regexp_substr(ip, '[^.]+', 1, 2)) * power(2,16)
        + to_number(regexp_substr(ip, '[^.]+', 1, 3)) * power(2,8)
        + to_number(regexp_substr(ip, '[^.]+', 1, 4))
     , icb.*
     , icl.* 
  from ip_city_block icb
  join ip_city_location icl
    on icl.locid = icb.locid  
 where to_number(regexp_substr(ip, '[^.]+', 1, 1)) * power(2,24)
        + to_number(regexp_substr(ip, '[^.]+', 1, 2)) * power(2,16)
        + to_number(regexp_substr(ip, '[^.]+', 1, 3)) * power(2,8)
        + to_number(regexp_substr(ip, '[^.]+', 1, 4))
       between icb.startipnum and icb.endipnum

SQL Fiddle demonstration of REGEXP_SUBSTR output

如果 在PL / SQL中执行此操作,您应该做两件事:

  1. 看看您是否可以将您的功能声明为deterministic
  2. 尝试并利用sub-query caching
  3. 好像你已经做了2,但你可以尝试使用WITH子句来扩展它:

    with the_ip as ( select get_ip_integer('74.253.103.98') as ip from dual )
    select the_ip.ip
         , icb.*
         , icl.* 
      from ip_city_block icb
      join ip_city_location icl
        on icl.locid = icb.locid
      join the_ip
        on the_ip.ip between icb.startipnum and icb.endipnum
    

答案 1 :(得分:1)

你使用双线进入正确的轨道,但是对于子查询缓存,你可以在选择中进行。

select (select get_ip_integer('74.253.103.98') from dual) ip,
       icb.*,icl.* 
from ip_city_block icb, ip_city_location icl 
where get_ip_integer('74.253.103.98') between  icb.startipnum and  icb.endipnum  and
      icl.locid = icb.locid;

您也应该使用result_cache定义您的功能。

请点击此处了解详情:http://www.oracle.com/technetwork/issue-archive/2011/11-sep/o51asktom-453438.html