我有一张包含以下数据类型的表格:
create table store (
n_id serial not null primary key,
n_place_id integer not null references place(n_id),
dt_modified timestamp not null,
t_tag varchar(4),
n_status integer not null default 0
...
(about 50 more fields)
);
n_id,n_place_id,dt_modified和以下查询中使用的所有其他字段都有索引。
此表目前包含大约100,000行,但可能会增长到接近一百万甚至更多。然而,现在让我们假设我们保持在100K左右。
我正在尝试从这些表中选择满足两个条件的行:
n_place_id
位于特定子集中的所有行(此部分很简单);或n_place_id
值,按dt_modified
排序的前十行(这是变得更复杂的地方)。在一个SQL中执行它似乎太痛苦了,所以我很高兴为此存储函数。我这样定义了我的函数:
create or replace function api2.fn_api_mobile_objects()
returns setof store as
$body$
declare
maxres_free integer := 10;
resulter store%rowtype;
mcnt integer := 0;
previd integer := 0;
begin
create temporary table paid on commit drop as
select n_place_id from payments where t_reference is not null and now()::date between dt_paid and dt_valid;
for resulter in
select * from store where n_status > 0 and t_tag is not null order by n_place_id, dt_modified desc
loop
if resulter.n_place_id in (select n_place_id from paid) then
return next resulter;
else
if previd <> resulter.n_place_id then
mcnt := 0;
previd := resulter.n_place_id;
end if;
if mcnt < maxres_free then
return next resulter;
mcnt := mcnt + 1;
end if;
end if;
end loop;
end;$body$
language 'plpgsql' volatile;
问题在于
select * from api2.fn_api_mobile_objects()
执行需要大约6-7秒。考虑到之后,这个结果集需要join
到其他3个表,并且应用了一系列附加条件并进行了进一步排序,这显然是不可接受的。
好吧,我仍然需要获取这些数据,所以要么我缺少函数中的东西,要么我需要重新考虑整个算法。无论哪种方式,我都需要帮助。
答案 0 :(得分:1)
CREATE TABLE store
( n_id serial not null primary key
, n_place_id integer not null -- references place(n_id)
, dt_modified timestamp not null
, t_tag varchar(4)
, n_status integer not null default 0
);
INSERT INTO store(n_place_id,dt_modified,n_status)
SELECT n,d,n%4
FROM generate_series(1,100) n
, generate_series('2012-01-01'::date ,'2012-10-01'::date, '1 day'::interval ) d
;
WITH zzz AS (
SELECT n_id AS n_id
, rank() OVER (partition BY n_place_id ORDER BY dt_modified) AS rnk
FROM store
)
SELECT st.*
FROM store st
JOIN zzz ON zzz.n_id = st.n_id
WHERE st.n_place_id IN ( 1,22,333)
OR zzz.rnk <=10
;
更新:这是与子查询相同的selfjoin构造(计划程序对CTE的处理方式略有不同):
SELECT st.*
FROM store st
JOIN ( SELECT sx.n_id AS n_id
, rank() OVER (partition BY sx.n_place_id ORDER BY sx.dt_modified) AS zrnk
FROM store sx
) xxx ON xxx.n_id = st.n_id
WHERE st.n_place_id IN ( 1,22,333)
OR xxx.zrnk <=10
;
答案 1 :(得分:1)
经过多次努力,我设法让存储的函数在1秒钟内返回结果(这是一个巨大的改进)。现在函数看起来像这样(我添加了额外的条件,它对性能影响不大):
create or replace function api2.fn_api_mobile_objects(t_search varchar)
returns setof store as
$body$
declare
maxres_free integer := 10;
resulter store%rowtype;
mid integer := 0;
begin
create temporary table paid on commit drop as
select n_place_id from payments where t_reference is not null and now()::date between dt_paid and dt_valid
union
select n_place_id from store where n_status > 0 and t_tag is not null group by n_place_id having count(1) <= 10;
for resulter in
select * from store
where n_status > 0 and t_tag is not null
and (t_name ~* t_search or t_description ~* t_search)
and n_place_id in (select n_place_id from paid)
loop
return next resulter;
end loop;
for mid in
select distinct n_place_id from store where n_place_id not in (select n_place_id from paid)
loop
for resulter in
select * from store where n_status > 0 and t_tag is not null and n_place_id = mid order by dt_modified desc limit maxres_free
loop
return next resulter;
end loop;
end loop;
end;$body$
language 'plpgsql' volatile;
这在我的本地机器上运行时间超过1秒,在现场运行约0.8-1.0秒。就我的目的而言,这已经足够好了,虽然我不确定随着数据量的增长会发生什么。
答案 2 :(得分:0)
作为一个简单的建议,我喜欢做这种故障排除的方法是构建一个查询,让我在那里大部分时间,并正确优化它,然后在它周围添加必要的pl / pgsql。这种方法的主要优点是您可以根据查询计划进行优化。
此外,如果你没有处理很多行,array_agg()和unnest()是你的朋友,因为他们允许你(在Pg 8.4及更高版本!)免除临时表管理开销并简单地构造和查询内存中的元组数组作为关系。如果你只是在内存而不是临时表中访问一个数组(计划开销更少,查询开销也更少),它也可能表现更好。
另外,在您更新的查询中,我会考虑用子查询或连接替换最终循环,允许规划器决定何时进行嵌套循环查找或何时尝试找到更好的方法。