查找跨表几乎相同行的更改列

GT.*_*GT. 4 postgresql dynamic-sql

我在 64 位 Windows 7 上运行 PostgreSQL 9.3.5。

我的数据每季度到达,在多个表 ( table1, ..., tableN) 中,这些表在周期内通过基于关键标识符的跨表约束进行链接。在其他列中,每个表都具有随时间持续存在的标识符pfi-持久特征标识符ufi-通用特征标识符

pfi每个表都是唯一的(非常罕见的是table1.pfi = table2.pfi.
ufi在所有表和所有时间都是唯一的。它不是行数据的散列,但您可以这样认为。

每个时期,在每个表中,一些新的pfi产生,一些旧pfi的退役。一些pfi改变属性。ufi跟踪给定(行)的任何属性的任何更改pfi,因此为它获取更改的(和新的)行table1只是一个问题:

-- 1st query
select a.*
into vm201512.property_d
from vm201512.property a
where not exists (select 1 from vm201412.property where ufi = a.ufi);
Run Code Online (Sandbox Code Playgroud)

这将选择pfi在至少一列中是新的 (new ) 或更改的所有行。

每个表的大约 96% 在各个方面都保持不变。因此,在分析跨周期变化时,我构建了一个仅包含更改数据和新数据的表。这将表大小从 ~3.5m 行减少到 ~225k 行:如果您随后对相对复杂的多边形和多个(空间和非空间)JOINs进行空间比较,那么这将大大减少。

property表的列相对较少,因此我可以确定数据的哪些元素发生了变化,如下所示:

-- 2nd query
create table vm201512.property_d_changes as 
select pfi, 
   case when a.view_pfi=b.view_pfi then 0::int else 1::INT end as view_pfi,
   case when a.status=b.status then 0::int else 1::INT end as status,
   case when a.property_type=b.property_type then 0::int else 1::INT end as property_type,
   -- ... more columns
from vm201512.property_d a -- table created with first query
join vm201412.property b using (pfi);
Run Code Online (Sandbox Code Playgroud)

这给了我一个很好的表格,我可以在其中准确确定更改的(不是新的)行发生了什么变化。我可以弄清楚pfi123456 对其propnum及其status; 进行了更改。我可以弄清楚有多少pfi人对他们的view_pfi- 那种事情发生了变化。

其他几个表有 >50 列,这使得 case 语句变得笨拙(我意识到它只需要编码一次,但如果数据结构发生变化怎么办?)

有两个不同表new.table1, old.table1中的两行,其中new.table1.pfi = old.table1.pfi一列或多列不同,是否有简洁、优雅的 PostgreSQL 语句来找出更改的列?还是我被困住了CASE

我意识到我可以编写一个动态函数来遍历给定表的所有列,并使用CASE语句构建查询。

Erw*_*ter 5

澄清

您的评论需要首先解决:

数字数据几乎总是取 0(文本类型取 '')

这里的关键词是“几乎”。只要它不是“从不”(例如“从不!”),无论如何您都需要考虑 NULL。

没有测试风险NULL=NULL,这会不恰当地返回 1

不,不会。任何与 NULL 相比的东西总是 NULL 甚至NULL=NULL。尝试一下。您需要了解 NULL 比较。

我想我只需要更改sum(col1)sum(col1::int)即可获取更改的行数col1

如果您想计算 的每个案例a.col1 IS DISTINCT FROM b.col1,那么您需要首先使用NULL 安全比较。除此之外,你的表达会起作用。有多种选择,视情况而定:

select a.* into vm201512 ...在第一个查询中使用。别。SELECT INTO ..气馁。CREATE TABLE AS ...在您的第二个查询中使用上级喜欢。

此外,Postgres 在tablefunc 模块中提供了枢轴功能,但这根本不是“枢轴”问题。这里没有任何旋转。

核心问题是由于不同的输入表而导致查询的动态特性。

解决方案

假设没有 NULL 值。如果可以使用 NULL 值,请使用IS NOT DISTINCT FROM代替=
在 Postgres 9.5 中测试。应该适用于 Postgres 9.1 或更高版本。

您可以像这样构建查询:

CREATE OR REPLACE FUNCTION f_build_query(_t1 regclass
                                       , _t2 regclass
                                       , _join_col text = 'pfi')
  RETURNS text AS
$func$
SELECT format('SELECT %I, %s FROM %s a JOIN %s b USING (%1$I);'
            , _join_col
            , string_agg(format ('a.%1$I = b.%1$I AS %1$I', attname), ', ' ORDER BY attnum)
            , _t1, _t2)
FROM   pg_attribute
WHERE  attrelid = _t1        -- compare all columns from 1st table
AND    NOT attisdropped      -- no dropped (dead) columns
AND    attnum > 0            -- no system columns
AND    attname <> _join_col  -- exclude "pfi"
$func$  LANGUAGE sql;
Run Code Online (Sandbox Code Playgroud)

称呼:

SELECT f_build_query('vm201512.property_d', 'vm201412.property');
Run Code Online (Sandbox Code Playgroud)

返回这样的查询(您可以依次执行):

SELECT pfi, a.a = b.a AS a, a."weird NaMe" = b."weird NaMe" AS "weird NaMe" -- more ...
FROM vm201512.property_d a JOIN vm201412.property b USING (pfi);
Run Code Online (Sandbox Code Playgroud)

结果:

 pfi | a | b | weird NaMe
-----+---+---+------------
   1 | t | f | t
   2 | f | t | f
Run Code Online (Sandbox Code Playgroud)

适用于任意输入表,并安全地处理标识符。您可以根据需要提供符合或不符合架构的表名。

简单的动态解决方案

困难在于返回不同的行类型。SQL 要求在调用时知道返回类型。为避免困难,您可以改为返回一个简单的数组。您按列的原始顺序获得值,但不会像第一个查询那样获得列名:

CREATE OR REPLACE FUNCTION f_diff_matrix(_t1 regclass
                                       , _t2 regclass
                                       , _join_col text = 'pfi')
  RETURNS TABLE (pfi int, change_matrix bool[]) AS   -- Adapt data type of pfi to your needs!
$func$
BEGIN
   RETURN QUERY EXECUTE (
   SELECT format('SELECT %I, ARRAY[%s] FROM %s a JOIN %s b USING (%1$I)'
               , _join_col
               , string_agg(format ('a.%1$I = b.%1$I', attname), ', ' ORDER BY attnum)
               , _t1, _t2)
   FROM   pg_attribute
   WHERE  attrelid = _t1        -- compare all columns from 1st table
   AND    NOT attisdropped      -- no dropped (dead) columns
   AND    attnum > 0            -- no system columns
   AND    attname <> _join_col  -- exclude "pfi"
   );
END
$func$  LANGUAGE plpgsql;
Run Code Online (Sandbox Code Playgroud)

调用(注意区别!):

SELECT * FROM f_diff_matrix('vm201512.property_d', 'vm201412.property');
Run Code Online (Sandbox Code Playgroud)

结果:

 pfi | change_matrix
-----+---------------
   1 | {t,f,t}  -- one element per column
   2 | {f,t,f}
Run Code Online (Sandbox Code Playgroud)

SQL小提琴。


可能甚至做出相同的函数返回的各种表动态的结果集,但我怀疑它是值得的并发症:

如果您确实需要动态枢轴功能(不是在这种情况下):