Types avancés

Module S9

Dalibo SCOP

24.04

17 avril 2024

Sur ce document

Formation Module S9
Titre Types avancés
Révision 24.04
PDF https://dali.bo/s9_pdf
EPUB https://dali.bo/s9_epub
HTML https://dali.bo/s9_html
Slides https://dali.bo/s9_slides
TP https://dali.bo/s9_tp
TP (solutions) https://dali.bo/s9_solutions

Licence Creative Commons CC-BY-NC-SA

Cette formation est sous licence CC-BY-NC-SA. Vous êtes libre de la redistribuer et/ou modifier aux conditions suivantes :

  • Paternité
  • Pas d’utilisation commerciale
  • Partage des conditions initiales à l’identique

Marques déposées

PostgreSQL® Postgres® et le logo Slonik sont des marques déposées par PostgreSQL Community Association of Canada.

Versions de PostgreSQL couvertes

Ce document ne couvre que les versions supportées de PostgreSQL au moment de sa rédaction, soit les versions 12 à 16.

Types avancés

PostgreSQL offre des types avancés :

  • Composés :
    • hstore
    • JSON : json, jsonb
    • XML
  • Pour les objets binaires :
    • bytea
    • Large Objects

Types composés : généralités

  • Un champ = plusieurs attributs
  • De loin préférable à une table Entité/Attribut/Valeur
  • Uniquement si le modèle relationnel n’est pas assez souple
  • 3 types dans PostgreSQL :
    • hstore : clé/valeur
    • json : JSON, stockage texte, validation syntaxique, fonctions d’extraction
    • jsonb : JSON, stockage binaire, accès rapide, fonctions d’extraction, de requêtage, indexation avancée

hstore

Stocker des données non structurées

  • Extension
  • Stockage Clé/Valeur, uniquement texte
  • Binaire
  • Indexable
  • Plusieurs opérateurs disponibles

hstore : exemple

CREATE EXTENSION hstore ;

CREATE TABLE animaux (nom text, caract hstore);
INSERT INTO animaux VALUES ('canari','pattes=>2,vole=>oui');
INSERT INTO animaux VALUES ('loup','pattes=>4,carnivore=>oui');
INSERT INTO animaux VALUES ('carpe','eau=>douce');

CREATE INDEX idx_animaux_donnees ON animaux
                                 USING gist (caract);
SELECT *, caract->'pattes' AS nb_pattes FROM animaux
WHERE caract@>'carnivore=>oui';
 nom  |              caract               | nb_pattes
------+-----------------------------------+-----------
 loup | "pattes"=>"4", "carnivore"=>"oui" | 4

JSON

{
  "firstName": "Jean",
  "lastName": "Dupont",
  "age": 27,
  "address": {
    "streetAddress": "43 rue du Faubourg Montmartre",
    "city": "Paris",
    "postalCode": "75009"
  }
}
  • json : format texte
  • jsonb : format binaire, à préférer
  • jsonpath : SQL/JSON paths (PG 12+)

Type json

  • Type texte
    • avec validation du format
  • Réservé au stockage à l’identique
  • Préférer jsonb

Type jsonb

  • JSON au format Binaire
  • Indexation GIN
  • Gestion du langage JSON Path (v12+)

Validation du format JSON

SELECT  '{"auteur": "JRR Tolkien","titre":"Le Hobbit"}' IS JSON ;
-- true
SELECT  '{"auteur": "JRR Tolkien","titre":"Le Hobbit}' IS JSON ;
-- false
  • Aussi : IS JSON WITH/WITHOUT UNIQUE KEYS, IS JSON ARRAY, IS JSON OBJECT, IS JSON SCALAR
  • PostgreSQL 16+

JSON : Exemple d’utilisation

CREATE TABLE personnes (datas jsonb );

