web-dev-qa-db-ja.com

Postgresql 9.0+のPLpgSQLでテーブルをループする

すべてのテーブルをループして、各テーブルの行をカウントします。次のクエリでエラーが発生します。

_DO $$
DECLARE
    tables CURSOR FOR
        SELECT tablename FROM pg_tables
        WHERE tablename NOT LIKE 'pg_%'
        ORDER BY tablename;
    tablename varchar(100);
    nbRow int;
BEGIN
    FOR tablename IN tables LOOP
        EXECUTE 'SELECT count(*) FROM ' || tablename INTO nbRow;
        -- Do something with nbRow
    END LOOP;
END$$;
_

エラー:

_ERROR:  syntax error at or near ")"
LINE 1: SELECT count(*) FROM (sql_features)
                                          ^
QUERY:  SELECT count(*) FROM (sql_features)
CONTEXT:  PL/pgSQL function inline_code_block line 8 at EXECUTE statement
_

_sql_features_は、私のDB内のテーブルの名前です。私はすでにquote_ident()を使用しようとしましたが、役に立ちませんでした。

17
Totor

カーソルはスカラー値ではなくレコードを返すため、「tablename」は文字列変数ではありません。

連結により、レコードはこの(sql_features)のような文字列に変換されます。あなたが選択した場合スキーマ名とテーブル名、レコードのテキスト表現は(public,sql_features)になります。

したがって、SQLステートメントを作成するには、レコード内の列にアクセスする必要があります。

DO $$
DECLARE
    tables CURSOR FOR
        SELECT tablename
        FROM pg_tables
        WHERE tablename NOT LIKE 'pg_%'
        ORDER BY tablename;
    nbRow int;
BEGIN
    FOR table_record IN tables LOOP
        EXECUTE 'SELECT count(*) FROM ' || table_record.tablename INTO nbRow;
        -- Do something with nbRow
    END LOOP;
END$$;

WHERE schemaname = 'public'の代わりにnot like 'pg_%'を使用して、Postgresシステムテーブルを除外することができます。

15

Plpgsqlでループするために明示カーソルを実際に使用する必要があった最後の時間を思い出せません。
FOR loop の暗黙カーソルを使用します。

DO
$$
DECLARE
    rec   record;
    nbrow bigint;
BEGIN
   FOR rec IN
      SELECT *
      FROM   pg_tables
      WHERE  tablename NOT LIKE 'pg\_%'
      ORDER  BY tablename
   LOOP
      EXECUTE 'SELECT count(*) FROM '
        || quote_ident(rec.schemaname) || '.'
        || quote_ident(rec.tablename)
      INTO nbrow;
      -- Do something with nbrow
   END LOOP;
END
$$;

すべてのスキーマ(search_pathにないスキーマを含む)でこの機能を使用するには、スキーマ名を含める必要があります。

また、実際にneedを使用して quote_ident()またはformat() とともに%Iを使用して、SQLインジェクションから保護します。テーブル名は、二重引用符で囲んだほとんど何でもできます。

細部:アンダースコア(_)をLIKEパターンでエスケープして、literalアンダースコアにします:tablename NOT LIKE 'pg\_%'

どうすればいいですか:

DO
$$
DECLARE
    tbl   regclass;
    nbrow bigint;
BEGIN
   FOR tbl IN
      SELECT c.oid
      FROM   pg_class     c
      JOIN   pg_namespace n ON n.oid = c.relnamespace
      WHERE  c.relkind = 'r'
      AND    n.nspname NOT LIKE 'pg\_%'         -- system schema(s)
      AND    n.nspname <> 'information_schema'  -- information schema
      ORDER  BY n.nspname, c.relname
   LOOP
      EXECUTE 'SELECT count(*) FROM ' || tbl INTO nbrow;
      -- raise notice '%: % rows', tbl, nbrow;
   END LOOP;
END
$$;
  • クエリ pg_catalog.pg_classtablenameの代わりに、テーブルのOIDを提供します。

  • オブジェクト識別子タイプregclass は、簡単にするために便利です。特に、必要に応じてテーブル名を二重引用符で囲み、スキーマで修飾します( SQLインジェクション も防止します) )。

  • このクエリは、一時テーブルも除外します(一時スキーマは、pg_temp%で内部的に命名されます)。

  • 特定のスキーマのテーブルのみが必要な場合:

    AND    n.nspname = 'public' -- schema name here, case-sensitive
    
26