Postgresql

動態查找兩個表之間不匹配的行

  • December 4, 2018

我這裡有一個函式,它應該將兩個表作為參數並檢查它們是否相同。

create or replace function testing.equal_tables(
   varchar,
   varchar)
   returns void as
$$
begin

   execute 'select *
    from
    (select * from ' || $1 ||'
    except
    select * from ' || $2 || ') a
    union
    (select * from ' || $2 || '
    except
    select * from ' || $1 || ');'
   ;

end;
$$ language plpgsql;

當我用這兩張表呼叫它時,一張有 20 行,一張有 10 行,我只得到空集,這不是正確的結果:

select testing.equal_tables('ee1', 'ee2');

當我修改函式以返回語句的字元串時,它正確返回,但這仍然無濟於事,因為我想使用函式、準備好的語句或其他東西來執行返回字元串。有什麼辦法可以使這個功能起作用嗎?

我從您的範例中得出,您只想匹配具有相同結構(兼容的行類型)的表。

基本查詢

首先,您的基本查詢不必要地複雜並且可能不正確。改為考慮:

(TABLE a EXCEPT ALL TABLE b)  -- ALL keeps duplicate rows and is faster
UNION ALL
(TABLE b EXCEPT ALL TABLE a);

我懷疑你想折疊重複。通常情況下,表一開始就不包含完整的重複項,省略ALL關鍵字會指示 Postgres 在每個步驟中嘗試折疊重複項,這只是浪費時間。細節:

即使完全重複的行是可能的,最好將它們全部返回以避免誤導性答案***。***如果你想在結果中折疊重複,一個單一UNION的工作:

(TABLE a EXCEPT ALL TABLE b)
UNION
(TABLE b EXCEPT ALL TABLE a);

但是,查詢NATURAL FULL OUTER JOIN效率更高,幾乎相同:

SELECT *
FROM   a NATURAL FULL OUTER JOIN b
WHERE  a IS NULL OR b IS NULL;

有兩個微妙的、奇異的極端案例:

  1. 這將返回所有以 NULL 開頭的行。不過,這將是一個奇怪的表格設計,允許這樣的行。
  2. 這不會返回與另一個表中相同數量的重複行不匹配的重複行:單個匹配足以消除所有 - 這與結果中的折疊重複略有不同!看看下面的小提琴。同樣,這將是一個奇怪的表格設計,允許完全重複的行。

功能

到目前為止,您所擁有的內容因多種原因而不起作用。

要動態返回實際的表行(而不僅僅是計數或文本表示),您需要使用多態類型。

由於第二個表必然具有兼容的行類型(根據我的假設),因此只需為此輸入表名就足夠了。

CREATE OR REPLACE FUNCTION f_tbl_diff(_tbl1_type ANYELEMENT, _tbl2 text)
 RETURNS SETOF ANYELEMENT AS
$func$
BEGIN
  RETURN QUERY EXECUTE format('
     SELECT *
     FROM   %1$s NATURAL FULL OUTER JOIN %2$I
     WHERE  %1$s IS NULL OR %2$I IS NULL'
   , pg_typeof(_tbl1_type), _tbl2);
END
$func$  LANGUAGE plpgsql;

稱呼:

SELECT * FROM f_tbl_diff(NULL::a, 'b');

注意第一個參數的特殊語法!我們送出定義返回類型的實際行,而不僅僅是表名。關於 SO 的相關答案有很多細節(滾動到最後一章):

這將返回另一個表中沒有完全匹配的所有行

SQL Fiddle還展示了奇異的極端情況。

關於動態 SQL:

引用自:https://dba.stackexchange.com/questions/120644