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

Trucs et astuces Postgres

Travaillez-vous avec Postgres au quotidien ? Écrire un code d'application qui parle à Postgres ? Consultez ensuite les petits extraits SQL ci-dessous qui peuvent vous aider à travailler plus rapidement !

Insérer plusieurs lignes dans une seule instruction

L'instruction INSERT peut insérer plusieurs lignes dans une seule instruction :

INSERT INTO planets (name, gravity)
     VALUES ('earth',    9.8),
            ('mars',     3.7),
            ('jupiter', 23.1);

En savoir plus sur ce que INSERT peut faire ici.

Insérer une ligne et renvoyer des valeurs attribuées automatiquement

Les valeurs générées automatiquement avec les constructions DEFAULT/serial/IDENTITY peuvent être renvoyées par l'instruction INSERT à l'aide de la clause RETURNING. Du point de vue du code d'application, un tel INSERT est exécuté comme un SELECT qui renvoie un jeu d'enregistrements.

-- table with 2 column values auto-generated on INSERT
CREATE TABLE items (
    slno       serial      PRIMARY KEY,
    name       text        NOT NULL,
    created_at timestamptz DEFAULT now()
);

INSERT INTO items (name)
     VALUES ('wooden axe'),
            ('loom'),
            ('eye of ender')
  RETURNING name, slno, created_at;

-- returns:
--      name     | slno |          created_at
-- --------------+------+-------------------------------
--  wooden axe   |    1 | 2020-08-17 05:35:45.962725+00
--  loom         |    2 | 2020-08-17 05:35:45.962725+00
--  eye of ender |    3 | 2020-08-17 05:35:45.962725+00

Clés primaires UUID générées automatiquement

Les UUID sont parfois utilisés à la place des clés primaires pour diverses raisons. Voici comment vous pouvez utiliser un UUID au lieu d'un numéro de série ou d'IDENTITÉ :

CREATE EXTENSION IF NOT EXISTS "uuid-ossp";

CREATE TABLE items (
    id    uuid DEFAULT uuid_generate_v4(),
    name  text NOT NULL
);

INSERT INTO items (name)
     VALUES ('wooden axe'),
            ('loom'),
            ('eye of ender')
  RETURNING id, name;
  
-- returns:
--                   id                  |     name
-- --------------------------------------+--------------
--  1cfaae8c-61ff-4e82-a656-99263b7dd0ae | wooden axe
--  be043a89-a51b-4d8b-8378-699847113d46 | loom
--  927d52eb-c175-4a97-a0b2-7b7e81d9bc8e | eye of ender

Insérer si non existant, mettre à jour sinon

Dans Postgres 9.5 et versions ultérieures, vous pouvez upsert directement en utilisant la construction ON CONFLICT :

CREATE TABLE parameters (
    key   TEXT PRIMARY KEY,
    value TEXT
);

-- when "key" causes a constraint violation, update the "value"
INSERT INTO parameters (key, value) 
     VALUES ('port', '5432')
ON CONFLICT (key) DO
            UPDATE SET value=EXCLUDED.value;

Copier des lignes d'une table dans une autre

L'instruction INSERT a une forme où les valeurs peuvent être fournies par une instruction SELECT. Utilisez ceci pour copier des lignes d'une table dans une autre :

-- copy between tables with similar columns 
  INSERT INTO pending_quests
SELECT * FROM quests
        WHERE progress < 100;

-- supply some values from another table, some directly
  INSERT INTO archived_quests
       SELECT now() AS archival_date, *
         FROM quests
        WHERE completed;

Si vous souhaitez charger des tableaux en masse, consultez également la commande COPY, qui peut être utilisée pour insérer des lignes à partir d'un fichier texte ou CSV.

Supprimer et renvoyer les informations supprimées

Vous pouvez utiliser le RETURNING clause pour renvoyer les valeurs des lignes qui ont été supprimées à l'aide d'une instruction de suppression en bloc :

-- return the list of customers whose licenses were deleted after expiry
DELETE FROM licenses
      WHERE now() > expiry_date
  RETURNING customer_name;

Déplacer des lignes d'une table à une autre

Vous pouvez déplacer des lignes d'une table à une autre en une seule instruction, en utilisant les CTE avec DELETE .. RETURNING :

