Цикл на таблицах с PL / pgSQL в Postgres 9.0+

Я хочу перебрать все мои таблицы, чтобы подсчитать строки в каждом из них. Следующий запрос возвращает мне ошибку:

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 - имя таблицы в моей БД. Я уже пытался использовать quote_ident() но безрезультатно.

2 ответов


курсор возвращает запись, а не скалярное значение, поэтому "tablename" не является строковой переменной.

конкатенация превращает запись в строку, которая выглядит следующим образом (sql_features). Если бы вы выбрали, например, schemaname с именем таблицы, текстовое представление записи было бы (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.


я не могу вспомнить последний раз, когда мне действительно нужно было использовать явный курсор для цикла в plpgsql.
Используйте неявный курсор FOR цикл, это намного чище:

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).

кроме того, вы на самом деле нужно использовать quote_ident() или format() С %I для защиты от SQL-инъекций. Имя таблицы может быть почти все внутри двойных кавычек.

незначительные детали: избежать подчеркивания (_) в LIKE шаблон, чтобы сделать его литерал подчеркивание: 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_class вместо tablename, он обеспечивает OID таблицы.

  • на объект с идентификатором типа regclass is удобно для упрощения, в частности, имена таблиц имеют двойные кавычки и схему, где это необходимо автоматически (также предотвращает SQL-инъекций).

  • этот запрос также исключает временные таблицы (временная схема называется с pg_temp% внутренне).

  • если вы хотите только таблицы из данной схемы:

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