web-dev-qa-db-fra.com

Convertir une ligne à un document JSON imbriqué contenant toutes les relations

Dans PostgreSQL 10.10, j'ai créé une fonction de déclenchement dans PL/PGSQL qui convertit la ligne NEW à un objet JSON à l'aide de to_jsonb(NEW). Mais maintenant, je dois inclure les enregistrements de l'autre côté des clés étrangères de l'enregistrement NEW dans l'objet JSON de manière imbriquée.

Par exemple:

avant:

employee = {
    "id": 1,
    "name": "myname",
    "department": 2,
    "phone_no": "123456789"
}

après:

employee = {
    "id": 1,
    "name": "myname",
    "department": {
        "id": 2,
        "name": "IT"
    },
    "phone_no": "123456789"
}

Quelle est la meilleure et la plus générique de manière générique de l'accomplir sans acquisition préalable sur le schéma de l'enregistrement NEW? Je dois conserver cette fonction de déclenchement aussi générique que possible parce que je prévois d'l'utiliser sur toutes les tables. Un niveau de profondeur dans les clés étrangères suivantes me suffit actuellement. Aussi à simplifier, je peux supposer que toutes les clés étrangères devraient être une seule colonne.

Si je comprends bien, j'ai besoin de boucler sur toutes les colonnes de l'enregistrement NEW, découvrez si la colonne est une clé étrangère utilisant information_schema ou alors pg_catalog, trouvez les détails de la clé étrangère telles que la colonne sur laquelle il pointe de la table, puis effectuez un SQL dynamique SELECT (car je suppose que les noms de table et colonne seraient des chaînes, pas des identificateurs SQL) sur la table cible Pour l'enregistrement cible, convertissez l'enregistrement en JSON et l'attribuez-la enfin à la clé appropriée de l'objet JSON à niveau supérieur.

J'essaie encore d'écrire le code de travail actuel pour cela, pour lequel je me félicite de l'aide ou des indications. Et il pourrait y avoir des solutions plus simples à ce problème, que j'aimerais savoir.

2
zaadeh

Votre hypothèse est assez proche, vous aurez besoin de SQL dynamique.

Mais cela devrait être considérablement plus rapide et plus élégant que de boucler à travers toutes les colonnes de l'enregistrement NEW record, etc.:

CREATE OR REPLACE FUNCTION trg_jsonb_row_with_fk()
  RETURNS trigger AS
$func$
DECLARE
   _sql text;
   _jsonb_row jsonb;
BEGIN
   SELECT 'SELECT to_jsonb($1) || '
       || string_agg(
            format('(SELECT jsonb_build_object(%1$L, t.*)
                     FROM %2$s t WHERE %3$I = $1.%1$I)'
                 , a.attname                 -- %1$L, %1$I
                 , c.confrelid::regclass     -- %2$s
                 , f.attname)                -- %3$I
            , ' || ')
   FROM   pg_constraint c
   JOIN   pg_attribute  a ON a.attrelid = c.conrelid
   JOIN   pg_attribute  f ON f.attrelid = c.confrelid
   WHERE  c.conrelid = TG_RELID     
   AND    c.contype  = 'f'               -- to select only FK constraints
   AND    a.attnum   = c.conkey[1]       -- assuming only single-col FKs!
   AND    f.attnum   = c.confkey[1]
   INTO   _sql;

   IF FOUND THEN                         -- FKs found
      EXECUTE _sql USING NEW INTO _jsonb_row;
   ELSE                                  -- no FKs found, plain conversion
      _jsonb_row := to_jsonb(NEW);
   END IF;

   RAISE NOTICE '%', _jsonb_row;         -- do something with it ...
   RETURN NEW;                           -- proceed with org. row
END
$func$  LANGUAGE plpgsql;

Exemple de déclenchement à l'aide de la fonction ci-dessus:

CREATE TRIGGER upd_bef_jsonb_row_with_fk
  BEFORE UPDATE ON tbl
  FOR EACH ROW EXECUTE PROCEDURE trg_jsonb_row_with_fk();

Cela construit des sous-sols pour tous les FKS à partir de Tables de catalogue Postgres et exécute la commande SQL de manière dynamique. Pour la simplicité, j'inclus toutes les colonnes utilisateur des lignes correspondantes dans les tables de recherche (t.*).

Nitpick: jsonb_build_object(%1$L, t.*), pas jsonb_build_object(%1$L, t).
[.____] On dirait que l'ajout de bruit, mais cela évite un problème de cas d'angle: Ceci est censé fonctionner pour tout ​​table d'entrée, et on pourrait contenir une colonne nommée t. Ensuite, le nom t dans l'expression ci-dessus résoudrait à la colonne au lieu de l'alias de table (la ligne complète). L'utilisation de t.* enlève cette ambiguïté car elle ne peut résoudre que toute la ligne. (Les parenthèses seraient nécessaires pour se référer à un type composite colonne, comme (t).*). Lisez le manuel ici et ici .

Comme il utilise le nom de la colonne des colonnes FK d'origine comme nom de clé pour les objets étendus, la plaine Concaténation avec || Fait savoir ce dont vous avez besoin: elle remplace la valeur simple existante avec l'objet JSONB.

En plus de lecture:

1
Erwin Brandstetter