sql >> Database >  >> RDS >> PostgreSQL

Tips en trucs van Postgres

Werk je dagelijks met Postgres? Applicatiecode schrijven die met Postgres praat? Bekijk dan de hapklare SQL-fragmenten hieronder die je kunnen helpen sneller te werken!

Meerdere rijen in één instructie invoegen

De INSERT-instructie kan meer dan één rij in een enkele instructie invoegen:

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

Lees hier meer over wat INSERT kan doen.

Voeg een rij in en retourneer automatisch toegewezen waarden

Waarden die automatisch worden gegenereerd met DEFAULT/serial/IDENTITY-constructies kunnen worden geretourneerd door de INSERT-instructie met behulp van de RETURNING-clausule. Vanuit het perspectief van de applicatiecode wordt zo'n INSERT uitgevoerd als een SELECT die een recordset retourneert.

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

Automatisch gegenereerde primaire UUID-sleutels

UUID's worden om verschillende redenen soms gebruikt in plaats van primaire sleutels. Hier ziet u hoe u een UUID kunt gebruiken in plaats van een serienummer of 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

Invoegen indien niet aanwezig, anders bijwerken

In Postgres 9.5 en hoger kunt u upsert direct met behulp van de ON CONFLICTconstructie:

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;

Kopieer rijen van de ene tabel naar de andere

De INSERT-instructie heeft een vorm waarin de waarden kunnen worden geleverd door een SELECT-instructie. Gebruik dit om rijen van de ene tabel naar de andere te kopiëren:

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

Als je tabellen in bulk wilt laden, bekijk dan ook de COPY-opdracht, die kan worden gebruikt om rijen uit een tekst- of CSV-bestand in te voegen.

Verwijderde informatie verwijderen en teruggeven

U kunt de RETURNING . gebruiken clausule om waarden te retourneren uit de rijen die zijn verwijderd met behulp van een bulk-delete-instructie:

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

Verplaats rijen van de ene tafel naar de andere

U kunt rijen van de ene tabel naar de andere verplaatsen in een enkele instructie, door CTE's te gebruiken met 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;

Rijen bijwerken en bijgewerkte waarden retourneren

De clausule RETURNING kan ook in UPDATEs worden gebruikt. Merk op dat alleen de nieuwe waarden van de bijgewerkte kolommen op deze manier kunnen worden geretourneerd.

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

Als je de originele waarde van de bijgewerkte kolommen nodig hebt:het is mogelijk via een self-join, maar er is geen garantie voor atomiciteit. Probeer een SELECT .. FOR UPDATE . te gebruiken in plaats daarvan.

Update een paar willekeurige rijen en retourneer de bijgewerkte

Hier leest u hoe u een paar willekeurige rijen uit een tabel kunt kiezen, ze kunt bijwerken en de bijgewerkte kunt retourneren, allemaal in één keer:

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;

Maak een tafel net als een andere tafel

Gebruik de CREATE TABLE .. LIKE constructie om een ​​tabel te maken met dezelfde kolommen als een andere:

CREATE TABLE to_be_audited (LIKE purchases);

Standaard creëert dit geen vergelijkbare indexen, beperkingen, standaardinstellingen enz. Vraag Postgres expliciet om dit te doen:

CREATE TABLE to_be_audited (LIKE purchases INCLUDING ALL);

Bekijk hier de volledige syntaxis.

Een willekeurige set rijen extraheren in een andere tabel

Sinds Postgres 9.5 is de functie TABLESAMPLE beschikbaar om een ​​voorbeeld van rijen uit een tabel te extraheren. Er zijn momenteel twee bemonsteringsmethoden, en bernoulli is meestal degene die je wilt:

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

Het systeem tablesampling-methode is sneller, maar retourneert geen uniforme distributie. Zie de documenten voor meer info.

Een tabel maken op basis van een selectiequery

U kunt de CREATE TABLE .. AS-constructie gebruiken om de tabel te maken en deze in één keer te vullen vanuit een SELECT-query:

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

De resulterende tabel is als een gerealiseerde weergave zonder een daaraan gekoppelde query. Lees meer over MAAK TAFEL .. ALS hier.

Maak niet-gelogde tabellen

Niet ingelogd tabellen worden niet ondersteund door WAL-records. Dit betekent dat updates en verwijderingen van dergelijke tabellen sneller zijn, maar ze zijn niet crashtolerant en kunnen niet worden gerepliceerd.

CREATE UNLOGGED TABLE report_20200817 (LIKE report_v3);

Tijdelijke tabellen maken

Tijdelijk tabellen zijn impliciet niet-gelogde tabellen, met een kortere levensduur. Ze vernietigen zichzelf automatisch aan het einde van een sessie (standaard) of aan het einde van de transactie.

Gegevens in tijdelijke tabellen kunnen niet tussen sessies worden gedeeld. Meerdere sessies kunnen tijdelijke tabellen maken met dezelfde naam.

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

Opmerkingen toevoegen

Opmerkingen kunnen aan elk object in de database worden toegevoegd. Veel tools, waaronder pg_dump, begrijpen deze. Een nuttige opmerking kan een hoop problemen tijdens het opruimen voorkomen!

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

Adviserende sloten

Adviesvergrendelingen kunnen worden gebruikt om acties te coördineren tussen twee apps die zijn verbonden met dezelfde databank. U kunt deze functie gebruiken om bijvoorbeeld een globale, gedistribueerdemutex voor een bepaalde bewerking te implementeren. Lees er hier alles over in de documenten.

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

Aggregeren in arrays, JSON-arrays of strings

Postgres biedt geaggregeerde functies die waarden samenvoegen in een GROUP toyield-arrays, JSON-arrays of 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;

