Pracujete s Postgresem denně? Napsat kód aplikace, který bude mluvit s Postgres? Pak se podívejte na krátké úryvky SQL níže, které vám pomohou pracovat rychleji!
Vložit více řádků do jednoho příkazu
Příkaz INSERT může vložit více než jeden řádek do jednoho příkazu:
INSERT INTO planets (name, gravity)
VALUES ('earth', 9.8),
('mars', 3.7),
('jupiter', 23.1);
Přečtěte si více o tom, co INSERT umí.
Vložit řádek a vrátit automaticky přiřazené hodnoty
Hodnoty automaticky generované pomocí konstrukcí DEFAULT/serial/IDENTITY lze vrátit příkazem INSERT pomocí klauzule RETURNING. Z pohledu aplikačního kódu se takový INSERT provede jako SELECT, který vrátí 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
Automaticky generované primární klíče UUID
UUID se někdy z různých důvodů používají místo primárních klíčů. Zde je návod, jak můžete použít UUID místo sériového čísla nebo 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
Vložit, pokud neexistuje, aktualizovat jinak
V Postgres 9.5 a novějších můžete upsovat přímo pomocí 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;
Kopírování řádků z jedné tabulky do druhé
Příkaz INSERT má formu, ve které lze hodnoty zadat příkazem SELECT. Použijte toto ke kopírování řádků z jedné tabulky do druhé:
-- 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;
Pokud hledáte hromadné načítání tabulek, podívejte se také na příkaz COPY, který lze použít k vložení řádků z textového nebo CSV souboru.
Smazat a vrátit smazané informace
Můžete použít RETURNING
klauzule pro vrácení hodnot z řádků, které byly odstraněny pomocí příkazu hromadného odstranění:
-- return the list of customers whose licenses were deleted after expiry
DELETE FROM licenses
WHERE now() > expiry_date
RETURNING customer_name;
Přesunout řádky z jedné tabulky do druhé
Řádky z jedné tabulky do druhé můžete přesouvat v jediném příkazu pomocí CTEswith 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;
Aktualizovat řádky a vrátit aktualizované hodnoty
Klauzuli RETURNING lze použít i v UPDATEs. Upozorňujeme, že tímto způsobem lze vrátit pouze nové hodnoty aktualizovaných sloupců.
-- grant random amounts of coins to eligible players
UPDATE players
SET coins = coins + (100 * random())::integer
WHERE eligible
RETURNING id, coins;
Pokud potřebujete původní hodnotu aktualizovaných sloupců:je to možné prostřednictvím vlastního spojení, ale neexistuje žádná záruka atomicity. Zkuste použít SELECT .. FOR
UPDATE
místo toho.
Aktualizovat několik náhodných řádků a vrátit ty aktualizované
Zde je návod, jak si můžete vybrat několik náhodných řádků z tabulky, aktualizovat je a vrátit aktualizované, to vše najednou:
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;
Vytvořit stůl jako jiný stůl
Pomocí konstrukce CREATE TABLE .. LIKE vytvořte tabulku se stejnými sloupci jako jiná:
CREATE TABLE to_be_audited (LIKE purchases);
Ve výchozím nastavení to nevytváří podobné indexy, omezení, výchozí hodnoty atd. Chcete-li to provést, zeptejte se výslovně Postgres:
CREATE TABLE to_be_audited (LIKE purchases INCLUDING ALL);
Úplnou syntaxi naleznete zde.
Extrahujte náhodnou sadu řádků do jiné tabulky
Od Postgres 9.5 je k dispozici funkce TABLESAMPLE pro extrahování vzorku řádků z tabulky. V současné době existují dvě metody vzorkování a bernoulli obvykle ten, který chcete:
-- 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;
systém metoda vzorkování tabulek je rychlejší, ale nevrací jednotné rozdělení. Další informace naleznete v dokumentaci.
Vytvoření tabulky z vybraného dotazu
K vytvoření tabulky a jejímu naplnění z dotazu SELECT můžete použít konstrukci CREATE TABLE .. AS, vše najednou:
CREATE TABLE to_be_audited AS
SELECT *
FROM purchases
TABLESAMPLE bernoulli(10)
WHERE transaction_date = CURRENT_DATE;
Výsledná tabulka je jako materializovaný pohled bez přidruženého dotazu. Přečtěte si více o CREATE TABLE .. JAKO zde.
Vytvořit nepřihlášené tabulky
Nepřihlášen tabulky nejsou podporovány záznamy WAL. To znamená, že aktualizace a mazání takových tabulek jsou rychlejší, ale nejsou odolné proti zhroucení a nelze je replikovat.
CREATE UNLOGGED TABLE report_20200817 (LIKE report_v3);
Vytvořit dočasné tabulky
Dočasné tabulky jsou implicitně nepřihlášené tabulky s kratší životností. Automaticky se zničí na konci relace (výchozí) nebo na konci transakce.
Data v dočasných tabulkách nelze sdílet mezi relacemi. Více relací může vytvořit dočasné tabulky se stejným názvem.
-- 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;
Přidat komentáře
Komentáře lze přidávat k libovolnému objektu v databázi. Mnoho nástrojů, včetně pg_dump, to chápe. Užitečný komentář by se mohl vyhnout spoustě problémů během čištění!
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';
Advisory Locks
Poradenské zámky lze použít ke koordinaci akcí mezi dvěma aplikacemi připojenými k stejnému databáze. Tuto funkci můžete použít například k implementaci globálního distribuovaného mutexu pro určitou operaci. Přečtěte si o tom vše zde v dokumentaci.
-- 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
Agregovat do polí, JSON polí nebo řetězců
Postgres poskytuje agregační funkce, které spojují hodnoty do GROUP toyield pole, pole JSON nebo řetězce:
-- 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;
Agregáty s objednávkou
Když už jsme u tohoto tématu, zde je návod, jak nastavit pořadí hodnot, které se předávají agregační funkci, v rámci každé skupiny :
-- 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;
Ano, v závorce volání funkce je na konci klauzule ORDER BY. Ano, syntaxe je divná.
Array a Unnest
Pomocí konstruktoru ARRAY převeďte sadu řádků, každý s jedním sloupcem, na pole. Ovladač databáze (jako JDBC) by měl být schopen mapovat pole Postgres do nativních polí a mohlo by se s ním snadněji pracovat.
-- convert rows (with 1 column each) into a 1-dimensional array
SELECT ARRAY(SELECT id FROM players WHERE lifetime_spend > 10000);
Funkce unnest to dělá obráceně – převede každou položku v poli na šipku. Nejužitečnější jsou při křížovém spojování se seznamem hodnot:
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..)
Kombinovat vybraná prohlášení s Union
Konstrukt UNION můžete použít ke kombinaci výsledků z více podobných SELECT:
SELECT name FROM weapons
UNION
SELECT name FROM tools
UNION
SELECT name FROM materials;
Použijte CTE k dalšímu zpracování kombinovaného výsledku:
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;
Existují také konstrukty INTERSECT a EXCEPT ve stejném duchu jako UNION. Přečtěte si více o těchto klauzulích v dokumentaci.
Rychlé opravy ve výběru:case, coalesce a nullif
CASE, COALESCE a NULLIF k provedení malých rychlých „oprav“ pro VYBRANÁ data. CASE je jako přepínač v jazycích podobných 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 lze použít k nahrazení určité hodnoty namísto 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 funguje opačným způsobem a umožňuje vám použít NULL místo určité hodnoty:
-- use NULL instead of '0.0.0.0'
SELECT nodename, NULLIF(ipv4, '0.0.0.0') FROM nodes;
Generování náhodných a sekvenčních testovacích dat
Různé metody generování náhodných dat:
-- 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);
Použijte bernoulli vzorkování tabulky pro výběr náhodného počtu řádků z tabulky:
-- select 15% of rows from the table, chosen randomly
SELECT *
FROM purchases
TABLESAMPLE bernoulli(15)
Použijte generate_series
ke generování sekvenčních hodnot celých čísel, dat a dalších inkrementálních vestavěných typů:
-- 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');
Zjistit přibližný počet řádků
Příšerný výkon COUNT(*)
je možná nejošklivějším vedlejším produktem architektury Postgres. Pokud potřebujete pouze přibližný počet řádků pro obrovskou tabulku, můžete se vyhnout úplnému COUNT dotazem na sběratel statistik:
SELECT relname, n_live_tup FROM pg_stat_user_tables;
Výsledek je po ANALÝZE přesný a bude postupně nesprávný, jak se budou řádky upravovat. Toto nepoužívejte, pokud chcete přesné počty.
Typ intervalu
interval typ nelze použít pouze jako datový typ sloupce, ale lze jej přidat a odečíst od datum a časové razítko hodnoty:
-- 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;
Vypnout ověřování omezení pro hromadné vkládání
-- 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;
Výpis tabulky nebo dotazu do souboru 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 );
Použití více nativních datových typů v návrhu schématu
Postgres přichází s mnoha vestavěnými datovými typy. Reprezentace dat, která vaše aplikace potřebuje, pomocí jednoho z těchto typů může ušetřit spoustu kódu aplikace, urychlit váš vývoj a vést k menšímu počtu chyb.
Pokud například zastupujete polohu osoby pomocí datového typupoint
a oblast zájmu jako polygon
, můžete zkontrolovat, zda je osoba v regionu, jednoduše pomocí:
-- the @> operator checks if the region of interest (a "polygon") contains
-- the person's location (a "point")
SELECT roi @> person_location FROM live_tracking;
Zde je několik zajímavých datových typů Postgres a odkazy, kde o nich můžete najít další informace:
- Výčtové typy podobné C
- Geometrické typy – bod, rámeček, úsečka, čára, cesta, mnohoúhelník, kruh
- Adresy IPv4, IPv6 a MAC
- Typy rozsahů – rozsahy celé číslo, datum a čas
- Pole, která mohou obsahovat hodnoty libovolného typu
- UUID – pokud potřebujete použít UUID nebo jen potřebujete pracovat se 129bajtovými náhodnými celými čísly, zvažte použití
uuid
typ auuid-oscp
rozšíření pro ukládání, generování a formátování UUID - Datum a časové intervaly pomocí typu INTERVAL
- a samozřejmě stále populární JSON a JSONB
Bundled Extensions
Většina instalací Postgresu obsahuje spoustu standardních „rozšíření“. Rozšíření jsou instalovatelné (a čistě odinstalovatelné) součásti, které poskytují funkce, které nejsou součástí jádra. Mohou být instalovány na bázi databáze.
Některé z nich jsou docela užitečné a stojí za to věnovat nějaký čas jejich seznámení:
- pg_stat_statements – statistiky týkající se provádění každého SQL dotazu
- auto_explain – protokoluje plán provádění dotazů (pomalých) dotazů
- postgres_fdw,dblink andfile_fdw – způsoby přístupu k dalším zdrojům dat (jako jsou vzdálené servery Postgres, servery MySQL, soubory v souborovém systému serveru), jako jsou běžné tabulky
- citext – datový typ „text bez rozlišení velkých a malých písmen“, efektivnější než nižší ()-ing všude na místě
- hstore – typ dat klíč–hodnota
- pgcrypto – SHA hašovací funkce, šifrování