sql >> データベース >  >> RDS >> PostgreSQL

テーブルと変更ログをPostgreSQLのビューにマージします

    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カタログをクエリします。これは、より高速だからです。 ::regclassへの特別なキャストに注意してください 。

    さて、それはあなたにテーブルを与えます 。 1つのunique_idのすべての値が必要です
    これを実現するには、基本的に3つのオプションがあります。

    1. 列ごとに1つの副選択(または結合)。高価で扱いにくい。ただし、有効なオプションは数列のみです。

    2. 大きなCASE ステートメント。

    3. ピボット機能 。 PostgreSQLはcrosstab()を提供します 追加モジュールtablefuncの関数 そのためです。
      基本的な手順:

      • PostgreSQLクロス集計クエリ

    crosstab()を使用した基本的なピボットテーブル

    関数を完全に書き直しました:

    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()として、カタログルックアップを値クエリから分離しました 2つのパラメーターを持つ関数は、列名を個別に提供します。欠落している値(変更にエントリがない)は、NULLに置き換えられます 自動的。 このユースケースに完全に一致します!

    attnameと仮定します column_nameと一致します 。 unique_idを除く 、特別な役割を果たします。

    完全自動化

    コメントへの対応:方法があります 列定義リストを自動的に提供します。しかし、それは気弱な人のためではありません。

    ここでは、いくつかの高度な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
    

    1つのテーブルの自動化された機能

    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;
    

    テーブルinstances はハードコーディングされており、明確であるようにスキーマが認定されています。リターンタイプとしてテーブルタイプを使用していることに注意してください。 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が必要です またはそれ以降。 (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);
    

    関連:

    • PL / pgSQL関数をリファクタリングして、さまざまなSELECTクエリの出力を返します
    • 動的SQLを使用して複合変数フィールドの値を設定する方法



    1. PythonのSSHトンネリングを介したPostgreSQLデータベースへの接続

    2. 数値としてのSQL順序文字列

    3. PHPを使用したデータベースへの接続

    4. Oracleクエリを定期的に実行するための最良の方法