pl / pgsql CTE插入父子表和ROWTYPE数组

时间:2016-06-17 13:47:42

标签: postgresql plpgsql

我有两个历史表。一个是父母,第二个是细节。在这种情况下,它们是跟踪另一个表中更改的历史记录表。

CREATE TABLE IF NOT EXISTS history (
    id serial PRIMARY KEY,
    tablename text,
    row_id integer,
    ts timestamp,
    username text,
    source text,
    action varchar(10)
);

CREATE TABLE IF NOT EXISTS history_detail (
    id serial PRIMARY KEY,
    master_id integer NOT NULL references history(id),
    colname text,
    oldval text,
    newval text
);

然后我有了将现有行与新行进行比较的函数。比较对我来说似乎很直接。我正在努力的部分是当我想将差异插入我的历史表时。在比较期间,我将差异存储到history_detail的数组中,当然那时我不知道id或父表行是什么。这就是我被挂断的地方。

CREATE OR REPLACE FUNCTION update_prescriber(_npi integer, colnames text[]) RETURNS VOID AS $$
DECLARE
    t text[];
    p text[];
    pos integer := 0;
    ts text;
    updstmt text := '';
    sstmt text := '';
    colname text;
    _id integer;
    _tstr text := '';
    _dtl history_detail%ROWTYPE;
    _dtls history_detail[] DEFAULT '{}';
BEGIN
    -- get the master table row id.
    SELECT id INTO _id FROM master WHERE npi = _npi;

    -- these select all the rows' column values cast as text.
    SELECT unnest_table('tempmaster', 'WHERE npi = ''' || _npi || '''') INTO t;
    SELECT unnest_table('master', 'WHERE npi = ''' || _npi || '''') INTO p;

    -- go through the arrays and compare values
    FOREACH ts IN ARRAY t
    LOOP
            pos := pos + 1;
            -- pos + 1 becuse the master table has the ID column
            IF p[pos + 1] != ts THEN
                    colname := colnames[pos];
                    updstmt := updstmt || ', ' || colname || '=t.' || colname;
                    sstmt := sstmt || ',' || colname;
                    _dtl.colname := colname;
                    _dtl.oldval := p[pos + 1];
                    _dtl.newval := ts;
                    _dtls := array_append(dtls, dtl);
                    RAISE NOTICE 'THERE IS a difference at for COLUMN %, old: %, new: %', colname, p[pos + 1], ts;
            END IF;

    END LOOP;

    RAISE NOTICE 'dtls length: %', array_length(dtls,1);
    RAISE NOTICE 'dtls: %', dtls;
    RAISE NOTICE 'done comparing: %', updstmt;
    IF length(updstmt) > 0 THEN
            WITH hist AS (
                    INSERT INTO history
                    (tablename, row_id, ts, username, source, action)
                    VALUES
                    ('master', _id, current_timestamp, 'me', 'source', 'update')
                    RETURNING *
            ), dtls AS (
                    SELECT hist.id_
            INSERT INTO history_detail
--
-- this is where I am having trouble 
--
            ;

            _tstr := 'UPDATE master
                    SET ' || substr(updstmt,2) || '
                    FROM (SELECT ' || substr(sstmt,2) || ' FROM tempmaster WHERE npi = ''' || _npi || ''') AS t
                    WHERE master.id = ' || _id || ';';
            EXECUTE _tstr;
    END IF;
END;
$$ LANGUAGE plpgsql;

在一个理想的世界里,我可以在一份声明中完成所有这些。我知道我可以在包含在另一个BEGIN..END内的多个语句中执行此操作。我想确保以最有效的方式做到这一点。我不认为有办法摆脱动态EXECUTE,但希望比我更聪明的人可以把我推向正确的方向。

感谢您的帮助。

1 个答案:

答案 0 :(得分:0)

我能够创建一个可以一次插入2个历史记录表的语句。

WITH hist AS (
    INSERT INTO history
    (tablename, row_id, ts, username, source, action)
    VALUES
    ('master', _id, current_timestamp, 'me', 'source', 'update')
    RETURNING id
), dtls AS (
    SELECT (my_row).* 
    FROM unnest(_dtls) my_row
), inserts AS (
    SELECT hist.id AS master_id,
           dtls.colname AS colname,
           dtls.newval AS newval,
           dtls.oldval AS oldval
    FROM dtls,hist
)
INSERT INTO history_detail
(master_id, colname, newval, oldval)
SELECT * FROM inserts
;

我仍然希望将列更新添加为不是EXECUTE语句的内容,但我真的不认为这是可能的。