INSERT INTO personnes  (datas) VALUES ('
{
  "firstName": "Jean",
  "lastName": "Valjean",
  "address": {
    "streetAddress": "43 rue du Faubourg Montmartre",
    "city": "Paris",
    "postalCode": "75002"
  },
  "phoneNumbers": [
    { "number": "06 12 34 56 78" },
    {"type": "bureau",
     "number": "07 89 10 11 12"}
  ],
  "children": [],
  "spouse": null
}
'),
('
{
  "firstName": "Georges",
  "lastName": "Durand",
  "address": {
    "streetAddress": "27 rue des Moulins",
    "city": "Châteauneuf",
    "postalCode": "45990"
  },
  "phoneNumbers": [
    { "number": "06 21 34 56 78" },
    {"type": "bureau",
     "number": "07 98 10 11 12"}
  ],
  "children": [],
  "spouse": null
}
');

JSON : Affichage de champs

SELECT datas->>'firstName' AS prenom,    -- text
       datas->'address'    AS addr       -- jsonb
       FROM personnes ;
SELECT datas #>> '{address,city}' AS villes FROM personnes ; -- text
SELECT datas['address']['city'] as villes from personnes ;  -- jsonb, v14
SELECT datas['address']->>'city' as villes from personnes ;  -- text, v14
SELECT jsonb_array_elements (datas->'phoneNumbers')->>'number'
FROM   personnes;

Conversions jsonb / relationnel

  • Construire un objet JSON depuis un ensemble :
    • json_object_agg()
  • Construire un ensemble de tuples depuis un objet JSON :
    • jsonb_each()
    • jsonb_to_record()
  • Manipuler des tableaux :
    • jsonb_array_elements()
    • jsonb_to_recordset()

JSON : performances

Inconvénients par rapport à un modèle normalisé :

  • Perte d’intégrité (types, contraintes, FK…)
  • Complexité du code
  • Pas de statistiques sur les clés JSON !
  • Pas forcément plus léger en disque
    • clés répétées
  • Lire 1 champ = lire tout le JSON
    • voire accès table TOAST
  • Mise à jour : tout ou rien

jsonb : indexation (1/2)

  • Index fonctionnel sur un champ précis
    • bonus : statistiques
    CREATE INDEX idx_prs_nom ON personnes ((datas->>'lastName')) ;
    ANALYZE personnes ;
  • Champ dénormalisé:
    • champ normal, indexable, facile à utiliser
    • statistiques
    ALTER TABLE personnes
    ADD COLUMN lastname text
    GENERATED ALWAYS AS  ((datas->>'lastName')) STORED ;

jsonb : indexation (2/2)

  • Indexation « schemaless » grâce au GIN :
CREATE INDEX idx_prs ON personnes USING gin(datas jsonb_path_ops) ;

SQL/JSON & JSONpath

  • SQL:2016 introduit SQL/JSON et le langage JSON Path
  • JSON Path :
    • langage de recherche pour JSON
    • concis, flexible, plus rapide
    • inclus dans PostreSQL 12 pour l’essentiel
    • exemple :
    SELECT
    jsonb_path_query (datas, '$.phoneNumbers[*] ? (@.type == "bureau") ')
    FROM personnes ;

XML

  • Type xml
    • stocke un document XML
    • valide sa structure
  • Quelques fonctions et opérateurs disponibles :
    • XMLPARSE, XMLSERIALIZE, query_to_xml, xmlagg
    • xpath (XPath 1.0 uniquement)

Objets binaires

  • Souvent une mauvaise idée…

  • 2 méthodes

    • bytea : type binaire
    • Large Objects : manipulation comme un fichier

bytea

  • Un type comme les autres
    • bytea : tableau d’octets
    • en texte : bytea_output = hex ou escape
  • Récupération intégralement en mémoire !
  • Toute modification entraîne la réécriture complète du bytea
  • Maxi 1 Go (à éviter)
    • en pratique intéressant pour quelques Mo
  • Import :
SELECT pg_read_binary_file ('/chemin/fichier');

Large Object

  • À éviter…
    • préférer bytea
  • Maxi 4 To (éviter…)
  • Objet indépendant des tables
    • OID à stocker dans les tables
    • se compresse mal
  • Suppression manuelle !
    • trigger
    • lo_unlink & vacuumlo
  • Fonction de manipulation, modification
    • lo_create, lo_import
    • lo_seek, lo_open, lo_read, lo_write

Quiz

Travaux pratiques

Hstore (Optionnel)

jsonb

Large Objects

Travaux pratiques (solutions)