问题
I am attempting to query a rather large IP/City (3,000,000+ rows) table based on an IP address. My source IP address is dotted notation such as 127.0.0.1 and the table has two fields stored as integers such as 2130706433. So I need to convert the dotted notation to the integer in my where clause like this;
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;
This query takes over 4 ~(4.33) seconds on a reasonably fast DB. The following query takes .062 seconds;
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;
The only difference is I replace the function get_ip_integer with the value that the function returns. If all I were doing is one lookup I would execute the second query and be done with it, but I'm not.
I actually want to join to another table that contains many ip addresses in dotted format, and when I do it takes forever. For fun I also tried;
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;
and this took ~4.33 seconds as well.
So the question is how can I force the get_ip_integer function to execute only once and use the results for the comparison?
I updated my function to Deterministic, and it seemed to help on the original query, but the more complex query is still unusable, performance wise. Here it is;
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;
Any thoughts on this?
After a little thought on my own I came up with this, while not blindingly fast it is acceptable;
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;
回答1:
Why are you using PL/SQL for this at all? From what you've said you're doing some maths, why not just do that in SQL? This'll also be possible with a combination of INSTR and SUBSTR but it's prettier to look at with 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
If you have to do this in PL/SQL you should do two things:
- See if you can declare your function as deterministic.
- Try and take advantage of sub-query caching.
It appears as though you're already doing 2, but you could try and extend this by using a WITH clause:
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
回答2:
you were on the right track with dual, but for subquery caching, you do it in the select.
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;
also you should define your function with result_cache.
see here for more details: http://www.oracle.com/technetwork/issue-archive/2011/11-sep/o51asktom-453438.html
来源:https://stackoverflow.com/questions/14964562/how-can-you-force-an-function-in-a-where-clause-to-execute-once-in-oracle