使用pl / pgsql

时间:2017-01-28 00:04:40

标签: postgresql

问题:是否有一种方法(可能是pl / pgsql函数方式?)使用我的日志表中找到的值在特定表上创建INSERT / UPDATE / DELETE查询(即值'操作',' schema_name',' table_name',' column_name',' data_type'(即列数据type)和' new_val'?)。

正在记录的表和我需要运行INSERT / UPDATE /或DELETE的表如下所示:

enter image description here

..并且日志表如下所示:

enter image description here

... 4个突出显示的日志条目应该像这样插入到表中:

enter image description here

...我试图找到一种方法在 ANOTHER DATABASE表上运行INSERT / UPDATE /或DELETE(这与表中的名称/模式/等相同)选择具体的' usr'和' event_date'在日志记录表中。

为了得到我想要的结果(仅针对INSERT语句 - 见下文),SQL非常笨拙(demo in SQL FIDDLE)。我很想知道另一种方式是否可行......

INSERT INTO Engineering.Elective_Courses 
            (gid, grade, class, student_id) 
    WITH
    t1 AS
    (Select new_val 
    From student.history
    WHERE
       column_name = 'gid'
    AND
       usr = 'Principal K.'
    AND
       (event_date >= '2017-01-26' AND event_date <  '2017-01-29')),
    t2 AS (Select new_val 
    From student.history
    WHERE
       column_name = 'grade'
    AND
       usr = 'Principal K.'
    AND
       (event_date >= '2017-01-26' AND event_date <  '2017-01-29')),
    t3 AS (Select new_val 
    From student.history
    WHERE
       column_name = 'class'
    AND
       usr = 'Principal K.'
    AND
       (event_date >= '2017-01-26' AND event_date <  '2017-01-29')),
    t4 AS (Select new_val 
    From student.history
    WHERE
       column_name = 'student_id'
    AND
       usr = 'Principal K.'
    AND
       (event_date >= '2017-01-26' AND event_date <  '2017-01-29'))
     select t1.new_val::int, t2.new_val, t3.new_val, t4.new_val::int
      from t1,t2, t3, t4;

2 个答案:

答案 0 :(得分:2)

您必须使用dynamic SQL

此查询汇总各个操作的数据:

select 
    action, event_date, usr, 
    schema_name, table_name, pkey_id,
    string_agg(quote_ident(column_name), ',' order by history_id) as cols, 
--  string_agg(quote_literal(new_val), ',' order by history_id) as vals
--  correction:
    string_agg(coalesce(quote_literal(new_val), 'null'), ',' order by history_id) as vals
from student.history
group by 1, 2, 3, 4, 5, 6;

 action |     event_date      |     usr      | schema_name |    table_name    | pkey_id |            cols            |            vals            
--------+---------------------+--------------+-------------+------------------+---------+----------------------------+----------------------------
 DELETE | 2017-01-28 12:20:03 | Ast. Dean J. | Engineering | Elective_Courses | 14      | grade                      | 
 INSERT | 2017-01-26 22:42:53 | Principal K. | Engineering | Elective_Courses | 12      | gid,grade,class,student_id | '12','B-','PYS7C','607752'
 UPDATE | 2017-01-26 22:42:53 | Ast. Dean J. | Engineering | Elective_Courses | 13      | grade                      | 'C'
(3 rows)

为了更好的安全性,历史表应该有一个额外的唯一动作ID,以区分例如:同一个用户同时在同一个桌面上进行两次插入,但这种巧合不太可能。

该功能基于以上查询。它有一个参数,历史表上的WHERE子句的文本。它返回生成的查询。 它还可以执行查询(在注释的代码段中)。

create or replace function restore_log(condition text)
returns setof text language plpgsql as $$
declare
    relid regclass;
    pkey text;
    query text;
    rec record;
begin
    for rec in
        execute format('
            select 
                action, event_date, usr, 
                schema_name, table_name, pkey_id,
                string_agg(quote_ident(column_name), '','' order by history_id) as cols, 
                string_agg(coalesce(quote_literal(new_val), ''null''), '','' order by history_id) as vals
            from student.history
            where %s
            group by 1, 2, 3, 4, 5, 6',
            condition)
    loop
        relid:= format('%s.%s', rec.schema_name, rec.table_name)::regclass;
        pkey:= get_pkey_name(relid); -- see below
        query:= case rec.action
            when 'INSERT' then
                format(
                    'insert into %s.%s (%s) values (%s)',
                    rec.schema_name, rec.table_name, rec.cols, rec.vals)
            when 'UPDATE' then
                format(
                    'update %s.%s set (%s) = (%s) where %s = %s',
                    rec.schema_name, rec.table_name, rec.cols, rec.vals, pkey, rec.pkey_id)
            when 'DELETE' then
                format(
                    'delete from %s.%s where %s = %s',
                    rec.schema_name, rec.table_name, pkey, rec.pkey_id)
            else null end;
        return next query;
--      if query not null then
--          execute(query);
--      end if;
    end loop;
end $$;

用法示例:

select * from restore_log('true');

                                                restore_log                                                
-----------------------------------------------------------------------------------------------------------
 delete from Engineering.Elective_Courses where gid = 14
 insert into Engineering.Elective_Courses (gid,grade,class,student_id) values ('12','B-','PYS7C','607752')
 update Engineering.Elective_Courses set (grade) = ('C') where gid = 13
(3 rows)


select * from restore_log($$
    usr = 'Principal K.' 
    and event_date >= '2017-01-26' 
    and event_date <  '2017-01-29'$$);

                                                restore_log                                                
-----------------------------------------------------------------------------------------------------------
 insert into Engineering.Elective_Courses (gid,grade,class,student_id) values ('12','B-','PYS7C','607752')
(1 row)     

查找给定表的单列主键列名的函数(在restore_log()中使用):

create or replace function get_pkey_name(regclass)
returns name language sql as $$
    select attname
    from pg_constraint c
    join pg_attribute a on attrelid = conrelid and attnum = conkey[1]
    where conrelid = $1
    and contype = 'p'
    and cardinality(conkey) = 1
$$;

安全注意事项,出于安全原因,基本上您应该使用format('%I.%I, schema_name, table_name),但在这种情况下,由于在数据中使用大写字母,它会产生错误的结果。

答案 1 :(得分:1)

如果要插入多行,则显示的查询中的交叉连接将生成错误的笛卡尔积。永远不要使用它。

如果目标表是预定义的,那么普通crosstab()查询就可以完成工作:

INSERT INTO engineering.elective_courses (gid, grade, class, student_id)

SELECT * FROM crosstab(
     $$SELECT pkey_id, column_name, new_val 
       FROM   student.history
       WHERE  usr = 'Principal K.'       -- your criteria here
       AND    event_date >= '2017-01-26'
       AND    event_date <  '2017-01-29'
       AND    action = 'INSERT'
       ORDER  BY 1$$
   , $$SELECT unnest('{grade,class,student_id}'::text[])$$)
   AS ct (gid int, grade varchar, class varchar, student_id int);

gid(示例中为history_id = 0)的额外行必须与列pkey_id匹配,并且完全是多余的。 crosstab()只是忽略了它,因为&#39; gid&#39;未在第二个函数参数中列为目标列。

详细说明:

如果不应预定义目标表,也可以动态创建语句。密切相关的答案:

高级: