PostgreSQL
 sql >> Datenbank >  >> RDS >> PostgreSQL

Postgres-Tipps und Tricks

Arbeiten Sie täglich mit Postgres? Anwendungscode schreiben, der mit Postgres spricht? Dann sehen Sie sich die mundgerechten SQL-Schnipsel unten an, die Ihnen helfen können, schneller zu arbeiten!

Fügen Sie mehrere Zeilen in einer Anweisung ein

Die INSERT-Anweisung kann mehr als eine Zeile in eine einzelne Anweisung einfügen:

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

Lesen Sie hier mehr darüber, was INSERT tun kann.

Eine Zeile einfügen und automatisch zugewiesene Werte zurückgeben

Mit DEFAULT/serial/IDENTITY-Konstrukten automatisch generierte Werte können von der INSERT-Anweisung unter Verwendung der RETURNING-Klausel zurückgegeben werden. Aus Sicht des Anwendungscodes wird ein solches INSERT wie ein SELECT ausgeführt, das ein Recordset zurückgibt.

-- 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 generierte UUID-Primärschlüssel

UUIDs werden manchmal aus verschiedenen Gründen anstelle von Primärschlüsseln verwendet. So können Sie anstelle einer Seriennummer oder IDENTITÄT eine UUID verwenden:

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

Einfügen, falls nicht vorhanden, andernfalls aktualisieren

In Postgres 9.5 und höher können Sie upsert direkt mit dem ON CONFLICT-Konstrukt:

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;

Zeilen von einer Tabelle in eine andere kopieren

Die INSERT-Anweisung hat eine Form, in der die Werte durch eine SELECT-Anweisung bereitgestellt werden können. Verwenden Sie dies, um Zeilen von einer Tabelle in eine andere zu kopieren:

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

Wenn Sie Tabellen massenweise laden möchten, sehen Sie sich auch den COPY-Befehl an, der zum Einfügen von Zeilen aus einer Text- oder CSV-Datei verwendet werden kann.

Löschen und Zurückgeben gelöschter Informationen

Sie können den RETURNING verwenden -Klausel, um Werte aus den Zeilen zurückzugeben, die mit einer Bulk-Delete-Anweisung gelöscht wurden:

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

Zeilen von einer Tabelle in eine andere verschieben

Sie können Zeilen in einer einzigen Anweisung von einer Tabelle in eine andere verschieben, indem Sie CTEs mit DELETE .. RETURNING verwenden :

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

Zeilen aktualisieren und aktualisierte Werte zurückgeben

Die RETURNING-Klausel kann auch in UPDATEs verwendet werden. Beachten Sie, dass auf diese Weise nur die neuen Werte der aktualisierten Spalten zurückgegeben werden können.

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

Wenn Sie den ursprünglichen Wert der aktualisierten Spalten benötigen:Dies ist über einen Self-Join möglich, es gibt jedoch keine Garantie für Atomarität. Versuchen Sie es mit SELECT .. FOR UPDATE stattdessen.

Aktualisiere ein paar zufällige Zeilen und gib die aktualisierten zurück

So können Sie ein paar zufällige Zeilen aus einer Tabelle auswählen, sie aktualisieren und die aktualisierten zurückgeben, alles auf einmal:

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;

Erstellen Sie eine Tabelle wie eine andere Tabelle

Verwenden Sie das Konstrukt CREATE TABLE .. LIKE, um eine Tabelle mit denselben Spalten wie eine andere zu erstellen:

CREATE TABLE to_be_audited (LIKE purchases);

Standardmäßig werden dadurch keine ähnlichen Indizes, Einschränkungen, Standardeinstellungen usw. erstellt. Um dies zu tun, fragen Sie Postgres explizit:

CREATE TABLE to_be_audited (LIKE purchases INCLUDING ALL);

Die vollständige Syntax finden Sie hier.

Extrahiere einen zufälligen Satz von Zeilen in eine andere Tabelle

Seit Postgres 9.5 ist die TABLESAMPLE-Funktion verfügbar, um eine Stichprobe von Zeilen aus einer Tabelle zu extrahieren. Derzeit gibt es zwei Stichprobenverfahren und Bernoulli Dies ist normalerweise die gewünschte:

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

