Loop on tables with PL/pgSQL in Postgres 9.0+

时间:2021-01-31 22:58:32

I want to loop through all my tables to count rows in each of them. The following query gets me an error:

我想遍历所有表来计算每个表中的行数。以下查询给我一个错误:

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$$;

Errors:

错误:

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 is a table's name in my DB. I already tried to use quote_ident() but to no avail.

sql_features是我的数据库中的表名。我已经尝试使用quote_ident()但无济于事。

2 个解决方案

#1


10  

The cursor returns a record, not a scalar value, so "tablename" is not a string variable.

游标返回记录而不是标量值,因此“tablename”不是字符串变量。

The concatenation turns the record into a string that looks like this (sql_features). If you had selected e.g. the schemaname with the tablename, the text representation of the record would have been (public,sql_features).

连接将记录转换为一个看起来像这样的字符串(sql_features)。如果您选择了例如带有tablename的schemaname,记录的文本表示形式(public,sql_features)。

So you need to access the column inside the record to create your SQL statement:

因此,您需要访问记录中的列以创建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$$;

You might want to use WHERE schemaname = 'public' instead of not like 'pg_%' to exclude the Postgres system tables.

您可能希望使用WHERE schemaname ='public'而不是像'pg_%'来排除Postgres系统表。

#2


19  

I can't remember the last time I actually needed to use an explicit cursor for looping in plpgsql.
Use the implicit cursor of a FOR loop, that's much cleaner:

我不记得上次我真的需要在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$$;

You need to include the schema name to make this work for all schemas (including those not in your search_path).

您需要包含模式名称,以使其适用于所有模式(包括那些不在您的search_path中的模式)。

Also, you actually need to use quote_ident() or format() with %I to safeguard against SQL injection. A table name can be almost anything inside double quotes.

此外,您实际上需要使用带有%I的quote_ident()或format()来防止SQL注入。表名几乎可以是双引号内的任何内容。

How I would do it

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  relkind = 'r'
      AND    n.nspname !~~ 'pg_%'
      ORDER  BY n.nspname, c.relname
    LOOP
      EXECUTE 'SELECT count(*) FROM ' || tbl INTO nbrow;
      -- raise notice '%', nbrow;
   END LOOP;
END
$$;
  • Query pg_catalog.pg_class instead of tablename, it provides the OID of the table.

    查询pg_catalog.pg_class而不是tablename,它提供表的OID。

  • Use the object identifier type regclass, makes everything simpler, in particular, SQL injection is avoided automatically.

    使用对象标识符类型regclass,使一切变得更简单,特别是自动避免SQL注入。

  • If you want only tables from a given schema:

    如果只需要来自给定模式的表:

    AND    n.nspname = 'public'
    

#1


10  

The cursor returns a record, not a scalar value, so "tablename" is not a string variable.

游标返回记录而不是标量值,因此“tablename”不是字符串变量。

The concatenation turns the record into a string that looks like this (sql_features). If you had selected e.g. the schemaname with the tablename, the text representation of the record would have been (public,sql_features).

连接将记录转换为一个看起来像这样的字符串(sql_features)。如果您选择了例如带有tablename的schemaname,记录的文本表示形式(public,sql_features)。

So you need to access the column inside the record to create your SQL statement:

因此,您需要访问记录中的列以创建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$$;

You might want to use WHERE schemaname = 'public' instead of not like 'pg_%' to exclude the Postgres system tables.

您可能希望使用WHERE schemaname ='public'而不是像'pg_%'来排除Postgres系统表。

#2


19  

I can't remember the last time I actually needed to use an explicit cursor for looping in plpgsql.
Use the implicit cursor of a FOR loop, that's much cleaner:

我不记得上次我真的需要在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$$;

You need to include the schema name to make this work for all schemas (including those not in your search_path).

您需要包含模式名称,以使其适用于所有模式(包括那些不在您的search_path中的模式)。

Also, you actually need to use quote_ident() or format() with %I to safeguard against SQL injection. A table name can be almost anything inside double quotes.

此外,您实际上需要使用带有%I的quote_ident()或format()来防止SQL注入。表名几乎可以是双引号内的任何内容。

How I would do it

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  relkind = 'r'
      AND    n.nspname !~~ 'pg_%'
      ORDER  BY n.nspname, c.relname
    LOOP
      EXECUTE 'SELECT count(*) FROM ' || tbl INTO nbrow;
      -- raise notice '%', nbrow;
   END LOOP;
END
$$;
  • Query pg_catalog.pg_class instead of tablename, it provides the OID of the table.

    查询pg_catalog.pg_class而不是tablename,它提供表的OID。

  • Use the object identifier type regclass, makes everything simpler, in particular, SQL injection is avoided automatically.

    使用对象标识符类型regclass,使一切变得更简单,特别是自动避免SQL注入。

  • If you want only tables from a given schema:

    如果只需要来自给定模式的表:

    AND    n.nspname = 'public'