PostgreSQL
 sql >> Database >  >> RDS >> PostgreSQL

Suggerimenti e trucchi per Postgres

Lavori con Postgres quotidianamente? Scrivi il codice dell'applicazione che parla con Postgres? Quindi dai un'occhiata ai piccoli frammenti SQL di seguito che possono aiutarti a lavorare più velocemente!

Inserisci più righe in un'unica istruzione

L'istruzione INSERT può inserire più di una riga in una singola istruzione:

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

Leggi di più su cosa può fare INSERT qui.

Inserisci una riga e restituisci valori assegnati automaticamente

I valori generati automaticamente con i costrutti DEFAULT/serial/IDENTITY possono essere restituiti dall'istruzione INSERT utilizzando la clausola RETURNING. Dal punto di vista del codice dell'applicazione, tale INSERT viene eseguito come un SELECT che restituisce arecordset.

-- 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

Chiavi primarie UUID generate automaticamente

Gli UUID vengono talvolta utilizzati al posto delle chiavi primarie per vari motivi. Ecco come puoi usare un UUID invece di un seriale o IDENTITY:

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

Inserisci se non esistente, aggiorna altrimenti

In Postgres 9.5 e versioni successive, puoi aggiornare utilizzando direttamente il costrutto 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;

Copia righe da una tabella a un'altra

L'istruzione INSERT ha un modulo in cui i valori possono essere forniti da un'istruzione SELECT. Usalo per copiare le righe da una tabella all'altra:

-- 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;

Se stai cercando di caricare in blocco le tabelle, controlla anche il comando COPY, che può essere utilizzato per inserire righe da un file di testo o CSV.

Elimina e restituisci le informazioni eliminate

Puoi utilizzare il RETURNING clausola per restituire i valori dalle righe che sono state eliminate utilizzando un'istruzione di eliminazione in blocco:

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

Sposta le righe da una tabella all'altra

Puoi spostare le righe da una tabella all'altra in una singola istruzione, utilizzando CTE con 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;

Aggiorna righe e restituisce valori aggiornati

La clausola RETURNING può essere utilizzata anche negli UPDATE. Nota che solo i nuovi valori delle colonne aggiornate possono essere restituiti in questo modo.

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

Se è necessario il valore originale delle colonne aggiornate:è possibile tramite un self-join, ma non c'è garanzia di atomicità. Prova a usare un SELECT .. FOR UPDATE invece.

Aggiorna alcune righe casuali e restituisci quelle aggiornate

Ecco come puoi scegliere alcune righe casuali da una tabella, aggiornarle e restituire quelle aggiornate, tutto in una volta:

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;

Crea una tabella come un'altra tabella

Usa il costrutto CREATE TABLE .. LIKE per creare una tabella con le stesse colonne di un'altra:

CREATE TABLE to_be_audited (LIKE purchases);

Per impostazione predefinita, questo non crea indici, vincoli, impostazioni predefinite simili, ecc. Per farlo, chiedi esplicitamente a Postgres:

CREATE TABLE to_be_audited (LIKE purchases INCLUDING ALL);

Vedi la sintassi completa qui.

Estrai un insieme casuale di righe in un'altra tabella

Da Postgres 9.5, la funzione TABLESAMPLE è disponibile per estrarre un campione di righe da una tabella. Esistono attualmente due metodi di campionamento e bernoulli è di solito quello che vuoi:

-- 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;

Il sistema Il metodo tableampling è più veloce, ma non restituisce una distribuzione uniforme. Consulta i documenti per ulteriori informazioni.

Crea una tabella da una query di selezione

Puoi usare il costrutto CREATE TABLE .. AS per creare la tabella e popolarla da una query SELECT, tutto in una volta:

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

La tabella risultante è come una vista materializzata senza una query associata. Leggi di più su CREATE TABLE .. COME qui.

Crea tabelle non registrate

Non registrato le tabelle non sono supportate da record WAL. Ciò significa che gli aggiornamenti e le eliminazioni di tali tabelle sono più rapidi, ma non tollerano gli arresti anomali e non possono essere replicati.

CREATE UNLOGGED TABLE report_20200817 (LIKE report_v3);

Crea tabelle temporanee

Temporaneo le tabelle sono tabelle non registrate implicitamente, con una durata più breve. Si autodistruggono automaticamente al termine di una sessione (impostazione predefinita) o al termine della transazione.

I dati all'interno di tabelle temporanee non possono essere condivisi tra sessioni. Sessioni multiple possono creare tabelle temporanee con lo stesso nome.

-- 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;

Aggiungi commenti

I commenti possono essere aggiunti a qualsiasi oggetto nel database. Molti strumenti, incluso pg_dump, li capiscono. Un commento utile potrebbe semplicemente evitare un sacco di problemi durante la pulizia!

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';

Blocchi di avviso

I blocchi di avviso possono essere utilizzati per coordinare le azioni tra due app collegate alla uguale Banca dati. È possibile utilizzare questa funzione per implementare un mutex distribuito globale per una determinata operazione, ad esempio. Leggi tutto qui in thedocs.

-- 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