Das System Die Tablesampling-Methode ist schneller, gibt jedoch keine einheitliche Verteilung zurück. Weitere Informationen finden Sie in der Dokumentation.

Erstellen Sie eine Tabelle aus einer Auswahlabfrage

Sie können das Konstrukt CREATE TABLE .. AS verwenden, um die Tabelle zu erstellen und mit einer SELECT-Abfrage zu füllen, alles in einem Rutsch:

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

Die resultierende Tabelle ist wie eine materialisierte Ansicht ohne zugeordnete Abfrage. Lesen Sie hier mehr über CREATE TABLE .. AS.

Nicht protokollierte Tabellen erstellen

Nicht angemeldet Tabellen werden nicht durch WAL-Einträge gestützt. Das bedeutet, dass Aktualisierungen und Löschungen solcher Tabellen schneller sind, aber sie sind nicht absturztolerant und können nicht repliziert werden.

CREATE UNLOGGED TABLE report_20200817 (LIKE report_v3);

Temporäre Tabellen erstellen

Vorübergehend Tabellen sind implizit nicht protokollierte Tabellen mit einer kürzeren Lebensdauer. Sie zerstören sich automatisch am Ende einer Sitzung (Standardeinstellung) oder am Ende der Transaktion.

Daten in temporären Tabellen können nicht sitzungsübergreifend geteilt werden. Mehrere Sitzungen können temporäre Tabellen mit demselben Namen erstellen.

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

Kommentare hinzufügen

Kommentare können zu jedem Objekt in der Datenbank hinzugefügt werden. Viele Tools, einschließlich pg_dump, verstehen diese. Ein nützlicher Kommentar kann eine Menge Ärger während der Bereinigung vermeiden!

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

Beratungssperren

Hinweissperren können verwendet werden, um Aktionen zwischen zwei Apps zu koordinieren, die mit derselben verbunden sind Datenbank. Sie können dieses Feature verwenden, um beispielsweise einen globalen, verteilten Mutex für eine bestimmte Operation zu implementieren. Lesen Sie alles darüber hier in den Dokumenten.

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

In Arrays, JSON-Arrays oder Strings aggregieren

Postgres bietet Aggregatfunktionen, die Werte in einer GROUP verketten toyield-Arrays, JSON-Arrays oder 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;

Aggregate mit Bestellung

Wo wir gerade beim Thema sind, hier erfahren Sie, wie Sie die Reihenfolge der Werte festlegen, die innerhalb jeder Gruppe an die Aggregatfunktion übergeben werden :

-- 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, es gibt eine nachgestellte ORDER BY-Klausel in der Klammer des Funktionsaufrufs. Ja, die Syntax ist seltsam.

Array und Entschachtelung

Verwenden Sie den ARRAY-Konstruktor, um eine Reihe von Zeilen mit jeweils einer Spalte in ein Array umzuwandeln. Der Datenbanktreiber (wie JDBC) sollte in der Lage sein, Postgres-Arrays nativen Arrays zuzuordnen, und könnte einfacher zu handhaben sein.

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

Die Unnest-Funktion macht das Gegenteil – sie konvertiert jedes Element in einem Array in eine Zeile. Sie sind am nützlichsten beim Querverknüpfen mit einer Liste von Werten:

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

Ausgewählte Anweisungen mit Union kombinieren

Sie können das UNION-Konstrukt verwenden, um die Ergebnisse mehrerer ähnlicher SELECTs zu kombinieren:

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

Verwenden Sie CTEs, um das kombinierte Ergebnis weiter zu verarbeiten:

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;

Es gibt auch INTERSECT- und EXCEPT-Konstrukte, ähnlich wie UNION. Lesen Sie mehr über diese Klauseln in den Dokumenten.

Quick Fixes in Select:Case, Coalesce und Nullif

CASE, COALESCE und NULLIF, um kleine schnelle „Korrekturen“ für AUSGEWÄHLTE Daten vorzunehmen. CASE ist wie switch in C-ähnlichen Sprachen:

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 kann verwendet werden, um NULL durch einen bestimmten Wert zu ersetzen.