-- move yet-to-start todo items from 2020 to 2021
WITH ah_well AS (
    DELETE FROM todos_2020
          WHERE NOT started
      RETURNING *
)
INSERT INTO todos_2021
            SELECT * FROM ah_well;

Mettre à jour les lignes et renvoyer les valeurs mises à jour

La clause RETURNING peut également être utilisée dans les UPDATE. Notez que seules les nouvelles valeurs des colonnes mises à jour peuvent être renvoyées de cette façon.

-- grant random amounts of coins to eligible players
   UPDATE players
      SET coins = coins + (100 * random())::integer
    WHERE eligible
RETURNING id, coins;

Si vous avez besoin de la valeur d'origine des colonnes mises à jour :c'est possible grâce à une jointure auto-religieuse, mais il n'y a aucune garantie d'atomicité. Essayez d'utiliser un SELECT .. FOR UPDATE à la place.

Mettre à jour quelques lignes aléatoires et renvoyer les mises à jour

Voici comment vous pouvez choisir quelques lignes aléatoires dans un tableau, les mettre à jour et renvoyer celles mises à jour, le tout en une seule fois :

WITH lucky_few AS (
    SELECT id
      FROM players
  ORDER BY random()
     LIMIT 5
)
   UPDATE players
      SET bonus = bonus + 100 
    WHERE id IN (SELECT id FROM lucky_few)
RETURNING id;

Créer une table comme une autre table

Utilisez la construction CREATE TABLE .. LIKE pour créer une table avec les mêmes colonnes qu'une autre :

CREATE TABLE to_be_audited (LIKE purchases);

Par défaut, cela ne crée pas d'index, de contraintes, de valeurs par défaut similaires, etc. Pour ce faire, demandez explicitement à Postgres :

CREATE TABLE to_be_audited (LIKE purchases INCLUDING ALL);

Voir la syntaxe complète ici.

Extraire un ensemble aléatoire de lignes dans une autre table

Depuis Postgres 9.5, la fonctionnalité TABLESAMPLE est disponible pour extraire un échantillon de lignes d'une table. Il existe actuellement deux méthodes d'échantillonnage, et bernoulli est généralement celui que vous souhaitez :

-- copy 10% of today's purchases into another table
INSERT INTO to_be_audited
     SELECT *
       FROM purchases
TABLESAMPLE bernoulli(10)
      WHERE transaction_date = CURRENT_DATE;

Le système La méthode d'échantillonnage de table est plus rapide, mais ne renvoie pas de distribution uniforme. Consultez la documentation pour plus d'informations.

Créer une table à partir d'une requête de sélection

Vous pouvez utiliser la construction CREATE TABLE .. AS pour créer la table et la remplir à partir d'une requête SELECT, le tout en une seule fois :

CREATE TABLE to_be_audited AS
      SELECT *
        FROM purchases
 TABLESAMPLE bernoulli(10)
       WHERE transaction_date = CURRENT_DATE;

La table résultante est comme une vue matérialisée sans requête associée. En savoir plus sur CREATE TABLE .. AS ici.

Créer des tables non enregistrées

Non connecté les tables ne sont pas soutenues par des enregistrements WAL. Cela signifie que les mises à jour et les suppressions de ces tables sont plus rapides, mais elles ne tolèrent pas les plantages et ne peuvent pas être répliquées.

CREATE UNLOGGED TABLE report_20200817 (LIKE report_v3);

Créer des tables temporaires

Temporaire les tables sont implicitement des tables non journalisées, avec une durée de vie plus courte. Ils s'autodétruisent automatiquement à la fin d'une session (par défaut), ou à la fin de la transaction.

Les données des tables temporaires ne peuvent pas être partagées entre les sessions. Plusieurs sessions peuvent créer des tables temporaires portant le même nom.

-- temp table for duration of the session
CREATE TEMPORARY TABLE scratch_20200817_run_12 (LIKE report_v3);

-- temp table that will self-destruct after current transaction
CREATE TEMPORARY TABLE scratch_20200817_run_12
                      (LIKE report_v3)
                      ON COMMIT DROP;

