PostgreSQL 相同的触发器函数在 INSERT 上更新到不同的表(使用相同的模式)

Posted

技术标签:

【中文标题】PostgreSQL 相同的触发器函数在 INSERT 上更新到不同的表(使用相同的模式)【英文标题】:PostgreSQL same trigger function to UPDATE on INSERT into different tables (using the same schema) 【发布时间】:2019-06-07 03:48:16 【问题描述】:

我有 100 多个具有相同架构的表,并且我有一个触发器函数,可以在将数据插入到该表时更新某些列。

表架构:

CREATE TABLE symbol_daily_ohlc (
 cdate date,
 open numeric(8,2),
 high numeric(8,2),
 low numeric(8,2),
 close numeric(8,2),
 sma8 numeric(8,2)
);

触发功能:

create or replace function update_sma8() RETURNS TRIGGER AS
$$
BEGIN
UPDATE symbol_daily_ohlc d SET sma8 = s.simple_mov_avg 
FROM
(
 SELECT  sec.cdate,AVG(sec.close)  
 OVER(ORDER BY sec.cdate ROWS BETWEEN 7 PRECEDING AND CURRENT ROW) AS 
 simple_mov_avg FROM symbol_daily_ohlc sec
)s where s.cdate = NEW.cdate  --The newly inserted cdate
 AND d.cdate = s.cdate;   
RETURN NULL;
END $$ language plpgsql;

在桌子上设置触发器:

CREATE TRIGGER trig_update_sma
AFTER INSERT ON symbol_daily_ohlc
FOR EACH ROW
EXECUTE PROCEDURE update_sma8();

这适用于给定的表,即 symbol_daily_ohlc。我想使用相同的触发函数,即 update_sma8() 用于具有相同架构的任何表(我不想为不同的表重写相同的函数)。

我尝试用 TG_TABLE_NAME 替换表名(即 symbol_daily_ohlc),但这不起作用 - 引发错误。那么该怎么做呢?

参考:SQL trigger function to UPDATE daily moving average upon INSERT

【问题讨论】:

您不应该拥有具有相同架构的表。如果您正在考虑这样的事情,那么您的数据模型可能存在问题。所有这些数据都应该放在一个表中,其中的一列定义了您希望数据具有的任何其他属性。 该数据库用于存储股票市场的历史数据,即不同品种(股票)的数据。我认为这是研究特定股票的更好方法。 然而,拥有一个名为“stock_symbol”的列就意味着不会出现这个问题。如果你愿意,你仍然可以拥有一百万张“桌子”。CREATE VIEW msft_history AS SELECT * FROM all_stock_history WHERE stock_symbol = 'MSFT';CREATE VIEW goog_history AS SELECT * FROM all_stock_history WHERE stock_symbol = 'GOOG' 而不是 UPDATE 将您想要的值分配给 new 伪记录的各个列。然后,您将不会在触发器主体中拥有表名,并且可以将其用于具有您寻址的列的每个表(当然具有兼容的类型)。 @GordonLinoff 我接受了您的建议,并相应地更改了我的数据库。感谢你和凯厄斯·贾德。 【参考方案1】:

您可以使用相同的过程为所有表执行并返回触发器,但不能为所有表使用相同的触发器。

这是一个动态创建带有表名后缀的触发器的块(使用EXECUTE format

DO $$
declare
tabs RECORD;
BEGIN
for tabs IN
(select table_name,table_schema
   from information_schema.tables where table_name 
   like 'symbol_daily_ohlc%' 
  -- and table_schema like '%'
) LOOP
EXECUTE format('CREATE TRIGGER check_update_%I
    AFTER INSERT ON %I.%I
    FOR EACH ROW
EXECUTE PROCEDURE update_sma8()',tabs.table_name,
 tabs.table_schema
,tabs.table_name);
END LOOP;
END $$;

这是你的触发器,它从TG_TABLE_NAME动态获取表名

create or replace function update_sma8() RETURNS TRIGGER AS
$$
 BEGIN

EXECUTE format ('UPDATE %I d SET sma8 = s.simple_mov_avg 
FROM
(
 SELECT  sec.cdate,AVG(sec.close)  
   OVER(ORDER BY sec.cdate ROWS BETWEEN 7 PRECEDING AND CURRENT ROW) AS 
    simple_mov_avg FROM %I sec
)s where s.cdate = %L  --The newly inserted cdate
     AND d.cdate = s.cdate',TG_TABLE_NAME,TG_TABLE_NAME,NEW.cdate);   
RETURN NULL;

END $$ language plpgsql;

Demo

正如其他人所建议的,拥有多个具有相同结构的表并不是一个好主意。您应该考虑将它们合并到一张表中。

【讨论】:

你能回答真是太好了。谢谢你。我也会评估其他人的建议。【参考方案2】:

我认为您可能必须使用 SQL 来生成 SQL,然后运行生成的 SQL。比如:

select CONCAT('create or replace function update_sma8() RETURNS TRIGGER AS
  $$
  BEGIN
  UPDATE ', table_name, '  d SET sma8 = s.simple_mov_avg 
  FROM
  (
   SELECT  sec.cdate,AVG(sec.close)  
   OVER(ORDER BY sec.cdate ROWS BETWEEN 7 PRECEDING AND CURRENT ROW) AS 
   simple_mov_avg FROM ', table_name, '
  )s where s.cdate = NEW.cdate  --The newly inserted cdate
  AND d.cdate = s.cdate;   
  RETURN NULL;
  END $$ language plpgsql;
') from information_schema.tables

但实际上,您应该考虑 Gordon 的建议(以及我的后续行动)并将所有数据放回一张表中:

SELECT CONCAT('INSERT INTO all_hist SELECT ''', table_name, ''', t.* FROM ', table_name) FROM information_schema.tables

这将生成一堆将所有表数据放入 all_hist 的 sql,该表应该与其他数千个表具有相同的架构,除了额外的列“符号”或其他列。

如果你真的想要的话,你可以使用类似的技巧为自己创建一堆视图来重新创建数千个表的方法。

【讨论】:

感谢您的回答。我会评估您的建议。

以上是关于PostgreSQL 相同的触发器函数在 INSERT 上更新到不同的表(使用相同的模式)的主要内容,如果未能解决你的问题,请参考以下文章

PostgreSQL 日志触发器/函数查询数据

如何在 PostgreSQL 触发器函数中获取表名?

使用触发器/函数计算对 postgresql 的兴趣

PostgreSQL - 如何将函数的参数传递给触发器?

无法访问 PostgreSQL 触发函数中的主键值

插入后PostgreSQL触发函数更新