PostgreSQL
 sql >> база данни >  >> RDS >> PostgreSQL

Съвети и трикове за Postgres

Работите ли с Postgre ежедневно? Напишете код на приложението, който говори с Postgres? След това разгледайте SQL фрагментите по-долу, които могат да ви помогнат да работите по-бързо!

Вмъкване на няколко реда в едно изявление

Инструкцията INSERT може да вмъкне повече от един ред в един израз:

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

Прочетете повече за това какво може да прави INSERT тук.

Вмъкване на ред и връщане на автоматично присвоени стойности

Стойности, генерирани автоматично с конструкции DEFAULT/serial/IDENTITY, могат да бъдат върнати от оператора INSERT с помощта на клаузата RETURNING. От гледна точка на кода на приложението, такъв INSERT се изпълнява като SELECT, който връща 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

Автоматично генерирани първични UUID ключове

UUID понякога се използват вместо първични ключове по различни причини. Ето как можете да използвате UUID вместо сериен номер или 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

Вмъкнете, ако не съществува, актуализирайте в противен случай

В Postgres 9.5 и по-нови, можете да пренастройвате директно с помощта на конструкцията ON CONFLICT:

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;

Копиране на редове от една таблица в друга

Инструкцията INSERT има форма, в която стойностите могат да бъдат предоставени от оператор SELECT. Използвайте това, за да копирате редове от една таблица в друга:

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

Ако търсите групово зареждане на таблици, вижте и командата COPY, която може да се използва за вмъкване на редове от текстов или CSV файл.

Изтриване и връщане на изтрита информация

Можете да използвате RETURNING клауза за връщане на стойности от редовете, които са били изтрити с помощта на израз за групово изтриване:

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

Преместване на редове от една таблица в друга

Можете да премествате редове от една таблица в друга в един израз, като използвате CTEs с 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;

Актуализиране на редове и връщане на актуализирани стойности

Клаузата RETURNING може да се използва и в UPDATE. Имайте предвид, че само новите стойности на актуализираните колони могат да бъдат върнати по този начин.

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

Ако имате нужда от оригиналната стойност на актуализираните колони:това е възможно чрез самостоятелно присъединяване, но няма гаранция за атомарност. Опитайте да използвате SELECT .. FOR UPDATE вместо това.

Актуализиране на няколко произволни реда и връщане на актуализираните

Ето как можете да изберете няколко произволни реда от таблица, да ги актуализирате и да върнете актуализираните, всичко наведнъж:

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;

Създайте таблица точно като друга таблица

Използвайте конструкцията CREATE TABLE .. LIKE, за да създадете таблица със същите колони като друга:

CREATE TABLE to_be_audited (LIKE purchases);

По подразбиране това не създава подобни индекси, ограничения, настройки по подразбиране и т.н. За да направите това, попитайте Postgres изрично:

CREATE TABLE to_be_audited (LIKE purchases INCLUDING ALL);

Вижте пълния синтаксис тук.

Извличане на произволен набор от редове в друга таблица

След Postgres 9.5, функцията TABLESAMPLE е налична за извличане на извадка от редове от таблица. Понастоящем има два метода за вземане на проби и bernoulli обикновено този, който искате:

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

Системата методът за вземане на таблици е по-бърз, но не връща равномерно разпределение. Вижте документите за повече информация.

Създаване на таблица от избрана заявка

Можете да използвате конструкцията CREATE TABLE .. AS, за да създадете таблицата и да я попълните от заявка SELECT, всичко наведнъж:

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

Получената таблица е като материализиран изглед без свързана с него заявка. Прочетете повече за CREATE TABLE .. КАТО тук.

Създаване на нерегистрирани таблици

Нерегистриран таблиците не са подкрепени от WAL записи. Това означава, че актуализациите и изтриванията на такива таблици са по-бързи, но не са устойчиви на сривове и не могат да бъдат репликирани.

CREATE UNLOGGED TABLE report_20200817 (LIKE report_v3);

Създаване на временни таблици

Временно таблиците са имплицитно нерегистрирани таблици с по-кратък живот. Те автоматично се самоунищожават в края на сесията (по подразбиране) или в края на транзакцията.

Данните във временните таблици не могат да се споделят между сесии. Няколко сесии могат да създават временни таблици със същото име.

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

Добавяне на коментари

Коментарите могат да се добавят към всеки обект в базата данни. Много инструменти, включително pg_dump, разбират това. Един полезен коментар може просто да избегне много неприятности по време на почистване!

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

Съветни заключване

Съветническите заключвания могат да се използват за координиране на действията между две приложения, свързани към същото база данни. Можете да използвате тази функция, за да приложите глобален, разпределен мутекс за определена операция, например. Прочетете всичко за това тук в документите.

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

Агрегиране в масиви, JSON масиви или низове

Postgres предоставя агрегатни функции, които обединяват стойности в GROUP toyield масиви, JSON масиви или низове:

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

