sql >> Databasteknik >  >> RDS >> PostgreSQL

Postgres tips och tricks

Arbetar du med Postgres till vardags? Skriva ansökningskod som talar med Postgres? Kolla sedan in de lite stora SQL-snuttarna nedan som kan hjälpa dig att arbeta snabbare!

Infoga flera rader i ett påstående

INSERT-satsen kan infoga mer än en rad i en enda sats:

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

Läs mer om vad INSERT kan göra här.

Infoga en rad och returnera automatiskt tilldelade värden

Värden som genereras automatiskt med DEFAULT/serial/IDENTITY-konstruktioner kan returneras av INSERT-satsen med hjälp av RETURNING-satsen. Ur applikationskodperspektivet exekveras en sådan INSERT som en SELECT som returnerar 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

Autogenererade UUID-primärnycklar

UUID används ibland istället för primärnycklar av olika anledningar. Här ishow kan du använda ett UUID istället för ett serienummer eller IDENTITET:

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

Infoga om det inte finns, uppdatera annars

I Postgres 9.5 och senare kan du upsert direkt med hjälp av ON CONFLICTconstruct:

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;

Kopiera rader från en tabell till en annan

INSERT-satsen har en form där värdena kan tillhandahållas av en SELECT-sats. Använd detta för att kopiera rader från en tabell till en annan:

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

Om du funderar på att massladda tabeller, kolla också in kommandot COPY, som kan användas för att infoga rader från en text- eller CSV-fil.

Ta bort och returnera raderad information

Du kan använda RETURNING sats för att returnera värden från raderna som raderades med hjälp av en bulk-delete-sats:

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

Flytta rader från ett bord till ett annat

Du kan flytta rader från en tabell till en annan i en enda sats, genom att använda CTEs med 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;

Uppdatera rader och returnera uppdaterade värden

RETURNING-satsen kan också användas i UPPDATERINGAR. Observera att endast de nya värdena för de uppdaterade kolumnerna kan returneras på detta sätt.

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

Om du behöver det ursprungliga värdet för de uppdaterade kolumnerna:det är möjligt genom att gå med själv, men det finns ingen garanti för atomicitet. Prova att använda en SELECT .. FOR UPDATE istället.

Uppdatera några slumpmässiga rader och returnera de uppdaterade

Så här kan du välja några slumpmässiga rader från en tabell, uppdatera dem och returnera de uppdaterade, allt på en gång:

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;

Skapa ett bord precis som ett annat bord

Använd konstruktionen CREATE TABLE .. LIKE för att skapa en tabell med samma kolumner som en annan:

CREATE TABLE to_be_audited (LIKE purchases);

Som standard skapar detta inte liknande index, begränsningar, standardinställningar etc. För att göra det, fråga Postgres uttryckligen:

CREATE TABLE to_be_audited (LIKE purchases INCLUDING ALL);

Se hela syntaxen här.

Extrahera en slumpmässig uppsättning rader till en annan tabell

Sedan Postgres 9.5 är TABLESAMPLE-funktionen tillgänglig för att extrahera ett urval av rader från en tabell. Det finns två samplingsmetoder för närvarande, och bernoulli är vanligtvis den du vill ha:

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

systemet tabellsamplingsmetoden är snabbare, men returnerar inte en enhetlig fördelning. Se dokumenten för mer information.

Skapa en tabell från en utvald fråga

Du kan använda konstruktionen CREATE TABLE .. AS för att skapa tabellen och fylla den från en SELECT-fråga, allt på en gång:

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

Den resulterande tabellen är som en materialiserad vy utan en fråga kopplad till den. Läs mer om CREATE TABLE .. AS här.

Skapa ologgade tabeller

Ologgad tabeller stöds inte av WAL-poster. Detta innebär att uppdateringar och raderingar till sådana tabeller är snabbare, men de är inte kraschtoleranta och kan inte replikeras.

CREATE UNLOGGED TABLE report_20200817 (LIKE report_v3);

Skapa tillfälliga tabeller

Tillfälligt tabeller är implicit ologgade tabeller, med en kortare livslängd. De förstör automatiskt i slutet av en session (standard) eller i slutet av transaktionen.

Data i tillfälliga tabeller kan inte delas mellan sessioner. Flera sessioner kan skapa tillfälliga tabeller med samma namn.

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

Lägg till kommentarer

Kommentarer kan läggas till alla objekt i databasen. Många verktyg, inklusive pg_dump, förstår dessa. En användbar kommentar kanske bara undviker massor av besvärande städning!

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

Rådgivningslås

Rådgivande lås kan användas för att samordna åtgärder mellan två appar som är anslutna till samma databas. Du kan använda den här funktionen för att implementera en global, distribuerad mutex för en viss operation, till exempel. Läs allt om det här i 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

Aggregera till arrayer, JSON-arrayer eller strängar

Postgres tillhandahåller aggregerade funktioner som sammanfogar värden i en GROUP toyield-arrayer, JSON-arrayer eller strängar:

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