-- temp table that will TRUNCATE itself after current transaction
CREATE TEMPORARY TABLE scratch_20200817_run_12
                       (LIKE report_v3)
                       ON COMMIT DELETE ROWS;

Ajouter des commentaires

Des commentaires peuvent être ajoutés à n'importe quel objet de la base de données. De nombreux outils, y compris pg_dump, les comprennent. Un commentaire utile pourrait éviter une tonne de problèmes lors du nettoyage !

COMMENT ON INDEX idx_report_last_updated
        IS 'needed for the nightly report app running in dc-03';

COMMENT ON TRIGGER tgr_fix_column_foo
        IS 'mitigates the effect of bug #4857';

Verrous consultatifs

Les verrous consultatifs peuvent être utilisés pour coordonner les actions entre deux applications connectées à la même base de données. Vous pouvez utiliser cette fonctionnalité pour implémenter un mutex distribué global pour une certaine opération, par exemple. Lisez tout à ce sujet ici dans la documentation.

-- client 1: acquire a lock 
SELECT pg_advisory_lock(130);
-- ... do work ...
SELECT pg_advisory_unlock(130);

-- client 2: tries to do the same thing, but mutually exclusive
-- with client 1
SELECT pg_advisory_lock(130); -- blocks if anyone else has held lock with id 130

-- can also do it without blocking:
SELECT pg_try_advisory_lock(130);
-- returns false if lock is being held by another client
-- otherwise acquires the lock then returns true

Agréger en tableaux, tableaux JSON ou chaînes

Postgres fournit des fonctions d'agrégation qui concatènent des valeurs dans un GROUP toyield arrays, JSON arrays or strings :

-- get names of each guild, with an array of ids of players that
-- belong to that guild
  SELECT guilds.name AS guild_name, array_agg(players.id) AS players
    FROM guilds
    JOIN players ON players.guild_id = guilds.id
GROUP BY guilds.id;

-- same but the player list is a CSV string
  SELECT guilds.name, string_agg(players.id, ',') -- ...
  
-- same but the player list is a JSONB array
  SELECT guilds.name, jsonb_agg(players.id) -- ...
  
-- same but returns a nice JSONB object like so:
-- { guild1: [ playerid1, playerid2, .. ], .. }
SELECT jsonb_object_agg(guild_name, players) FROM (
  SELECT guilds.name AS guild_name, array_agg(players.id) AS players
    FROM guilds
    JOIN players ON players.guild_id = guilds.id
GROUP BY guilds.id
) AS q;

Agrégats avec commande

Pendant que nous y sommes, voici comment définir l'ordre des valeurs qui sont transmises à la fonction d'agrégation, au sein de chaque groupe :

-- each state with a list of counties sorted alphabetically
  SELECT states.name, string_agg(counties.name, ',' ORDER BY counties.name)
    FROM states JOIN counties
    JOIN states.name = counties.state_name
GROUP BY states.name;

Oui, il y a une clause ORDER BY à la fin de la parenthèse d'appel de fonction. Oui, la syntaxe est bizarre.

Array et Unnest

Utilisez le constructeur ARRAY pour convertir un ensemble de lignes, chacune avec une colonne, en un tableau. Le pilote de base de données (comme JDBC) devrait être capable de mapper des tableaux Postgres dans des tableaux natifs et pourrait être plus facile à utiliser.

-- convert rows (with 1 column each) into a 1-dimensional array
SELECT ARRAY(SELECT id FROM players WHERE lifetime_spend > 10000);

La fonction unnest fait l'inverse - elle convertit chaque élément d'un tableau en une ligne. Ils sont particulièrement utiles pour les jointures croisées avec une liste de valeurs :

    SELECT materials.name || ' ' || weapons.name
      FROM weapons
CROSS JOIN UNNEST('{"wood","gold","stone","iron","diamond"}'::text[])
           AS materials(name);

-- returns:
--     ?column?
-- -----------------
--  wood sword
--  wood axe
--  wood pickaxe
--  wood shovel
--  gold sword
--  gold axe
-- (..snip..)

Combiner les instructions Select avec Union

Vous pouvez utiliser la construction UNION pour combiner les résultats de plusieurs SELECT similaires :

SELECT name FROM weapons
UNION
SELECT name FROM tools
UNION
SELECT name FROM materials;

