可以在CTE中订购JOIN操作吗? (PostgreSQL的)

时间:2016-12-21 23:57:06

标签: database postgresql common-table-expression

PostgreSQL 9.5

以下CTE可正常将记录从表格CPT和CPT_INVOICE移至DOCTOR_PROCEDURES并相应地更新DOCTORBILLING uid。但是,CPT_INVOICE具有其父级CPT的外键,因此该脚本将失败,直到删除该外键关系为止。

有没有办法迫使PostgreSQL按特定顺序执行CTE,即首先在planB之前执行planC?

TIA

WITH planA AS (
    select cpt_recid from doctorbilling
),
planC as (
    delete from cpt_invoice D
    USING planA a
    where D.recid = A.cpt_recid
    returning D.cpt_recid, D.ninsurance, D.ncash, D.mustschedule, D.doneinoffice
),
planB as (
    delete from cpt C
    USING planA A
    where C.recid = A.cpt_recid
    returning C.recid as cpt_recid, C.code, C.cdesc, C.procedure_type, C.sex
),
planD as (
    insert into doctor_procedures (code, cdesc, procedure_type, sex, ninsurance, ncash, mustschedule, doneinoffice, cpt_recid)
    select distinct on (b.cdesc)  b.code, b.cdesc, b.procedure_type, b.sex, c.ninsurance, c.ncash, c.mustschedule, c.doneinoffice, a.cpt_recid
    from planA A
    join planB B on B.cpt_recid = A.cpt_recid
    left join planC C on C.cpt_recid = A.cpt_recid   -- there may not be a cpt_invoice for the cpt_recid.
    order by b.cdesc
    returning cpt_recid, uid
)
update doctorbilling T
set uid = D.uid
from planD D
where T.cpt_recid = D.cpt_recid

2 个答案:

答案 0 :(得分:1)

首先,一般来说,如果你重复订单依赖,你最好以其他方式解决这个问题。 SQL是一种声明性语言,没有排序的概念,所以我们在这里做的任何事情都依赖于实现细节而不是标准的预期行为。你最好的选择是用一个用户定义的函数包装,逻辑更明确地分解。作为替代方法,您可以在运行此查询之前标记外键约束DEFERRABLE并将其设置为DEFERRED(然后在查询之后将其设置为IMMEDIATE)。这些将是解决问题的最佳选择和正确方法。

关于您特定的所需解决方案。这里的问题不是需要一般订购CTE,而是更需要订购连接操作。我认为以下在这种特殊情况下可能是安全的,但我并不完全确定(即更聪明的计划者将来可能会破坏它)。

planD as (
    insert into doctor_procedures (code, cdesc, procedure_type, sex, ninsurance, ncash, mustschedule, doneinoffice, cpt_recid)
    select distinct on (b.cdesc)  b.code, b.cdesc, b.procedure_type, b.sex, c.ninsurance, c.ncash, c.mustschedule, c.doneinoffice, a.cpt_recid
    from planA A
    left join planC C on C.cpt_recid = A.cpt_recid   -- there may not be a cpt_invoice for the cpt_recid.
    join planB B on B.cpt_recid = A.cpt_recid OR B.cpt_recid = c.cpt_recid
    order by b.cdesc
    returning cpt_recid, uid
)

我对这个长期解决方案并不完全有信心的原因是,一个更聪明的假设刨床可能会注意到c.cpt_recid总是等于a.cpt_recid,因此OR子句总是多余的。

答案 1 :(得分:1)

最简单的解决方案是使计划B依赖于计划C(删除计划A):

WITH planC as (
    delete from cpt_invoice D
    USING doctorbilling A
    where D.recid = A.cpt_recid
    returning D.recid, D.cpt_recid, D.ninsurance, D.ncash, D.mustschedule, D.doneinoffice
),
planB as (
    delete from cpt C
    USING planC X
    where C.recid = X.recid
    returning C.recid as cpt_recid, C.code, C.cdesc, C.procedure_type, C.sex
),
planD as (
    insert into doctor_procedures (code, cdesc, procedure_type, sex, ninsurance, ncash, mustschedule, doneinoffice, cpt_recid)
    select distinct on (b.cdesc)  b.code, b.cdesc, b.procedure_type, b.sex, c.ninsurance, c.ncash, c.mustschedule, c.doneinoffice, b.cpt_recid
    from planB B
    left join planC C on C.cpt_recid = B.cpt_recid   -- there may not be a cpt_invoice for the cpt_recid.
    order by b.cdesc
    returning cpt_recid, uid
)
update doctorbilling T
set uid = D.uid
from planD D
where T.cpt_recid = D.cpt_recid;

但是,这看起来有点奇怪,因为您在表doctorbilling的所有行上进行了大量不合格的数据修改语句。在实践中,您更有可能一次移动一个cpt_recid,这将使查询更加直接:

WITH planC as (
    delete from cpt_invoice D
    where D.recid = <<cpt_recid>>
    returning D.recid, D.cpt_recid, D.ninsurance, D.ncash, D.mustschedule, D.doneinoffice
),
planB as (
    delete from cpt C
    USING planC X
    where C.recid = X.recid -- maintain dependency
    returning C.code, C.cdesc, C.procedure_type, C.sex
),
planD as (
    insert into doctor_procedures (code, cdesc, procedure_type, sex, ninsurance, ncash, mustschedule, doneinoffice, cpt_recid)
    select distinct on (b.cdesc)  b.code, b.cdesc, b.procedure_type, b.sex, c.ninsurance, c.ncash, c.mustschedule, c.doneinoffice, b.cpt_recid
    from planB B
    left join planC C on true   -- there may not be a cpt_invoice for the cpt_recid.
    order by b.cdesc
    returning uid
)
update doctorbilling T
set uid = D.uid
from planD D
where T.cpt_recid = <<cpt_recid>>;

更好的是PL / pgSQL函数:

CREATE FUNCTION move_recid (id integer) RETURNS void AS $$
DECLARE
    ... -- declare all variables
BEGIN
    delete from cpt_invoice
    where recid = id
    returning cpt_recid, ninsurance, ncash, mustschedule, doneinoffice
         into inv_recid, inv_ins, inv_cash, inv_sch, inv_doi;

    delete from cpt
    where recid = id
    returning code, cdesc, procedure_type, sex
         into cpt_code, cpt_desc, cpt_proc, cpt_sex;

    insert into doctor_procedures (code, cdesc, procedure_type, sex, ninsurance, ncash,
                                   mustschedule, doneinoffice, cpt_recid)
    values (cpt_code, cpt_desc, ...)
    returning uid into dp_uid;

    update doctorbilling
    set uid = dp_uid
    where cpt_recid = id;
END;
$$ LANGUAGE plpgsql STRICT;

订单保证。其他程序员易于理解,易于维护。