【问题标题】:COALESCE failing following CTE Deletion. (PostgreSQL)删除 CTE 后 COALESCE 失败。 (PostgreSQL)
【发布时间】:2019-09-05 02:12:43
【问题描述】:

PostgreSQL 11.1 PgAdmin 4.1

有时会起作用

BEGIN;
    SET CONSTRAINTS ALL DEFERRED;

    WITH _in(trx, lastname, firstname, birthdate, old_disp, old_medname, old_sig, old_form, new_disp, new_medname, new_sig, new_form, new_refills)  AS (
            VALUES ('2001-06-07 00:00:00'::timestamp, 
                    UPPER(TRIM('JONES')), UPPER(TRIM('TOM')), '1952-12-30'::date,
                    64::integer, 
                    LOWER(TRIM('adipex 37.5mg tab')), LOWER(TRIM('one tab po qd')), LOWER(TRIM('tab')),
                    63::integer,
                    LOWER(TRIM('adipex 37.5mg tab')), LOWER(TRIM('one tab po qd')), LOWER(TRIM('tab')),
                    33::integer
                    )
            ),
        _s AS (                 -- RESOLVE ALL SURROGATE KEYS.
                SELECT n.*, d1.recid as old_medication_recid, d2.recid as new_medication_recid, pt.recid as patient_recid
                FROM _in n
                JOIN medications d1 ON (n.old_medname, n.old_sig, n.old_form) = (d1.medname, d1.sig, d1.form)
                JOIN medications d2 ON (n.new_medname, n.new_sig, n.new_form) = (d2.medname, d2.sig, d2.form)
                JOIN patients pt ON (pt.lastname, pt.firstname, pt.birthdate) = (n.lastname, n.firstname, n.birthdate)
        ),
         _t AS (               -- REMOVE CONFLICTING RECORD, IF ANY.
                DELETE FROM rx r
                USING _s n
                WHERE (r.trx::date, r.disp, r.patient_recid, r.medication_recid)=(n.trx::date, n.new_disp, n.patient_recid, n.new_medication_recid)
                RETURNING r.*
            ),
          _u  AS(               -- GET NEW SURROGATE KEY.
                SELECT COALESCE(_t.recid, r.recid) as target_recid, r.recid as old_recid
                FROM _s n
                JOIN rx r ON (r.trx, r.disp, r.patient_recid, r.medication_recid) = (n.trx, n.old_disp, n.patient_recid, n.old_medication_recid)
                LEFT JOIN _t ON (_t.trx::date, _t.disp, _t.patient_recid, _t.medication_recid) = (n.trx::date, n.new_disp, n.patient_recid, n.new_medication_recid)
            )                                   
        UPDATE rx r           -- UPDATE ORIGINAL RECORD WITH NEW VALUES.
        SET disp = n.new_disp, medication_recid = n.new_medication_recid, refills = n.new_refills, recid = _u.target_recid
        FROM _s n, _u
        WHERE r.recid = _u.old_recid
        RETURNING r.*;

COMMIT;

其中 table rx 定义为:

CREATE TABLE phoenix.rx
(
    recid integer NOT NULL DEFAULT nextval('rx_recid_seq'::regclass),
    trx timestamp without time zone NOT NULL,
    disp integer NOT NULL,
    refills integer,
    tprinted timestamp without time zone,
    tstop timestamp without time zone,
    modified timestamp without time zone DEFAULT now(),
    patient_recid integer NOT NULL,
    medication_recid integer NOT NULL,
    dposted date NOT NULL,
    CONSTRAINT pk_rx_recid PRIMARY KEY (recid),
    CONSTRAINT rx_unique UNIQUE (dposted, disp, patient_recid, medication_recid)

        DEFERRABLE,
    CONSTRAINT rx_medication_fk FOREIGN KEY (medication_recid)
        REFERENCES phoenix.medications (recid) MATCH SIMPLE
        ON UPDATE CASCADE
        ON DELETE RESTRICT
        DEFERRABLE,
    CONSTRAINT rx_patients FOREIGN KEY (patient_recid)
        REFERENCES phoenix.patients (recid) MATCH SIMPLE
        ON UPDATE CASCADE
        ON DELETE RESTRICT
)

几个小时后,发现冲突记录的“删除..”按预期工作,但在决定 rx.recid 的新代理键(主键)时,“COALESCE”语句似乎失败 - - 它似乎没有收到删除的结果。 (或者可能时机不对???)

任何帮助将不胜感激。

TIA