Utilisez les CTE pour poursuivre le traitement du résultat combiné :

WITH fight_equipment AS (
    SELECT name, damage FROM weapons
    UNION
    SELECT name, damage FROM tools
)
  SELECT name, damage
    FROM fight_equipment
ORDER BY damage DESC
   LIMIT 5;

Il existe également des constructions INTERSECT et EXCEPT, dans la même veine que UNION. En savoir plus sur ces clauses dans la documentation.

Corrections rapides dans Select :case, coalesce et nullif

Le CASE, COALESCE et NULLIF pour faire de petites "corrections" rapides pour les données SELECT.CASE est comme commutateur dans les langages de type C :

SELECT id,
       CASE WHEN name='typ0' THEN 'typo' ELSE name END
  FROM items;
  
SELECT CASE WHEN rating='G'  THEN 'General Audiences'
            WHEN rating='PG' THEN 'Parental Guidance'
            ELSE 'Other'
       END
  FROM movies;

COALESCE peut être utilisé pour substituer une certaine valeur au lieu de NULL.

-- use an empty string if ip is not available
SELECT nodename, COALESCE(ip, '') FROM nodes;

-- try to use the first available, else use '?'
SELECT nodename, COALESCE(ipv4, ipv6, hostname, '?') FROM nodes;

NULLIF fonctionne dans l'autre sens, vous permettant d'utiliser NULL au lieu d'une certaine valeur :

-- use NULL instead of '0.0.0.0'
SELECT nodename, NULLIF(ipv4, '0.0.0.0') FROM nodes;

Générer des données de test aléatoires et séquentielles

Diverses méthodes de génération de données aléatoires :

-- 100 random dice rolls
SELECT 1+(5 * random())::int FROM generate_series(1, 100);

-- 100 random text strings (each 32 chars long)
SELECT md5(random()::text) FROM generate_series(1, 100);

-- 100 random text strings (each 36 chars long)
SELECT uuid_generate_v4()::text FROM generate_series(1, 100);

-- 100 random small text strings of varying lengths
CREATE EXTENSION IF NOT EXISTS "pgcrypto";
SELECT gen_random_bytes(1+(9*random())::int)::text
  FROM generate_series(1, 100);

-- 100 random dates in 2019
SELECT DATE(
         DATE '2019-01-01' + ((random()*365)::int || ' days')::interval
       )
  FROM generate_series(1, 100);
  
-- 100 random 2-column data: 1st column integer and 2nd column string
WITH a AS (
  SELECT ARRAY(SELECT random() FROM generate_series(1,100))
),
b AS (
  SELECT ARRAY(SELECT md5(random()::text) FROM generate_series(1,100))
)
SELECT unnest(i), unnest(j)
  FROM a a(i), b b(j);

-- a daily count for 2020, generally increasing over time
SELECT i, ( (5+random()) * (row_number() over()) )::int
  FROM generate_series(DATE '2020-01-01', DATE '2020-12-31', INTERVAL '1 day')
       AS s(i);

Utilisez bernoulli échantillonnage de table pour sélectionner un nombre aléatoire de lignes dans une table :

-- select 15% of rows from the table, chosen randomly  
     SELECT *
       FROM purchases
TABLESAMPLE bernoulli(15)

Utilisez generate_series pour générer des valeurs séquentielles d'entiers, de dates et d'autres types intégrés incrémentiels :

-- generate integers from 1 to 100
SELECT generate_series(1, 100);

-- call the generated values table as "s" with a column "i", to use in
-- CTEs and JOINs
SELECT i FROM generate_series(1, 100) AS s(i);

-- generate multiples of 3 in different ways
SELECT 3*i FROM generate_series(1, 100) AS s(i);
SELECT generate_series(1, 100, 3);

-- works with dates too: here are all the Mondays in 2020:
SELECT generate_series(DATE '2020-01-06', DATE '2020-12-31', INTERVAL '1 week');

Obtenir le nombre approximatif de lignes

L'horrible performance de COUNT(*) est peut-être le sous-produit le plus laid de l'architecture de Postgres. Si vous avez juste besoin d'un nombre approximatif de lignes pour une énorme table, vous pouvez éviter un COUNT complet en interrogeant le collecteur de statistiques :