Aggregerat med beställning

Medan vi är inne på ämnet, så här ställer du in ordningen på värden som skickas till den aggregerade funktionen inom varje grupp :

-- 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, det finns en efterföljande ORDER BY-sats inuti funktionsanropsparanthesis. Ja, syntaxen är konstig.

Array och Unnest

Använd ARRAY-konstruktorn för att konvertera en uppsättning rader, var och en med en kolumn, till en array. Databasdrivrutinen (som JDBC) bör kunna mappa Postgres-arrayer till inhemska arrayer och kan vara lättare att arbeta med.

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

Funktionen unnest gör det omvända – den konverterar varje objekt i en array till pil. De är mest användbara vid korskoppling med en lista med värden:

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

Kombinera valda uttalanden med Union

Du kan använda UNION-konstruktionen för att kombinera resultaten från flera liknande SELECT:

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

Använd CTE för att ytterligare bearbeta det kombinerade resultatet:

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;

Det finns också INTERSECT- och EXCEPT-konstruktioner, i samma veva som UNION. Läs mer om dessa klausuler i dokumenten.

Snabbkorrigeringar i Select:case, coalesce och nullif

CASE, COALESCE och NULLIF för att göra små snabba "fixar" för VALD data.CASE är som switch på C-liknande språk:

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 användas för att ersätta ett visst värde istället för 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 fungerar på andra sätt, låter dig använda NULL istället för ett visst värde:

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

Generera slumpmässiga och sekventiella testdata

Olika metoder för att generera slumpmässiga data:

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

Använd bernoulli tabellsampling för att välja ett slumpmässigt antal rader från en tabell:

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

Använd generate_series för att generera sekventiella värden av heltal, datum och andra inkrementerbara inbyggda typer:

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

Få ungefärligt antal rader

COUNT(*)s hemska prestanda är kanske den fulaste biprodukten av Postgres arkitektur. Om du bara behöver ett ungefärligt antal rader för en enorm tabell kan du undvika ett helt ANTAL genom att fråga statistiksamlaren:

SELECT relname, n_live_tup FROM pg_stat_user_tables;

Resultatet är korrekt efter en ANALYS och kommer att bli gradvis felaktigt när raderna ändras. Använd inte detta om du vill ha korrekta räkningar.

Intervalltyp

intervallet typ kan inte bara användas som kolumndatatyp, utan kan läggas till och subtraheras från datum och tidsstämpel värden:

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

Stäng av begränsningsvalidering för bulkinfogning

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

Dumpa en tabell eller fråga till en CSV-fil

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

Använd fler inbyggda datatyper i din schemadesign

Postgres kommer med många inbyggda datatyper. Att representera de data som din applikation behöver med hjälp av en av dessa typer kan spara massor av applikationskod, göra din utveckling snabbare och resultera i färre fel.

Till exempel, om du representerar en persons plats med hjälp av datatypenpoint och en region av intresse som en polygon , kan du kontrollera om personen är i regionen helt enkelt med:

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

Här är några intressanta Postgres-datatyper och länkar till var du kan hitta mer information om dem:

  • C-liknande enumtyper
  • Geometriska typer – punkt, ruta, linjesegment, linje, bana, polygon, cirkel
  • IPv4-, IPv6- och MAC-adresser
  • Omfångstyper – heltal, datum och tidsstämpelintervall
  • Arrayer som kan innehålla värden av vilken typ som helst
  • UUID – om du behöver använda UUID, eller bara behöver arbeta med 129-byte slumpmässiga heltal, överväg att använda uuid typ och uuid-oscp tillägg för att lagra, generera och formatera UUID
  • Datum och tidsintervall med typen INTERVAL
  • och naturligtvis den ständigt populära JSON och JSONB

Bundlade tillägg

De flesta Postgres-installationer inkluderar ett gäng vanliga "tillägg". Tillägg är installerbara (och rent avinstallerbara) komponenter som tillhandahåller funktionalitet som inte ingår i kärnan. De kan installeras per databas.

Vissa av dessa är ganska användbara, och det är värt att lägga lite tid på att lära känna dem:

  • pg_stat_statements – statistik om exekvering av varje SQL-fråga
  • auto_explain – logga exekveringsplanen för (långsamma) frågor
  • postgres_fdw,dblink andfile_fdw – sätt att komma åt andra datakällor (som fjärranslutna Postgres-servrar, MySQL-servrar, filer på serverns filsystem) som vanliga tabeller
  • citext – en datatyp som är "skiftlägesokänslig text", effektivare än lägre()-ing över hela platsen
  • hstore – en nyckel-värdedatatyp
  • pgcrypto –SHA-hashningsfunktioner, kryptering

  1. CX_Oracle - importera data från Oracle till Pandas dataram

  2. Lista alla tabeller i postgresql informationsschema

  3. Vad är INTE logisk operatör i SQL Server - SQL Server / TSQL Tutorial Del 121

  4. Hur man aktiverar/inaktiverar CHECK-begränsningar i SQLite