web-dev-qa-db-fra.com

Boucle sur les tables avec PLpgSQL dans Postgresql 9.0+

Je veux parcourir toutes mes tables pour compter les lignes dans chacune d'elles. La requête suivante m'obtient une erreur:

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

Les erreurs:

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 Est le nom d'une table dans ma base de données. J'ai déjà essayé d'utiliser quote_ident() mais en vain.

17
Totor

Le curseur renvoie un enregistrement, pas une valeur scalaire, donc "nom_table" n'est pas une variable chaîne.

La concaténation transforme l'enregistrement en une chaîne qui ressemble à ceci (sql_features). Si vous aviez sélectionné par exemple le schéma avec le nom de la table, la représentation textuelle de l'enregistrement aurait été (public,sql_features).

Vous devez donc accéder à la colonne à l'intérieur de l'enregistrement pour créer votre instruction 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$$;

Vous voudrez peut-être utiliser WHERE schemaname = 'public' au lieu de not like 'pg_%' pour exclure les tables système Postgres.

15

Je ne me souviens pas de la dernière fois où j'avais réellement besoin d'utiliser un curseur explicite pour faire une boucle dans plpgsql.
Utilisez le curseur implicite d'une boucle FOR , c'est beaucoup plus propre:

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

Vous devez inclure le nom du schéma pour que cela fonctionne pour tous les schémas (y compris ceux qui ne figurent pas dans votre search_path).

De plus, vous avez réellement besoin d'utiliser quote_ident() ou format() avec %I Pour se protéger contre l'injection SQL. Un nom de table peut être presque n'importe quoi entre guillemets doubles.

Petit détail: échapper le trait de soulignement (_) Dans le modèle LIKE pour en faire un littéral trait de soulignement: tablename NOT LIKE 'pg\_%'

Comment je pourrais le faire:

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
$$;
  • Requête pg_catalog.pg_class au lieu de tablename, elle fournit le OID de la table.

  • Le type d'identifiant d'objet regclass est pratique pour simplifier, en particulier, les noms de table sont entre guillemets et qualifiés de schéma si nécessaire automatiquement (empêche également injection SQL ).

  • Cette requête exclut également les tables temporaires (le schéma temporaire est nommé avec pg_temp% En interne).

  • Si vous ne souhaitez que des tables d'un schéma donné:

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