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 oftablename
, 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 oftablename
, 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'