web-dev-qa-db-fra.com

Comment utiliser une variable de type d'enregistrement dans plpgsql?

Comment puis-je utiliser le résultat de la requête stocké dans une variable de type d'enregistrement pour une autre requête dans la même fonction stockée? J'utilise Postgres 9.4.4.

Avec une table comme celle-ci:

create table test (id int, tags text[]);
insert into test values (1,'{a,b,c}'),
                        (2,'{c,d,e}');

J'ai écrit une fonction (simplifiée) comme ci-dessous:

CREATE OR REPLACE FUNCTION func(_tbl regclass)
RETURNS TABLE (t TEXT[], e TEXT[])
LANGUAGE plpgsql AS $$
DECLARE
  t RECORD;
  c INT;
BEGIN
  EXECUTE format('SELECT id, tags FROM %s', _tbl) INTO t;
  SELECT count(*) FROM t INTO c;
  RAISE NOTICE '% results', c;
  SELECT * FROM t;
END
$$;

... mais n'a pas fonctionné:

select func('test');
ERROR:  42P01: relation "t" does not exist
LINE 1: SELECT count(*) FROM t
                             ^
QUERY:  SELECT count(*) FROM t
CONTEXT:  PL/pgSQL function func(regclass) line 7 at SQL statement
LOCATION:  parserOpenTable, parse_relation.c:986
14
SG. Nihonbashi

Le principal malentendu: une variable record contient une ligne single (ou est NULL), pas une table (0-n lignes d'un type bien connu). Il n'y a pas de "variables de table" dans Postgres ou PL/pgSQL . Selon la tâche, il existe différentes alternatives:

Par conséquent, vous ne pouvez pas affecter multiple des lignes à une variable de type record. Dans cette déclaration:

EXECUTE format('SELECT id, tags FROM %s', _tbl) INTO t;

... Postgres assigne seulement la première ligne et rejette le reste. Puisque "le premier" n'est pas bien défini dans votre requête, vous vous retrouvez avec un choix arbitraire. Evidemment à cause du malentendu évoqué au départ.

Une variable record ne peut pas non plus être utilisée à la place des tables dans les requêtes SQL. C'est la principale cause de l'erreur que vous obtenez:

la relation "t" n'existe pas

Il devrait être clair maintenant que count(*) n'aurait aucun sens pour commencer, puisque t n'est qu'un seul enregistrement/ligne - en plus d'être impossible de toute façon.

Enfin (même si le reste fonctionnerait), votre type de retour semble incorrect: (t TEXT[], e TEXT[]). Puisque vous sélectionnez id, tags Dans t, vous souhaitez renvoyer quelque chose comme (id int, e TEXT[]).

Ce que vous essayez de faire fonctionnerait comme ceci :

CREATE OR REPLACE FUNCTION func(_tbl regclass)
  RETURNS TABLE (id int, e text[]) AS
$func$
DECLARE
   _ct int;
BEGIN
   EXECUTE format(
      'CREATE TEMP TABLE tmp ON COMMIT DROP AS
       SELECT id, tags FROM %s'
    , _tbl);

   GET DIAGNOSTICS _ct = ROW_COUNT; -- cheaper than another count(*)

   -- ANALYZE tmp;  -- if you are going to run multiple queries

   RAISE NOTICE '% results', _ct;

   RETURN QUERY TABLE tmp;
END
$func$ LANGUAGE plpgsql;

Appelez (notez la syntaxe!) :

SELECT * FROM func('test');

En relation:

Juste une preuve de concept. Pendant que vous sélectionnez la table entière, vous utiliserez simplement la table sous-jacente à la place. En réalité, vous aurez une clause WHERE dans la requête ...

Attention à la non-concordance du type qui se cache, count() renvoie bigint, vous ne pouvez pas l'assigner à une variable integer. Aurait besoin d'un transtypage: count(*)::int.

Mais j'ai complètement remplacé ça, c'est moins cher de faire ça juste après EXECUTE:

GET DIAGNOSTICS _ct = ROW_COUNT; 

Détails dans le manuel.

Pourquoi ANALYZE?


En plus: les CTE en SQL simple peuvent souvent faire le travail:

17
Erwin Brandstetter