SELECT relname, n_live_tup FROM pg_stat_user_tables;

Le résultat est exact après une ANALYSE, et sera progressivement incorrect au fur et à mesure que les lignes seront modifiées. Ne l'utilisez pas si vous voulez des comptages précis.

Type d'intervalle

L'intervalle type peut non seulement être utilisé comme type de données de colonne, mais peut être ajouté et soustrait de date et horodatage valeurs :

-- get licenses that expire within the next 7 days
SELECT id
  FROM licenses
 WHERE expiry_date BETWEEN now() - INTERVAL '7 days' AND now();
 
-- extend expiry date
UPDATE licenses
   SET expiry_date = expiry_date + INTERVAL '1 year'
 WHERE id = 42;

Désactiver la validation des contraintes pour l'insertion en bloc

-- add a constraint, set as "not valid"
ALTER TABLE players
            ADD CONSTRAINT fk__players_guilds
                           FOREIGN KEY (guild_id)
                            REFERENCES guilds(id)
            NOT VALID;

-- insert lots of rows into the table
COPY players FROM '/data/players.csv' (FORMAT CSV);

-- now validate the entire table
ALTER TABLE players
            VALIDATE CONSTRAINT fk__players_guilds;

Vider une table ou une requête dans un fichier CSV

-- dump the contents of a table to a CSV format file on the server
COPY players TO '/tmp/players.csv' (FORMAT CSV);

-- "header" adds a heading with column names
COPY players TO '/tmp/players.csv' (FORMAT CSV, HEADER);

-- use the psql command to save to your local machine
\copy players TO '~/players.csv' (FORMAT CSV);

-- can use a query instead of a table name
\copy ( SELECT id, name, score FROM players )
      TO '~/players.csv'
      ( FORMAT CSV );

Utilisez plus de types de données natifs dans votre conception de schéma

Postgres est livré avec de nombreux types de données intégrés. Représenter les données dont votre application a besoin à l'aide de l'un de ces types peut économiser beaucoup de code d'application, accélérer votre développement et entraîner moins d'erreurs.

Par exemple, si vous représentez l'emplacement d'une personne à l'aide du type de données point et une région d'intérêt sous forme de polygon , vous pouvez vérifier si la personne est dans la région simplement avec :

-- the @> operator checks if the region of interest (a "polygon") contains
-- the person's location (a "point")
SELECT roi @> person_location FROM live_tracking;

Voici quelques types de données Postgres intéressants et des liens vers lesquels vous pouvez trouver plus d'informations à leur sujet :

  • Types d'énumération de type C
  • Types géométriques :point, boîte, segment de ligne, ligne, chemin, polygone, cercle
  • Adresses IPv4, IPv6 et MAC
  • Types de plages :plages d'entiers, de dates et d'horodatage
  • Tableaux pouvant contenir des valeurs de n'importe quel type
  • UUID :si vous avez besoin d'utiliser des UUID ou si vous avez simplement besoin de travailler avec des entiers aléatoires de 129 octets, envisagez d'utiliser le uuid type et le uuid-oscp extension pour le stockage, la génération et le formatage des UUID
  • Intervalles de date et d'heure utilisant le type INTERVAL
  • et bien sûr les très populaires JSON et JSONB

Extensions groupées

La plupart des installations de Postgres incluent un tas d'"extensions" standard. Les extensions sont des composants installables (et proprement désinstallables) qui fournissent des fonctionnalités non incluses dans le noyau. Ils peuvent être installés par base de données.

Certaines d'entre elles sont très utiles, et cela vaut la peine de passer un peu de temps à les connaître :

  • pg_stat_statements - statistiques concernant l'exécution de chaque requête SQL
  • auto_explain – enregistre le plan d'exécution des requêtes (lente)
  • postgres_fdw,dblink etfile_fdw - moyens d'accéder à d'autres sources de données (comme les serveurs Postgres distants, les serveurs MySQL, les fichiers sur le système de fichiers du serveur) comme les tables régulières
  • citext - un type de données "texte insensible à la casse", plus efficace que lower()-ing partout
  • hstore – un type de données clé-valeur
  • pgcrypto – Fonctions de hachage SHA, chiffrement