Цикл по таблицам с 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(), но безрезультатно.


person Totor    schedule 07.03.2013    source источник


Ответы (2)


Курсор возвращает запись, а не скалярное значение, поэтому "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.

person a_horse_with_no_name    schedule 07.03.2013

Я не могу вспомнить, когда в последний раз мне действительно нужно было использовать явный курсор для зацикливания в 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 или regclass для защиты от 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 удобен для упрощения. В частности, имена таблиц заключаются в двойные кавычки и при необходимости автоматически уточняются схемой (также предотвращает SQL-инъекция).

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

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

    AND    n.nspname = 'public' -- schema name here, case-sensitive
person Erwin Brandstetter    schedule 07.03.2013
comment
Видимо, в Postgre 9.6 NOT LIKE 'sql_%' тоже (желательно) нужен в 1-м примере. Во 2-м примере необходимо n.nspname != 'information_schema'. Еще одно отличие заключается в том, что второй игнорирует временные таблицы (из-за условия pg_%). - person Masa Sakano; 04.09.2018
comment
@MasaSakano: Все хорошие моменты, я обновил соответственно (с некоторыми улучшениями). - person Erwin Brandstetter; 04.09.2018