【问题讨论】:

    标签: postgresql constraints common-table-expression coalesce


    【解决方案1】:

    这个is documented

    WITH 中的子语句彼此同时执行,并与主查询同时执行。因此,当在WITH 中使用数据修改语句时,指定更新实际发生的顺序是不可预测的。所有语句都使用相同的快照执行(参见Chapter 13,因此它们无法“看到”彼此对目标表的影响。

    如果 CTE 出现在 DML 语句中,请勿在具有 CTE 的语句中使用同一个表两次。而是使用DELETE ... RETURNING 并在语句的其他部分使用返回值。

    如果您不能像那样重写语句,请使用多个语句,而不是将所有内容都放在一个 CTE 中。

    【讨论】:

    • 这可以解释发生了什么。关于如何实现这一点的建议?
    • 我添加了一般提示。你的特殊情况太复杂了,我无法理解。
    【解决方案2】:

    @LaurenzAlbe 的回答完全正确。以下是我的问题的有效解决方案。有几点需要注意:

    1. 唯一约束在 rx 中定义为日期的列上形成,并由更新/插入触发器创建,该触发器将 trx 的时间戳转换为日期:如 trx::date。由于我不清楚的原因,使用 r.trx::date 代替 r.dposted 会导致识别出许多记录,而不是我想要的一个记录。不知道为什么???所以第一个解决方法是使用 r.dposted,而不是 r.trx::date。
    2. 虽然 cte 被设计为彼此独立,但通过使用“返回...”并以逐步方式合并 cte,可以将一个 cte 建立在另一个之上以获得最终结果集。 工作代码是:

      WITH _in(trx, lastname, firstname, birthdate, old_disp, old_medname, old_sig, old_form, new_disp, new_medname, new_sig, new_form, new_refills)  AS (
                  VALUES ('2001-06-07 00:00:00'::timestamp, 
                          UPPER(TRIM('smith')), UPPER(TRIM('john')), '1957-12-30'::date,
                          28::integer, 
                          LOWER(TRIM('test')), LOWER(TRIM('i am sig')), LOWER(TRIM('tab')),
                          28::integer,
                          LOWER(TRIM('test 1')), LOWER(TRIM('i am sig')), LOWER(TRIM('tab')),
                          8::integer
                          )
                  ),  
                _m AS (
                      SELECT n.*, d1.recid as old_medication_recid, d2.recid as new_medication_recid, pt.recid as patient_recid
                      FROM _in n
                      JOIN patients pt ON (pt.lastname, pt.firstname, pt.birthdate) = (n.lastname, n.firstname, n.birthdate)
                      JOIN medications d1 ON (n.old_medname, n.old_sig, n.old_form) = (d1.medname, d1.sig, d1.form)
                      LEFT JOIN medications d2 ON (n.new_medname, n.new_sig, n.new_form) = (d2.medname, d2.sig, d2.form)              
                ),              
                _t AS (               -- REMOVE CONFLICTING RECORD, IF ANY.
                      DELETE FROM rx r
                      USING _m
                      WHERE (r.dposted, r.disp, r.patient_recid, r.medication_recid) = (_m.trx::date,_m.new_disp, _m.patient_recid, _m.new_medication_recid)
                      RETURNING r.*
                  ),
                _s AS (               -- GET NEW SURROGATE KEY
                      SELECT _m.*, r1.recid as old_recid, r2.recid as new_recid, COALESCE(r2.recid, r1.recid) as target_recid
                      FROM _m
                      JOIN rx r1 ON (r1.dposted, r1.disp, r1.patient_recid, r1.medication_recid) = (_m.trx::date,_m.old_disp, _m.patient_recid, _m.old_medication_recid)
                      LEFT JOIN rx r2 ON (r2.dposted, r2.disp, r2.patient_recid, r2.medication_recid) = (_m.trx::date,_m.new_disp, _m.patient_recid, _m.new_medication_recid)
                      LEFT JOIN _t ON (_t.recid = r2.recid)
                )
              UPDATE rx               -- UPDATE ORIGINAL RECORD WITH NEW VALUES.
              SET disp = _s.new_disp, medication_recid = _s.new_medication_recid, refills = _s.new_refills, recid = _s.target_recid
              FROM _s 
              WHERE rx.recid = _s.old_recid
              RETURNING rx.*;
      
      COMMIT;
      

    希望这对某人有所帮助。

    【讨论】:

      猜你喜欢
      • 2020-10-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-08-07
      • 1970-01-01
      • 2018-05-12
      • 1970-01-01
      • 2012-04-08
      相关资源
      最近更新 更多