-- 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 funktioniert umgekehrt, sodass Sie NULL anstelle eines bestimmten Werts verwenden können:

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

Generiere zufällige und sequentielle Testdaten

Verschiedene Methoden zur Generierung von Zufallsdaten:

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

Verwenden Sie bernoulli Tabellen-Sampling, um eine zufällige Anzahl von Zeilen aus einer Tabelle auszuwählen:

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

Verwenden Sie generate_series um sequentielle Werte von ganzen Zahlen, Datumsangaben und anderen inkrementierbaren eingebauten Typen zu generieren:

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

Ungefähre Zeilenanzahl erhalten

Die schreckliche Leistung von COUNT(*) ist vielleicht das hässlichste Nebenprodukt der Architektur von Postgres. Wenn Sie nur eine ungefähre Zeilenanzahl für eine Hugetable benötigen, können Sie eine vollständige COUNT vermeiden, indem Sie den Statistiksammler abfragen:

SELECT relname, n_live_tup FROM pg_stat_user_tables;

Das Ergebnis ist nach einer ANALYZE genau und wird zunehmend falsch, wenn die Zeilen geändert werden. Verwenden Sie dies nicht, wenn Sie genaue Zählungen wünschen.

Intervalltyp

Das Intervall type kann nicht nur als Spaltendatentyp verwendet werden, sondern kann zu date addiert und von ihm subtrahiert werden und Zeitstempel Werte:

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

Beschränkungsvalidierung für Masseneinfügung deaktivieren

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

Speichere eine Tabelle oder Abfrage in einer CSV-Datei

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

Verwenden Sie mehr native Datentypen in Ihrem Schemadesign

Postgres enthält viele integrierte Datentypen. Die Darstellung der Daten, die Ihre Anwendung benötigt, mit einem dieser Typen kann eine Menge Anwendungscode einsparen, Ihre Entwicklung beschleunigen und zu weniger Fehlern führen.

Wenn Sie beispielsweise den Standort einer Person mit dem Datentyppoint darstellen und eine Region of Interest als polygon , können Sie einfach prüfen, ob die Person in der Region ist:

-- 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 sind einige interessante Postgres-Datentypen und Links, wo Sie weitere Informationen darüber finden können:

  • C-ähnliche Aufzählungstypen
  • Geometrische Typen – Punkt, Quader, Liniensegment, Linie, Pfad, Polygon, Kreis
  • IPv4-, IPv6- und MAC-Adressen
  • Bereichstypen – Integer-, Datums- und Zeitstempelbereiche
  • Arrays, die Werte beliebigen Typs enthalten können
  • UUID – Wenn Sie UUIDs verwenden oder nur mit zufälligen 129-Byte-Integern arbeiten müssen, sollten Sie die Verwendung der uuid in Erwägung ziehen type und den uuid-oscp Erweiterung zum Speichern, Generieren und Formatieren von UUIDs
  • Datums- und Zeitintervalle mit dem Typ INTERVAL
  • und natürlich die allseits beliebten JSON und JSONB

Gebündelte Erweiterungen

Die meisten Postgres-Installationen enthalten eine Reihe von Standard-„Erweiterungen“. Erweiterungen sind installierbare (und sauber deinstallierbare) Komponenten, die Funktionen bereitstellen, die nicht im Kern enthalten sind. Sie können pro Datenbank installiert werden.

Einige davon sind sehr nützlich, und es lohnt sich, etwas Zeit damit zu verbringen, sie kennenzulernen:

  • pg_stat_statements – Statistiken über die Ausführung jeder SQL-Abfrage
  • auto_explain – den Abfrageausführungsplan von (langsamen) Abfragen protokollieren
  • postgres_fdw, dblink und file_fdw – Möglichkeiten für den Zugriff auf andere Datenquellen (wie entfernte Postgres-Server, MySQL-Server, Dateien im Dateisystem des Servers) wie reguläre Tabellen
  • citext – ein „case-insensitive text“-Datentyp, effizienter als überall low()-ing
  • hstore – ein Schlüsselwert-Datentyp
  • pgcrypto –SHA-Hashing-Funktionen, Verschlüsselung