web-dev-qa-db-ja.com

pl / pgsql関数によって実行されたDMLステートメントをログに記録する方法は?

いくつかのフィールドをリストし、動的に構築されたUPDATEコマンドを使用してそれらの内容をクリアするpl/pgsql関数(以下を参照)があります。

_log_statement = 'mod'_を設定すると、SELECT fnct_clear_temp_fields();を使用して関数を実行しても、ログに何も表示されません。 _log_statement = 'all'_を設定して関数を実行すると、ログにSELECT fnct_clear_temp_fields();が表示されますが、基になるUPDATEコマンドは表示されません。

UPDATEコマンドもログに表示する方法はありますか?

参考までに、ここに関数があります:

_CREATE OR REPLACE FUNCTION fnct_clear_temp_fields() RETURNS VOID AS $$
DECLARE
    --Put into a cursor a view dynamically listing all user-defined fields beginning with 'temp_'
    dataset_1 CURSOR FOR 
        SELECT 
            column_name,
            table_name
        FROM information_schema.tables 
        NATURAL JOIN information_schema.columns 
        WHERE 
            table_schema='public'
            AND table_type='BASE TABLE'
            AND column_name ~ '^temp_'
        ORDER BY table_name,column_name;

    --Record variable to go through each line of the view above
    dataset_1_row RECORD;

BEGIN
    OPEN dataset_1; --Open the cursor
    FETCH dataset_1 INTO dataset_1_row; --first row of the view

    WHILE FOUND LOOP
        RAISE NOTICE 'Table: %, Column: %',  dataset_1_row.table_name,dataset_1_row.column_name;

        --Set to NULL the contents of the current 'temp_' column
        EXECUTE 'UPDATE '||dataset_1_row.table_name||' SET '||dataset_1_row.column_name||'=NULL WHERE '||dataset_1_row.column_name||' IS NOT NULL';

        FETCH dataset_1 INTO dataset_1_row; --next row please.
    END LOOP; --while end

    CLOSE dataset_1;

    RETURN;
END;
$$ LANGUAGE plpgsql;
_
5

だから、実際の答えとしての私の提案:

この関数でのみ必要な場合は、RAISE LOG '%', your_statement;、または実際のコード:

...
DECLARE
    exec_str text;
...
    --Set to NULL the contents of the current 'temp_' column
    exec_str := 'UPDATE '||dataset_1_row.table_name||
                'SET '||dataset_1_row.column_name||'=NULL 
                 WHERE '||dataset_1_row.column_name||' IS NOT NULL';
    RAISE LOG 'Query executed: %', exec_str;
    EXECUTE exec_str;
...

また、私は見つけます

FOR dataset_1_row IN SELECT ... 
LOOP 
END LOOP;

よりスムーズに構築します。

2
dezso

質問する

そこにisplpgsql関数内のすべてのステートメントを記録する組み込みの方法があります:_auto-explain_

_LOAD 'auto_explain';
SET auto_explain.log_min_duration = 1; -- exclude very fast trivial queries
SET auto_explain.log_nested_statements = ON; -- statements inside functions
_

この密接に関連した質問の詳細:
pgpsqlで記述されたUDF呼び出しのPostgresクエリプラン

潜在的に大量のログ出力を生成します。本番環境ではなく、デバッグにのみ使用します。
1つのステートメントだけを記録する必要がある場合は、 @ dezsoのアドバイス を使用してください。

コード監査

この書き換えられた関数を考えてみましょう:

_CREATE OR REPLACE FUNCTION fnct_clear_temp_fields()
  RETURNS void AS
$func$
DECLARE
   rec record;
   qry text;
BEGIN
   FOR rec IN
      SELECT quote_ident(c.relname) AS tbl, quote_ident(a.attname) AS col
      FROM   pg_namespace n
      JOIN   pg_class     c ON c.relnamespace = n.oid
      JOIN   pg_attribute a ON a.attrelid = c.oid
      WHERE  n.nspname = 'public'
      AND    c.relkind = 'r'
      AND    a.attname LIKE 'temp_%'  -- LIKE is faster than ~
      AND    a.attnum > 0
      AND    NOT a.attisdropped
      ORDER  BY 1,2
   LOOP
      RAISE NOTICE 'Table: %, Column: %', rec.tbl, rec.col;
      qry := format('UPDATE %1$s SET %2$s = NULL WHERE %2$s IS NOT NULL', rec.tbl, rec.col);
      RAISE LOG 'Query: %', qry;
      EXECUTE qry;
   END LOOP;
END
$func$  LANGUAGE plpgsql;
_

主なポイント

  • 動的SQLに構築するすべての識別子をサニタイズするmustさもなければ、非標準で失敗する可能性があります二重引用符が必要な名前。さらに悪いことに、SQLインジェクションを受け入れる可能性があります。
    デモンストレーション quote_ident() 、サニタイズされた識別子を複数回使用しているため。 regclassまたは format() にはさらにオプションがあります:
    PostgreSQL関数パラメーターとしてのテーブル名

  • 情報スキーマの非常に遅いビューではなく、システムカタログに基づいてこのようなクエリを行うことを好みます。ただし、それは要件と好みの問題です。同等のものを示しており、これは約10倍高速です(UPDATEコマンドは関係ありません)。もっと:
    特定のスキーマにテーブルが存在するかどうかを確認する方法

  • LIKEは通常、より強力な正規表現マッチング(_~_)よりも高速です。 LIKEがその仕事をできるなら、それを使ってください。

  • 他のいくつかのマイナーな簡略化。

  • 詳細と関連する回答:
    テーブル名がパラメータであるカーソルの更新レコード

7

素晴らしいdezso、それはうまくいきます!これが私の関数の最終バージョンです:

CREATE OR REPLACE FUNCTION fnct_clear_temp_fields() RETURNS VOID AS $$
DECLARE
    dataset_1_row RECORD; --Record variable to go through each row of the view below
    update_query TEXT; --The dynamic UPDATE query to be executed

BEGIN
    FOR dataset_1_row IN --Cycle through rows of query below
            SELECT 
                column_name,
                table_name
            FROM information_schema.tables 
            NATURAL JOIN information_schema.columns 
            WHERE 
                table_schema='public'
                AND table_type='BASE TABLE'
                AND column_name ~ '^temp_'
            ORDER BY table_name,column_name
        LOOP

        RAISE NOTICE 'Table: %, Column: %',  dataset_1_row.table_name,dataset_1_row.column_name;
        --Create a dynamic update query to set to NULL the contents of the current 'temp_' column
        update_query :='UPDATE '||dataset_1_row.table_name||' SET '||dataset_1_row.column_name||'=NULL WHERE '||dataset_1_row.column_name||' IS NOT NULL;';
        RAISE LOG 'Query executed: %', update_query; --Put query def in log
        EXECUTE update_query; --Run the query
    END LOOP; --Next line of SELECT query above 

    RETURN;
END;
$$ LANGUAGE plpgsql;
1