Агрегира с поръчка

Докато сме по темата, ето как да зададете реда на стойностите, които се предават на агрегатната функция, във всяка група :

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

Да, има крайна клауза ORDER BY вътре в скобата за извикване на функция. Да, синтаксисът е странен.

Масив и деактивиране

Използвайте конструктора ARRAY, за да преобразувате набор от редове, всеки с една колона, в масив. Драйверът на базата данни (като JDBC) трябва да може да преобразува Postgres масиви в собствени масиви и може да е по-лесен за работа.

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

Функцията unnest прави обратното – преобразува всеки елемент от масива в стрелка. Те са най-полезни при кръстосано свързване със списък със стойности:

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

Комбиниране на избрани изявления със съюз

Можете да използвате конструкцията UNION, за да комбинирате резултатите от множество подобни SELECT:

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

Използвайте CTE за по-нататъшна обработка на комбинирания резултат:

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;

Съществуват и конструкции INTERSECT и EXCEPT, в същия дух като UNION. Прочетете повече за тези клаузи в документите.

Бързи корекции в Select:case, coalesce и nullif

CASE, COALESCE и NULLIF, за да направите малки бързи „поправки“ за ИЗБРАНИ данни. CASE е като превключвател на 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 може да се използва за заместване на определена стойност вместо 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 работи по друг начин, като ви позволява да използвате NULL вместо определена стойност:

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

Генериране на произволни и последователни тестови данни

Различни методи за генериране на произволни данни:

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

Използвайте bernoulli извадка от таблица, за да изберете произволен брой редове от таблица:

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

Използвайте generate_series за генериране на последователни стойности на цели числа, дати и други нарастващи вградени типове:

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

Вземете приблизителен брой редове

Ужасното представяне на COUNT(*) е може би най-грозният страничен продукт на архитектурата на Postgres. Ако имате нужда само от приблизителен брой редове за огромна таблица, можете да избегнете пълен COUNT, като попитате колектора на статистически данни:

SELECT relname, n_live_tup FROM pg_stat_user_tables;

Резултатът е точен след АНАЛИЗ и ще бъде постепенно неправилен, когато редовете се променят. Не използвайте това, ако искате точни преброявания.

Тип на интервала

интервалът тип може не само да се използва като тип данни в колона, но може да се добавя и изважда от дата и клеймо за време стойности:

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

Изключване на валидирането на ограничения за групово вмъкване

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

Изхвърлете таблица или заявка към 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 );

Използвайте повече естествени типове данни във вашия дизайн на схема

Postgres идва с много вградени типове данни. Представянето на данните, от които вашето приложение се нуждае, с помощта на един от тези типове може да спести много код на приложението, да направи вашата разработка по-бърза и да доведе до по-малко грешки.

Например, ако представяте местоположението на човек с помощта на тип данниpoint и регион от интерес като polygon , можете да проверите дали лицето е в региона просто с:

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

Ето някои интересни типове данни на Postgres и връзки към мястото, където можете да намерите повече информация за тях:

  • C-подобни типове enum
  • Геометрични типове – точка, поле, сегмент, линия, път, многоъгълник, кръг
  • IPv4, IPv6 и MAC адреси
  • Типове на диапазони – диапазони с цели числа, дата и време
  • Масиви, които могат да съдържат стойности от всякакъв тип
  • UUID – ако трябва да използвате UUID или просто трябва да работите със 129-байтови произволни цели числа, помислете за използването на uuid type и uuid-oscp разширение за съхранение, генериране и форматиране на UUID
  • Интервали за дата и време, използващи типа INTERVAL
  • и разбира се все по-популярните JSON и JSONB

Пакетни разширения

Повечето инсталации на Postgres включват куп стандартни „разширения“. Разширенията са компоненти за инсталиране (и безпроблемно деинсталиране), които осигуряват функционалност, които не са включени в ядрото. Те могат да бъдат инсталирани на база данни.

Някои от тях са доста полезни и си струва да отделите известно време, за да ги опознаете:

  • pg_stat_statements – статистика по отношение на изпълнението на всяка SQL заявка
  • auto_explain – регистриране на плана за изпълнение на заявката на (бавни) заявки
  • postgres_fdw,dblink andfile_fdw – начини за достъп до други източници на данни (като отдалечени Postgres сървъри, MySQL сървъри, файлове във файловата система на сървъра) като обикновени таблици
  • citext – тип данни „нечувствителен към главни и малки букви“, по-ефективен от low()-ing навсякъде
  • hstore – тип данни ключ-стойност
  • pgcrypto – SHA хеширащи функции, криптиране

  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. PostgreSQL сървърът няма да се изключи на Lion (Mac OS 10.7)

  2. Събирайте рекурсивни JSON ключове в Postgres

  3. postgresql мигрира JSON към JSONB

  4. Управление на друг PostgreSQL Commitfest

  5. Как да вмъкнете и изтриете данни в PostgreSQL