Aggrega in array, array JSON o stringhe

Postgres fornisce funzioni aggregate che concatenano i valori in un GRUPPO array toyield, array JSON o stringhe:

-- 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;

Aggrega con ordine

Mentre siamo sull'argomento, ecco come impostare l'ordine dei valori che vengono passati alla funzione di aggregazione, all'interno di ciascun gruppo :

-- 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;

Sì, c'è una clausola ORDER BY finale all'interno della paranthesis della chiamata di funzione. Sì, la sintassi è strana.

Array e Unnest

Utilizzare il costruttore ARRAY per convertire un insieme di righe, ciascuna con una colonna, in una matrice. Il driver del database (come JDBC) dovrebbe essere in grado di mappare gli array Postgres in array nativi e potrebbe essere più facile lavorarci.

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

La funzione unnest fa il contrario:converte ogni elemento in una matrice in una riga. Sono molto utili nell'unione incrociata con un elenco di valori:

    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..)

Unisci dichiarazioni Select con Union

È possibile utilizzare il costrutto UNION per combinare i risultati di più SELECT simili:

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

Usa i CTE per elaborare ulteriormente il risultato combinato:

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;

Esistono anche costrutti INTERSECT ed EXCEPT, sulla stessa linea di UNION. Maggiori informazioni su queste clausole nei documenti.

Correzioni rapide in Select:case, coalesce e nullif

CASE, COALESCE e NULLIF per apportare piccole "correzioni" rapide per i dati SELECTed.CASE è come switch in linguaggi simili a 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 può essere utilizzato per sostituire un determinato valore invece di 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 funziona nell'altro modo, permettendoti di usare NULL invece di un certo valore:

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

Genera dati di test casuali e sequenziali

Vari metodi per generare dati casuali:

-- 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);

Usa bernoulli campionamento della tabella per selezionare un numero casuale di righe da una tabella:

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

Usa generate_series per generare valori sequenziali di numeri interi, date e altri tipi integrati incrementabili:

-- 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');

Ottieni un conteggio approssimativo delle righe

L'orribile performance di COUNT(*) è forse il sottoprodotto più brutto dell'architettura di Postgres. Se hai solo bisogno di un conteggio approssimativo delle righe per un hugetable, puoi evitare un COUNT completo interrogando il raccoglitore di statistiche:

SELECT relname, n_live_tup FROM pg_stat_user_tables;

Il risultato è accurato dopo un'ANALISI e sarà progressivamente errato man mano che le righe vengono modificate. Non utilizzarlo se desideri conteggi accurati.

Tipo di intervallo

L'intervallo type non solo può essere utilizzato come tipo di dati di una colonna, ma può essere aggiunto e sottratto da date e timestamp valori:

-- 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;

Disattiva la convalida dei vincoli per l'inserimento collettivo

-- 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;

Esegui il dump di una tabella o di una query in un file 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 );

Utilizza più tipi di dati nativi nella progettazione dello schema

Postgres viene fornito con molti tipi di dati integrati. La rappresentazione dei dati di cui la tua applicazione ha bisogno utilizzando uno di questi tipi può far risparmiare molto codice dell'applicazione, velocizzare lo sviluppo e causare meno errori.

Ad esempio, se rappresenti la posizione di una persona utilizzando il tipo di datipoint e una regione di interesse come polygon , puoi verificare se la persona è nella regione semplicemente con:

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

Ecco alcuni tipi di dati Postgres interessanti e link a cui puoi trovare maggiori informazioni su di essi:

  • Tipi enum simili a C
  • Tipi geometrici:punto, riquadro, segmento di linea, linea, percorso, poligono, cerchio
  • Indirizzi IPv4, IPv6 e MAC
  • Tipi di intervallo:intervalli di numeri interi, di data e data e ora
  • Array che possono contenere valori di qualsiasi tipo
  • UUID – se hai bisogno di usare gli UUID, o hai solo bisogno di lavorare con numeri interi casuali a 129 byte, considera l'utilizzo di uuid digitare e il uuid-oscp estensione per la memorizzazione, la generazione e la formattazione di UUID
  • Intervalli di data e ora utilizzando il tipo INTERVAL
  • e ovviamente i sempre popolari JSON e JSONB

Estensioni raggruppate

La maggior parte delle installazioni di Postgres include una serie di "estensioni" standard. Le estensioni sono componenti installabili (e disinstallabili in modo pulito) che forniscono funzionalità non incluse nel core. Possono essere installati per database.

Alcuni di questi sono piuttosto utili e vale la pena dedicare un po' di tempo a conoscerli:

  • pg_stat_statements – statistiche sull'esecuzione di ogni query SQL
  • auto_explain – registra il piano di esecuzione delle query (lento)
  • postgres_fdw,dblink andfile_fdw – modi per accedere ad altre origini dati (come server Postgres remoti, server MySQL, file sul file system del server) come le normali tabelle
  • citext – un tipo di dati "testo senza distinzione tra maiuscole e minuscole", più efficiente di lower()-ing ovunque
  • hstore:un tipo di dati chiave-valore
  • pgcrypto – funzioni di hashing SHA, crittografia