Nat*_*u16 5 postgresql trigger dynamic-sql plpgsql postgresql-9.1
就像我的第一个问题中详细介绍的那样,我有一个Postgres 9.1数据库,其中有多个表,这些表具有完全相同的列名,它们仅在列值上有所不同:
tbl_log_a
tbl_log_b
tbl_log_c
...
Run Code Online (Sandbox Code Playgroud)
26 个表(从 a 到 z)。trfn_tbl_log_%letter%
每个表都有一个触发器,它调用名为(from a
to )的触发器函数z
,该函数执行完全相同的操作:
CREATE OR REPLACE FUNCTION trfn_tbl_log_a_timetypespan()
RETURNS trigger AS
$BODY$
DECLARE
v_timetype character varying;
v_timestmp_timetype timestamp without time zone;
v_timetypespan_resume interval;
v_stmtserial real;
v_sumtimetypespan_fnname interval;
BEGIN
IF NEW.timetype = 'lap' THEN
SELECT timetype, timestmp, timetypespan FROM tbl_log_a WHERE fnname = NEW.fnname AND (timetype = 'start' OR timetype = 'resume') ORDER BY stmtserial DESC LIMIT 1 INTO v_timetype, v_timestmp_timetype, v_timetypespan_resume;
IF v_timetype = 'start' THEN
NEW.timetypespan := age(NEW.timestmp, v_timestmp_timetype);
ELSIF v_timetype = 'resume' THEN
SELECT timestmp FROM tbl_log_a WHERE fnname = NEW.fnname AND timetype = 'start' ORDER BY stmtserial DESC LIMIT 1 INTO v_timestmp_timetype;
NEW.timetypespan := age(NEW.timestmp, v_timestmp_timetype) - v_timetypespan_resume;
ELSE
RAISE EXCEPTION USING MESSAGE = 'There is not any previous row...';
END IF;
ELSIF NEW.timetype = 'resume' THEN
SELECT timestmp FROM tbl_log_a WHERE fnname = NEW.fnname AND (timetype = 'start' OR timetype = 'lap') ORDER BY stmtserial DESC LIMIT 1 INTO v_timestmp_timetype;
IF FOUND THEN
NEW.timetypespan := age(NEW.timestmp, v_timestmp_timetype);
ELSE
RAISE EXCEPTION USING MESSAGE = 'There is not any previous row...';
END IF;
ELSIF NEW.timetype = 'total' THEN
SELECT stmtserial FROM tbl_log_a WHERE fnname = NEW.fnname AND timetype = 'total' ORDER BY stmtserial DESC LIMIT 1 INTO v_stmtserial;
SELECT SUM(timetypespan) FROM (SELECT DISTINCT ON (floor(timeidx)::int) floor(timeidx)::int timeidx, timetypespan
FROM tbl_log_a WHERE fnname = NEW.fnname AND timetype = 'lap' AND stmtserial > coalesce(v_stmtserial, 0) ORDER BY 1, 2 DESC) a INTO v_sumtimetypespan_fnname;
IF v_sumtimetypespan_fnname NOTNULL THEN
NEW.timetypespan := v_sumtimetypespan_fnname;
ELSE
RAISE EXCEPTION USING MESSAGE = 'There is not any previous row...';
END IF;
END IF;
return NEW;
END
$BODY$
LANGUAGE plpgsql VOLATILE;
Run Code Online (Sandbox Code Playgroud)
触发器定义:
CREATE TRIGGER trfn_tbl_log_a_timetypespan
BEFORE INSERT ON tbl_log_a
FOR EACH ROW EXECUTE PROCEDURE trfn_tbl_log_a_timetypespan();
Run Code Online (Sandbox Code Playgroud)
所以我必须创建 26 个触发器函数,每个tbl_log_%letter%
. 我试图用一个通用的触发函数来替换它们。
我们在我之前的问题下通过简化的函数制定了动态 SQL 的解决方案。相同的技术可以扩展到这个更复杂的场景吗?
EXECUTE format($$...
Run Code Online (Sandbox Code Playgroud)
是的,这应该有效(未经测试):
CREATE OR REPLACE FUNCTION trfn_tbl_log_timetypespan() -- generic name
RETURNS trigger AS
$func$
DECLARE
_timetype varchar;
_timetypespan_resume interval;
_ct int;
BEGIN
CASE NEW.timetype
WHEN 'lap' THEN
EXECUTE format($$
SELECT timetype, timetypespan, age($1, timestmp)
FROM %s
WHERE fnname = $2
AND timetype IN ('start', 'resume')
ORDER BY stmtserial DESC
LIMIT 1$$
, TG_RELID::regclass)
USING NEW.timestmp, NEW.fnname
INTO _timetype, _timetypespan_resume, NEW.timetypespan;
CASE _timetype
WHEN 'start' THEN -- do nothing
WHEN 'resume' THEN
EXECUTE format($$
SELECT age($1, timestmp) - _timetypespan_resume
FROM %s
WHERE fnname = $2
AND timetype = 'start'
ORDER BY stmtserial DESC
LIMIT 1$$
, TG_RELID::regclass)
USING NEW.timestmp, NEW.fnname
INTO NEW.timetypespan;
ELSE
RAISE EXCEPTION 'There is no previous row.';
END CASE;
WHEN 'resume' THEN
EXECUTE format($$
SELECT age($1, timestmp)
FROM %s
WHERE fnname = $2
AND timetype IN ('start', 'lap')
ORDER BY stmtserial DESC LIMIT 1$$
, TG_RELID::regclass)
USING NEW.timestmp, NEW.fnname
INTO NEW.timetypespan;
GET DIAGNOSTICS _ct = ROW_COUNT;
IF _ct > 0 THEN -- do nothing
ELSE
RAISE EXCEPTION 'There is no previous row.';
END IF;
WHEN 'total' THEN
EXECUTE format($$
SELECT COALESCE(SUM(timetypespan), $1)
FROM (
SELECT floor(timeidx)::int, max(timetypespan) AS timetypespan
FROM %1$s
WHERE fnname = $2
AND timetype = 'lap'
AND stmtserial > coalesce(
(SELECT stmtserial
FROM %1$s
WHERE fnname = $2
AND timetype = 'total'
ORDER BY stmtserial DESC
LIMIT 1), 0)
GROUP BY 1
) sub$$
, TG_RELID::regclass)
USING NEW.timetypespan, NEW.fnname
INTO NEW.timetypespan;
GET DIAGNOSTICS _ct = ROW_COUNT;
IF _ct > 0 THEN -- do nothing
ELSE
RAISE EXCEPTION 'There is no previous row.';
END IF;
END CASE;
RETURN NEW;
END
$func$ LANGUAGE plpgsql;
Run Code Online (Sandbox Code Playgroud)
这是我之前对您之前问题的回答的后续内容。在那里找到解释:
在做这件事的过程中,我简化了一些事情。SELECT
就像:第一个分支中的第二个ELSIF
只是SELECT
上面一个级别的重复。我免费合并了它。
我还删除了一些不必要的中间步骤,并NEW
直接分配给适用的字段。这就是为什么我可以删除你的大部分变量。
旁白:如果timeidx
只有正数,您可以使用更便宜的trunc(timeidx)
代替floor(timeidx)
。
如果您只想为 - 即实现相同的功能,则(在应用and之后)tbl_log_a
的有效执行代码如下所示:tbl_log_a
format()
EXECUTE
...
CASE NEW.timetype
WHEN 'lap' THEN
SELECT timetype, timetypespan, age(NEW.timestmp, timestmp)
FROM tbl_log_a
WHERE fnname = NEW.fnname
AND timetype IN ('start', 'resume')
ORDER BY stmtserial DESC
LIMIT 1
INTO _timetype, _timetypespan_resume, NEW.timetypespan;
CASE _timetype
WHEN 'start' THEN -- do nothing
WHEN 'resume' THEN
SELECT age(NEW.timestmp, timestmp) - _timetypespan_resume
FROM tbl_log_a
WHERE fnname = NEW.fnname
AND timetype = 'start'
ORDER BY stmtserial DESC
LIMIT 1
INTO NEW.timetypespan;
...
Run Code Online (Sandbox Code Playgroud)
归档时间: |
|
查看次数: |
274 次 |
最近记录: |