在plpgsql(PostgreSQL)中,CTE可以保存到外部循环吗?

时间:2015-11-05 11:16:30

标签: database postgresql plpgsql

(原件编辑)。

在plpgsql中,(PostgreSQL 9.2),我有一个函数定义为:

CREATE OR REPLACE FUNCTION test (patient_recid integer, tencounter timestamp without time zone)
  RETURNS SETOF view_dx AS
$BODY$ 

#variable_conflict use_column

DECLARE
    r view_dx%rowtype;

BEGIN

FOR r IN 

With person AS (
    select ....
   )
, alldx AS (
    select ....
)
............

select  ... from first cte 
union 
select ... from second cte
union
etc., etc.,  

LOOP
    r.tposted = (       .
            With person AS (
                ... SAME AS ABOVE, 
                      alldx AS (
                ... SAME AS ABOVE,
            )
            select max(b.tposted)
            from alldx b
            where r.cicd9 = b.code and r.cdesc = b.cdesc);

    r.treated = (   
                With person AS (
                ........SAME AS ABOVE           )
                , alldx AS (
                ........SAME AS ABOVE   
                )
                select ...);

    r.resolved =  (     
                With person AS (
                select p.chart_recid as recid
                from patients p
                where p.recid = patient_recid
                )
                ...etc, etc, 

     RETURN NEXT r; 

END LOOP;

RETURN;

END

$BODY$
  LANGUAGE plpgsql VOLATILE
  COST 100
  ROWS 1000;
ALTER FUNCTION test(integer, timestamp without time zone)
  OWNER TO postgres;

编辑:基本上,我有多个cte定义,它们在具有多个联合的代码的“For r IN”部分中运行良好,但在执行LOOP ... END LOOP部分时,每个CTE都需要重新定义每个SELECT声明。是否有一种避免同一CTE多重定义的好方法?

或者是否有更好(即更快)的方式。

欢迎并赞赏所有建议。

TIA

2 个答案:

答案 0 :(得分:1)

[这不是一个答案(信息太少,程序太大),但提示重写堆叠的CTE。]

联盟成员似乎都基于WITH person AS ( SELECT p.chart_recid as recid FROM patients p WHERE p.recid = patient_recid ) , alldx AS ( SELECT d.tposted, d.treated, d.resolved, d.recid as dx_recid, d.pmh, d.icd9_recid , i.code, i.cdesc, i.chronic FROM dx d JOIN icd9 i ON d.icd9_recid = i.recid JOIN person p ON d.chart_recid = p.recid WHERE d.tposted::date <= tencounter::date ) SELECT uni.tposted, uni.treated, uni.resolved, uni.dx_recid, uni.pmh, uni.icd9_recid , uni.code, uni.cdesc, uni.chronic , (uni.tposted::date = tencounter::date ) AS is_dx_at_encounter -- bitfield , EXISTS ( -- a record from a more recent date has resolved this problem. SELECT 1 FROM alldx x WHERE x.resolved = true AND uni.code = x.code AND uni.cdesc = x.cdesc AND uni.tposted = x.tposted AND x.tposted >= uni.tposted ) AS dx_resolved -- bitfield , EXISTS ( -- a record from a more recent date has resolved this problem. SELECT 1 FROM alldx x WHERE x.resolved = false AND uni.code = x.code AND uni.cdesc = x.cdesc AND uni.tposted = x.tposted AND x.tposted > uni.tposted ) AS dx_recurred -- bitfield , EXISTS ( SELECT * from alldx x where x.chronic = true AND uni.code = x.code AND uni.cdesc = x.cdesc ) AS dx_chronic -- bitfield -- etcetera FROM alldx uni ; ,所有这些都有一些不同的额外条件,主要是基于同一CTE中其他元组的存在。我的建议是统一这些,用布尔标志替换它们,如:

person
  • UNION CTE也可能被合并。
  • 也许你甚至不需要最后的循环
  • 但您必须找出所需的结果位域组合。
  • 原始中的ALL(没有Page.ClientScript.RegisterStartupScript(this.GetType(), "OpenWindow", "window.open('/Reports/EncroachmentPermit.aspx?pguid=' + CurrentPermitGuid, '_newtab');", true); )是一只可怕的野兽:它收集了联合部分的所有结果,但必须删除重复项。这可能会引入一个排序步骤,因为CTE引用倾向于隐藏它们的关键字段或隐含的来自调用查询的顺序。

答案 1 :(得分:0)

据我所知,在LOOP之前定义的CTE不会转移到LOOP本身。但是,可以在BOPIN块中定义临时表,该表可在LOOP块中使用。以下解决方案比原始代码快50倍。有人有更好的方法吗?

CREATE OR REPLACE FUNCTION test2 (patient_recid integer, tencounter timestamp without time zone)
  RETURNS SETOF view_dx AS
$BODY$ 

#variable_conflict use_column



DECLARE
    r view_dx%rowtype;


BEGIN
    -- create table can only be created in the BEGIN block
    Create temp table all_dx ON COMMIT DROP AS
    With person AS (
        select p.chart_recid as recid
        from patients p
        where p.recid = patient_recid
        )
       , alldx AS (
        select d.tposted, d.treated, d.resolved, d.recid as dx_recid, d.pmh, d.icd9_recid, i.code, i.cdesc, i.chronic
        from dx d
        join icd9 i on (d.icd9_recid = i.recid)
        join person p on (d.chart_recid = p.recid)
        where d.tposted::date <= tencounter::date
        )
      select * from alldx order by tposted desc;    

-- will loop through all the records produced by the unions and assign tposted, pmh, chronic, etc...
FOR r IN 

With 
dx_at_encounter AS (        -- get all diagnosis at time of encounter
    select code, cdesc from all_dx a
    where a.tposted::date = tencounter::date
)
, dx_resolved AS (              -- get most recent date of every resolved problem.
    select b.* from all_dx b
    join (
        select a.code, a.cdesc , max(tposted) as tposted
        from all_dx a
        where a.resolved = true 
        group by code,cdesc) j
    on (b.code = j.code and b.cdesc = j.cdesc and b.tposted = j.tposted)    
)
, never_resolved AS (       -- get all problems that have never been resolved before time of encounter.
                -- "not exists" is applied to each select output row AFTER the output row b.* is formed.
    select b.code, b.cdesc from all_dx b
    where not exists 
        (select 1 
         from dx_resolved d
         where b.code = d.code and b.cdesc = d.cdesc) 
)
, recurrent AS (        -- get all recurrent problems. (Problems that are now current after being resolved).
    select  b.code, b.cdesc
    from all_dx b
    join dx_resolved r on (b.cdesc = r.cdesc and b.tposted::date > r.tposted::date )
    where (b.resolved is null or b.resolved = false)  

)
, chronic_dx AS (
    select b.code, b.cdesc
    from all_dx b
    where b.chronic = true
)

-- all diagnosis at time of encounter
select  a.code, 
    a.cdesc
from dx_at_encounter a

union 
-- all recurrent problems
select 
    a.code, 
    a.cdesc
from recurrent a

union

-- all problems that have never been resolved
select 
    a.code, 
    a.cdesc
from never_resolved a

union

--all chonic problems
select 
    a.code, 
    a.cdesc
from chronic_dx a

-- LOOP goes to END LOOP which returns back to LOOP to process each of the result records from the unions.
LOOP
    r.tposted = (       -- get most recent useage of a diagnosis.
            select max(b.tposted)
            from all_dx b
            where r.cicd9 = b.code and r.cdesc = b.cdesc);

    r.treated = (   
            select b.treated from all_dx b
            where b.tposted = r.tposted and b.code = r.cicd9 and b.cdesc = r.cdesc);

    r.resolved =  (     
            select b.resolved from all_dx b
                where b.tposted = r.tposted and b.code = r.cicd9 and b.cdesc = r.cdesc);

    r.pmh = (
            select distinct true 
            from all_dx b
            where
            b.pmh = true and 
            b.code = r.cicd9 and 
            b.cdesc = r.cdesc ); 

    r.chronic = (
            select distinct true 
            from all_dx b
            where
            b.chronic = true and 
            b.code = r.cicd9 and 
            b.cdesc = r.cdesc); 

     RETURN NEXT r; -- return current row of SELECT

END LOOP;

RETURN;

END

$BODY$
  LANGUAGE plpgsql VOLATILE
  COST 100
  ROWS 1000;
ALTER FUNCTION test2(integer, timestamp without time zone)
  OWNER TO postgres;