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