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 dehstore
.
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 surpg_type
. Mais je suis allé un peu plus loin : -
Remplacer
quote_literal(_val)
avec insertion directe de valeur via leUSING
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 questring_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 quepg_type.typname
est toujours identique aupg_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'unRETURN
explicite . Ceci est juste un raccourci de notation. Pavel n'aimera pas ça, il préfère unRETURN
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$;