metaclass: (Default)
metaclass ([personal profile] metaclass) wrote2010-10-01 12:52 pm

PostgreSQL, refcursor и проблема 1+N запросов

Решил я по совету использовать курсоры, чтобы вернуть результат нескольких запросов в одном резалтсете. Ну то есть решить проблему 1+N запросов.

Сделал такой примерчик
ссылко1
ссылко2, скрипт
По идее, на каждую строку результсета должен вернутся открытый курсор, то бишь, мы фетчим внешний резалтсет, а внутри цикла фетча пытаемся фетчить полученный курсор.

На данный момент пока хрен что у меня получается, т.к. все компоненты доступа(и в дельфи, и в дотнете) возвращают первое поле запроса результат запроса "select * from myfunc() as test(a refcursor,b varchar(40))" видят тупо как строку и ни с какого бока к этому курсору не подступится.

Синтаксис работы с курсорами и возвращения множеств из функций postgresql тоже в некотором роде печалит, но это следствие видимо того, что я привык в этому плане к аккуратному синтаксису Firebird.

PS: Кстати, что забавно: в примерах везде приведены только самые простые случаи типа "возвращаем один курсор, возвращаем два заведомо известных курсора", а вот комбинацию "возвращаем множество туплов из курсора и атомарного типа" пришлось придумывать самому, с извращениями в виде create type.

PPS: Гамон. Победил эту хрень. исправленный скрипт (отличия - именование курсора и убрано его закрытие)
1)курсоры нужно именовать уникально, типа
cur='returnedcursor' || cast(parentid as varchar);
2)курсоры не нужно закрывать, т.к. второй запрос выполняется только после того как целиком выполнится первый, а не внутри него, как хотелось бы.
3)возвращенное в первом запросе имя курсора нужно использовать, чтобы обратится к его результатам:
Delphi: 'fetch all from '+q.Fields[0].AsString;
.NET: cmd2.CommandText = "FETCH ALL FROM "+reader1.GetString(0);
4)в дотнете это работает только с PreloadReader=true, иначе ругается что "уже открыт DataReader", я про это уже писал, что только Firebird умеет открывать более одного ридера.

В общем, те же яйца, вид в профиль.
Не знаю, как насчет парсинга запроса и кэширования результата парсинга, запросто может быть, что оно каждый раз во внутреннем цикле это делает повторно, то бишь опять та же проблема 1+N, только стоя и в гамаке.
Кроме того, сразу видно, что PL/pgSQL это ад, т.к. присваивая переменной курсора имя, мы делаем это имя до закрытия курсора доступным в FETCH ALL FROM, то есть, явное смешение данных и метаданных. Это можно было бы считать метапрограммированием, но это больше похоже на динамическую типизацию(анафема!!).

[identity profile] plumqqz.livejournal.com 2010-10-03 08:13 am (UTC)(link)
Проще надо быть, проще:
CREATE OR REPLACE FUNCTION get_govnoandgaz()
  RETURNS refcursor AS
$BODY$
declare
 wss_cr refcursor;
 gaz_cr refcursor;
 pss_cr refcursor;
 sss_cr refcursor;
 ct_cr refcursor;
 pl_cr refcursor;
 cpt_cr refcursor;
 vt_cr refcursor;
 rv refcursor;
 commpt_cr refcursor;
 contr_cr refcursor;
 fct_cr refcursor;
 lav_cr refcursor;
 win_cr refcursor;
 bal_cr refcursor;
 yo_cr refcursor;
 
begin
 open wss_cr for select id, name from water_supply_state where orderer is not null order by orderer;
 open gaz_cr for select id, name from gas_supply_state where orderer is not null order by orderer;
 open pss_cr for select id, name from power_supply_state where orderer is not null order by orderer;
 open sss_cr for select id, name from sewerage_supply_state where orderer is not null order by orderer;
 open ct_cr for select id, name from country_type where orderer is not null order by orderer;
 open pl_cr for select id, name from place where parent_id=3 order by name;
 open cpt_cr for select id, name from country_property_type where orderer is not null order by orderer;
 open vt_cr for select id, name from village_type order by name;
 open commpt_cr for select id, name as name from commercial_property_type order by orderer;
 open contr_cr for select id, name from contract_type where orderer is not null order by orderer;
 open fct_cr for select id, name from flat_condition_type where orderer is not null order by orderer;
 open lav_cr for select id, name from lavatory_type where orderer is not null order by orderer;
 open win_cr for select id, name from windows_type where orderer is not null order by orderer;
 open bal_cr for select id, name from balcony_type where orderer is not null order by orderer;
 open yo_cr for select id, name from years_owned_type where orderer is not null order by orderer;
 
 open rv for select wss_cr as water_supply_states,
                    gaz_cr as gas_supply_states,
                    pss_cr as power_supply_states,
                    sss_cr as sewerage_supply_states,
                    ct_cr as country_types,
                    pl_cr as places,
                    cpt_cr as country_property_types,
                    vt_cr as village_types,
                    commpt_cr as commercial_property_type,
                    contr_cr as contract_type,
                    fct_cr as flat_condition_type,
		    lav_cr as lavatory_type,
		    win_cr as windows_type,
		    bal_cr as balcony_type,
		    yo_cr as years_owned_type,
                    (select value from currency.currency where symb='USD' order by added desc limit 1) as usd_rate;
 return rv;
end;
$BODY$
  LANGUAGE plpgsql VOLATILE
  COST 100;
ALTER FUNCTION get_govnoandgaz() OWNER TO postgres;