php – 将表和更改日志合并到PostgreSQL中的视图中
作者:互联网
我的PostgreSQL数据库包含一个用于存储已注册实体实例的表.此表格通过电子表格上传填充. Web界面允许运算符修改所呈现的信息.但是,原始数据不会被修改.所有更改都存储在单独的表中,其中包含unique_id,column_name,value和updated_at列.
更改完成后,首先查询原始表,然后查询更改表(使用实例ID和最新更改日期,按列名称分组),将其呈现给运算符.这两个结果在PHP中合并,并在Web界面上显示.这是执行任务的一种相当严格的方式,我希望将所有逻辑保留在SQL中.
我可以使用以下查询轻松选择表的最新更改:
SELECT fltr_chg.unique_id, fltr_chg.column_name, chg_val.value
FROM changes AS chg_val
JOIN (
SELECT chg_rec.unique_id, chg_rec.column_name, MAX( chg_rec.updated_at )
FROM information_schema.columns AS source
JOIN changes AS chg_rec ON source.table_name = 'instances'
AND source.column_name = chg_rec.column_name
GROUP BY chg_rec.unique_id, chg_rec.column_name
) AS fltr_chg ON fltr_chg.unique_id = chg_val.unique_id
AND fltr_chg.column_name = chg_val.column_name;
从实例表中选择条目同样简单:
SELECT * FROM instances;
现在,如果只有一种方法可以转换前一个结果并将结果值替换为后者,基于unique_id和column_name,并仍将结果保留为表,则问题将得到解决.这可能吗?
我确信这不是最罕见的问题,而且很有可能,某些系统会以类似的方式跟踪数据的变化.如果不是通过上述方法之一(当前和寻求的解决方案),他们如何将它们应用于数据?
解决方法:
假设Postgres 9.1或更高版本.
我简化/优化了您的基本查询以检索最新值:
SELECT DISTINCT ON (1,2)
c.unique_id, a.attname AS col, c.value
FROM pg_attribute a
LEFT JOIN changes c ON c.column_name = a.attname
AND c.table_name = 'instances'
-- AND c.unique_id = 3 -- uncomment to fetch single row
WHERE a.attrelid = 'instances'::regclass -- schema-qualify to be clear?
AND a.attnum > 0 -- no system columns
AND NOT a.attisdropped -- no deleted columns
ORDER BY 1, 2, c.updated_at DESC;
我查询PostgreSQL目录而不是标准信息模式,因为它更快.请注意07000的特殊演员表.
现在,这给你一张桌子.您希望连续一个unique_id的所有值.
要实现这一目标,您基本上有三种选择:
>每列一个子选择(或连接).昂贵而笨重.但只有几列的有效选项.
>一个大的CASE声明.
>枢轴功能. PostgreSQL为此提供了crosstab()
function in the additional module tablefunc
.
基本说明:
带交叉表的基本数据透视表()
我完全重写了这个功能:
SELECT *
FROM crosstab(
$x$
SELECT DISTINCT ON (1, 2)
unique_id, column_name, value
FROM changes
WHERE table_name = 'instances'
-- AND unique_id = 3 -- un-comment to fetch single row
ORDER BY 1, 2, updated_at DESC;
$x$,
$y$
SELECT attname
FROM pg_catalog.pg_attribute
WHERE attrelid = 'instances'::regclass -- possibly schema-qualify table name
AND attnum > 0
AND NOT attisdropped
AND attname <> 'unique_id'
ORDER BY attnum
$y$
)
AS tbl (
unique_id integer
-- !!! You have to list all columns in order here !!! --
);
我将目录查找与值查询分开,因为带有两个参数的crosstab()函数分别提供了列名.缺少的值(更改中没有条目)会自动替换为NULL.这个用例的完美搭配!
假设attname与column_name匹配.排除unique_id,它扮演着特殊的角色.
完全自动化
解决your comment:有一种方法可以自动提供列定义列表.不过,这不适合胆小的人.
我在这里使用了许多高级Postgres功能:crosstab(),带动态SQL的plpgsql函数,复合类型处理,高级美元引用,目录查找,聚合函数,窗口函数,对象标识符类型,…
测试环境:
CREATE TABLE instances (
unique_id int
, col1 text
, col2 text -- two columns are enough for the demo
);
INSERT INTO instances VALUES
(1, 'foo1', 'bar1')
, (2, 'foo2', 'bar2')
, (3, 'foo3', 'bar3')
, (4, 'foo4', 'bar4');
CREATE TABLE changes (
unique_id int
, table_name text
, column_name text
, value text
, updated_at timestamp
);
INSERT INTO changes VALUES
(1, 'instances', 'col1', 'foo11', '2012-04-12 00:01')
, (1, 'instances', 'col1', 'foo12', '2012-04-12 00:02')
, (1, 'instances', 'col1', 'foo1x', '2012-04-12 00:03')
, (1, 'instances', 'col2', 'bar11', '2012-04-12 00:11')
, (1, 'instances', 'col2', 'bar17', '2012-04-12 00:12')
, (1, 'instances', 'col2', 'bar1x', '2012-04-12 00:13')
, (2, 'instances', 'col1', 'foo2x', '2012-04-12 00:01')
, (2, 'instances', 'col2', 'bar2x', '2012-04-12 00:13')
-- NO change for col1 of row 3 - to test NULLs
, (3, 'instances', 'col2', 'bar3x', '2012-04-12 00:13');
-- NO changes at all for row 4 - to test NULLs
一个表的自动功能
CREATE OR REPLACE FUNCTION f_curr_instance(int, OUT t public.instances) AS
$func$
BEGIN
EXECUTE $f$
SELECT *
FROM crosstab($x$
SELECT DISTINCT ON (1,2)
unique_id, column_name, value
FROM changes
WHERE table_name = 'instances'
AND unique_id = $f$|| $1 || $f$
ORDER BY 1, 2, updated_at DESC;
$x$
, $y$
SELECT attname
FROM pg_catalog.pg_attribute
WHERE attrelid = 'public.instances'::regclass
AND attnum > 0
AND NOT attisdropped
AND attname <> 'unique_id'
ORDER BY attnum
$y$) AS tbl ($f$
|| (SELECT string_agg(attname || ' ' || atttypid::regtype::text
, ', ' ORDER BY attnum) -- must be in order
FROM pg_catalog.pg_attribute
WHERE attrelid = 'public.instances'::regclass
AND attnum > 0
AND NOT attisdropped)
|| ')'
INTO t;
END
$func$ LANGUAGE plpgsql;
表实例是硬编码的,模式限定为明确的.请注意使用表类型作为返回类型. PostgreSQL中的每个表都自动注册了一个行类型.这必须匹配crosstab()函数的返回类型.
这将函数绑定到表的类型:
>如果您尝试DROP表,您将收到一条错误消息
> ALTER TABLE后,您的函数将失败.你必须重新创建它(没有更改).我认为这是9.1中的一个错误. ALTER TABLE不应该以静默方式破坏该函数,但会引发错误.
这表现得非常好.
呼叫:
SELECT * FROM f_curr_instance(3);
unique_id | col1 | col2
----------+-------+-----
3 |<NULL> | bar3x
注意col1在这里是如何为NULL.
在查询中使用以显示具有其最新值的实例:
SELECT i.unique_id
, COALESCE(c.col1, i.col1)
, COALESCE(c.col2, i.col2)
FROM instances i
LEFT JOIN f_curr_instance(3) c USING (unique_id)
WHERE i.unique_id = 3;
任何表格的完全自动化
(2016年新增.这是炸药.)
需要Postgres 9.1或更高版本. (可以与pg 8.4一起使用,但我没有费心去做.)
CREATE OR REPLACE FUNCTION f_curr_instance(_id int, INOUT _t ANYELEMENT) AS
$func$
DECLARE
_type text := pg_typeof(_t);
BEGIN
EXECUTE
(
SELECT format
($f$
SELECT *
FROM crosstab(
$x$
SELECT DISTINCT ON (1,2)
unique_id, column_name, value
FROM changes
WHERE table_name = %1$L
AND unique_id = %2$s
ORDER BY 1, 2, updated_at DESC;
$x$
, $y$
SELECT attname
FROM pg_catalog.pg_attribute
WHERE attrelid = %1$L::regclass
AND attnum > 0
AND NOT attisdropped
AND attname <> 'unique_id'
ORDER BY attnum
$y$) AS ct (%3$s)
$f$
, _type, _id
, string_agg(attname || ' ' || atttypid::regtype::text
, ', ' ORDER BY attnum) -- must be in order
)
FROM pg_catalog.pg_attribute
WHERE attrelid = _type::regclass
AND attnum > 0
AND NOT attisdropped
)
INTO _t;
END
$func$ LANGUAGE plpgsql;
调用(提供表类型为NULL :: public.instances:
SELECT * FROM f_curr_instance(3, NULL::public.instances);
有关:
> Refactor a PL/pgSQL function to return the output of various SELECT queries
> How to set value of composite variable field using dynamic SQL
标签:crosstab,php,postgresql,dynamic-sql,plpgsql 来源: https://codeday.me/bug/20190927/1823448.html