PostgreSQL
 sql >> Base de données >  >> RDS >> PostgreSQL

Comment définir la valeur du champ variable composite à l'aide de SQL dynamique

Plus rapide avec hstore

Depuis Postgres 9.0 , avec le module supplémentaire hstore installé dans votre base de données il existe une solution très simple et rapide avec le #= opérateur qui ...

remplacer[s] les champs dans record avec des valeurs correspondantes de hstore .

Pour installer le module :

CREATE EXTENSION hstore;

Exemples :

SELECT my_record #= '"field"=>"value"'::hstore;  -- with string literal
SELECT my_record #= hstore(field, value);        -- with values

Les valeurs doivent être converties en text et retour, évidemment.

Exemple de fonctions plpgsql avec plus de détails :

  • Boucle sans fin dans la fonction de déclenchement
  • Attribuer à NEW par clé dans un déclencheur Postgres

Fonctionne désormais avec json / jsonb , aussi !

Il existe des solutions similaires avec json (pg 9.3+) ou jsonb (page 9.4+)

SELECT json_populate_record (my_record, json_build_object('key', 'new-value');

La fonctionnalité n'était pas documentée, mais elle est officielle depuis Postgres 13. Le manuel :

Cependant, si base n'est pas NULL, les valeurs qu'il contient seront utilisées pour les colonnes sans correspondance.

Vous pouvez donc prendre n'importe quelle ligne existante et remplir des champs arbitraires (en écrasant ce qu'elle contient).

Principaux avantages de json contre hstore :

  • fonctionne avec Postgres standard, vous n'avez donc pas besoin d'un module supplémentaire.
  • fonctionne également pour les tableaux imbriqués et les types composites.

Inconvénient mineur :un peu plus lent.

Voir la réponse ajoutée de @ Geir pour plus de détails.

Sans hstore et json

Si vous êtes sur une ancienne version ou ne pouvez pas installer le module supplémentaire hstore ou ne peut pas supposer qu'il est installé, voici une version améliorée de ce que j'ai posté précédemment. Toujours plus lent que le hstore opérateur, cependant :

CREATE OR REPLACE FUNCTION f_setfield(INOUT _comp_val anyelement
                                          , _field text, _val text)
  RETURNS anyelement
  LANGUAGE plpgsql STABLE AS
$func$
BEGIN

EXECUTE 'SELECT ' || array_to_string(ARRAY(
      SELECT CASE WHEN attname = _field
                THEN '$2'
                ELSE '($1).' || quote_ident(attname)
             END AS fld
      FROM   pg_catalog.pg_attribute
      WHERE  attrelid = pg_typeof(_comp_val)::text::regclass
      AND    attnum > 0
      AND    attisdropped = FALSE
      ORDER  BY attnum
      ), ',')
USING  _comp_val, _val
INTO   _comp_val;

END
$func$;

Appel :

CREATE TEMP TABLE t( a int, b text);  -- Composite type for testing
SELECT f_setfield(NULL::t, 'a', '1');

Remarques

  • Un cast explicite de la valeur _val au type de données cible n'est pas nécessaire, un littéral de chaîne dans la requête dynamique serait automatiquement contraint, évitant la sous-requête sur pg_type . Mais je suis allé un peu plus loin :

  • Remplacer quote_literal(_val) avec insertion directe de valeur via le USING clause. Enregistre un appel de fonction et deux casts, et est de toute façon plus sûr. text est automatiquement contraint au type cible dans PostgreSQL moderne. (N'a pas testé avec les versions antérieures à 9.1.)

  • array_to_string(ARRAY()) est plus rapide que string_agg() .

  • Aucune variable nécessaire, pas de DECLARE . Moins de devoirs.

  • Pas de sous-requête dans le SQL dynamique. ($1).field est plus rapide.

  • pg_typeof(_comp_val)::text::regclass
    fait la même chose que
    (SELECT typrelid FROM pg_catalog.pg_type WHERE oid = pg_typeof($1)::oid)
    pour les types composites valides, juste plus rapide.
    Cette dernière modification est construite sur l'hypothèse que pg_type.typname est toujours identique au pg_class.relname associé pour les types composites enregistrés, et la double distribution peut remplacer la sous-requête. J'ai exécuté ce test dans une grande base de données pour vérifier, et il s'est avéré vide comme prévu :

    SELECT *
    FROM   pg_catalog.pg_type t
    JOIN   pg_namespace  n ON n.oid = t.typnamespace
    WHERE  t.typrelid > 0  -- exclude non-composite types
    AND    t.typrelid IS DISTINCT FROM
          (quote_ident(n.nspname ) || '.' || quote_ident(typname))::regclass
  • L'utilisation d'un INOUT le paramètre évite le besoin d'un RETURN explicite . Ceci est juste un raccourci de notation. Pavel n'aimera pas ça, il préfère un RETURN explicite déclaration ...

Tout assemblé, c'est deux fois plus rapide comme la version précédente.

Réponse originale (obsolète) :

Le résultat est une version ~ 2,25 fois plus rapide . Mais je n'aurais probablement pas pu le faire sans m'appuyer sur la deuxième version de Pavel.

De plus, cette version évite la plupart des castings au texte et retour en faisant tout dans une seule requête, il devrait donc être beaucoup moins sujet aux erreurs.
Testé avec PostgreSQL 9.0 et 9.1 .

CREATE FUNCTION f_setfield(_comp_val anyelement, _field text, _val text)
  RETURNS anyelement
  LANGUAGE plpgsql STABLE AS
$func$
DECLARE
   _list text;
BEGIN
_list := (
   SELECT string_agg(x.fld, ',')
   FROM  (
      SELECT CASE WHEN a.attname = $2
              THEN quote_literal($3) || '::'|| (SELECT quote_ident(typname)
                                                FROM   pg_catalog.pg_type
                                                WHERE  oid = a.atttypid)
              ELSE quote_ident(a.attname)
             END AS fld
      FROM   pg_catalog.pg_attribute a 
      WHERE  a.attrelid = (SELECT typrelid
                           FROM   pg_catalog.pg_type
                           WHERE  oid = pg_typeof($1)::oid)
      AND    a.attnum > 0
      AND    a.attisdropped = false
      ORDER  BY a.attnum
      ) x
   );

EXECUTE 'SELECT ' || _list || ' FROM  (SELECT $1.*) x'
USING  $1
INTO   $1;

RETURN $1;
END
$func$;