Aggregates met bestelling

Nu we het toch over het onderwerp hebben, hier is hoe u de volgorde van waarden instelt die worden doorgegeven aan de aggregatiefunctie, binnen elke groep :

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

Ja, er is een achterliggende ORDER BY-component in de functieaanroep paranthesis.Ja, de syntaxis is raar.

Array en Unnest

Gebruik de ARRAY-constructor om een ​​reeks rijen, elk met één kolom, om te zetten in een array. Het databasestuurprogramma (zoals JDBC) zou Postgres-arrays moeten kunnen toewijzen aan native arrays en is wellicht gemakkelijker om mee te werken.

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

De functie unnest doet het omgekeerde - het converteert elk item in een array naar een rij. Ze zijn het handigst bij kruisverbanden met een lijst met waarden:

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

Combineer Select-statements met Union

U kunt de UNION-constructie gebruiken om de resultaten van meerdere vergelijkbare SELECT's te combineren:

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

Gebruik CTE's om het gecombineerde resultaat verder te verwerken:

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;

Er zijn ook INTERSECT- en EXCEPT-constructies, in dezelfde geest als UNION. Lees meer over deze clausules in de documenten.

Snelle oplossingen in Select:case, coalesce en nullif

De CASE, COALESCE en NULLIF om kleine snelle "fixes" te maken voor GESELECTEERDE gegevens.CASE is als switch in C-achtige talen:

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 kan worden gebruikt om een ​​bepaalde waarde te vervangen in plaats van 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 werkt andersom, waardoor je NULL kunt gebruiken in plaats van een bepaalde waarde:

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

Willekeurige en sequentiële testgegevens genereren

Verschillende methoden om willekeurige gegevens te genereren:

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

Gebruik bernoulli tabelbemonstering om een ​​willekeurig aantal rijen uit een tabel te selecteren:

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

Gebruik generate_series om opeenvolgende waarden van gehele getallen, datums en andere incrementele ingebouwde typen te genereren:

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

Geschat aantal rijen ophalen

De verschrikkelijke prestatie van COUNT(*) is misschien wel het lelijkste bijproduct van de architectuur van Postgres. Als u alleen een geschat aantal rijen voor een enorme tabel nodig heeft, kunt u een volledige AANTAL vermijden door de statistiekverzamelaar op te vragen:

SELECT relname, n_live_tup FROM pg_stat_user_tables;

Het resultaat is nauwkeurig na een ANALYSE en zal geleidelijk onjuist worden naarmate de rijen worden gewijzigd. Gebruik dit niet als u nauwkeurige tellingen wilt.

Intervaltype

Het interval type kan niet alleen worden gebruikt als een kolomgegevenstype, maar kan worden toegevoegd aan en afgetrokken van datum en tijdstempel waarden:

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

Beperkingsvalidatie uitschakelen voor bulksgewijs invoegen

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

Dump een tabel of query naar een CSV-bestand

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

Gebruik meer native datatypes in uw schemaontwerp

Postgres wordt geleverd met veel ingebouwde gegevenstypen. Als u de gegevens weergeeft die uw toepassing nodig heeft met een van deze typen, kunt u veel toepassingscode besparen, uw ontwikkeling sneller maken en minder fouten veroorzaken.

Als u bijvoorbeeld de locatie van een persoon vertegenwoordigt met behulp van het gegevenstypepoint en een interessegebied als een polygon , u kunt eenvoudig controleren of de persoon in de regio is met:

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

Hier zijn enkele interessante Postgres-gegevenstypen en links waar u meer informatie over kunt vinden:

  • C-achtige opsommingstypen
  • Geometrische typen – punt, kader, lijnsegment, lijn, pad, veelhoek, cirkel
  • IPv4-, IPv6- en MAC-adressen
  • Bereiktypes – gehele getallen, datum- en tijdstempelbereiken
  • Arrays die waarden van elk type kunnen bevatten
  • UUID – als u UUID's moet gebruiken, of als u alleen met willekeurige gehele getallen van 129 bytes wilt werken, overweeg dan om de uuid te gebruiken type en de uuid-oscp extensie voor het opslaan, genereren en formatteren van UUID's
  • Datum- en tijdsintervallen met het INTERVAL-type
  • en natuurlijk de immer populaire JSON en JSONB

Gebundelde extensies

De meeste Postgres-installaties bevatten een aantal standaard "extensies". Extensies zijn installeerbare (en netjes verwijderbare) componenten die functionaliteit bieden die niet in de kern is opgenomen. Ze kunnen per database worden geïnstalleerd.

Sommige hiervan zijn heel nuttig en het is de moeite waard om er wat tijd aan te besteden om ze te leren kennen:

  • pg_stat_statements – statistieken over de uitvoering van elke SQL-query
  • auto_explain –log het uitvoeringsplan van de query van (langzame) query's
  • postgres_fdw,dblink andfile_fdw – manieren om toegang te krijgen tot andere gegevensbronnen (zoals externe Postgres-servers, MySQL-servers, bestanden op het bestandssysteem van de server) zoals gewone tabellen
  • citext – een gegevenstype “hoofdlettergevoelige tekst”, efficiënter dan lagere()-ing overal
  • hstore – een sleutel-waardegegevenstype
  • pgcrypto –SHA hashing-functies, encryptie

  1. Schending van integriteitsbeperking:1452 Kan een onderliggende rij niet toevoegen of bijwerken:

  2. Psycopg2 Invoegen in tabel met tijdelijke aanduidingen

  3. 2 manieren om Word Wrap in SQLite in te schakelen

  4. SQL Backup Recovery Tool om beschadigde SQL-back-up te herstellen - Productbeoordeling - Een gastpost van Daniel Jones