sql >> Databáze >  >> RDS >> PostgreSQL

Postgres Tipy A Triky

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 a uuid-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í

  1. Uživatel schématu Oracle nemůže vytvořit tabulku v proceduře

  2. Rozdělení řetězců:Nyní s menším množstvím T-SQL

  3. Hledání posledního indexu řetězce v Oracle

  4. Plánování kapacit pomocí údajů o výkonu