Krótka ściągawka z PostgreSQL: Szybka ściągawka dla programisty

Szybkie odniesienie do PostgreSQL

Page content

Szybki referencja do codziennego PostgreSQL w pracy: połączenia, składnia SQL, meta-polecenia psql, wydajność, JSON, funkcje okienkowe i wiele więcej.

Jeśli chcesz zwięzłego przypomnienia SQL między bazami danych, zanim przejdziesz do specyfiki Postgresa, ta ściągawka SQL z najbardziej użytecznymi poleceniami SQL będzie przydatnym towarzyszem.

postgresql logo

Połączenia i podstawy

# Połączenie
psql -h HOST -p 5432 -U USER -d DB
psql $DATABASE_URL

# Wewnątrz psql
\conninfo             -- pokaż połączenie
\l[+]                 -- lista baz danych
\c DB                 -- połącz się z BAZĄ
\dt[+] [schema.]pat   -- lista tabel
\dv[+]                -- lista widoków
\ds[+]                -- lista sekwencji
\df[+] [pat]          -- lista funkcji
\d[S+] nazwa           -- opisz tabelę/widok/sekwencję
\dn[+]                -- lista schematów
\du[+]                -- lista ról
\timing               -- przełącz pomiar czasu wykonania
\x                    -- rozszerzony widok
\e                    -- edytuj bufor w $EDITOR
\i plik.sql           -- wykonaj plik
\copy ...             -- COPY po stronie klienta
\! polecenie_shell    -- wykonaj polecenie powłoki

Typy danych (spopularyzowane)

  • Liczbowe: smallint, integer, bigint, decimal(p,s), numeric, real, double precision, serial, bigserial
  • Tekstowe: text, varchar(n), char(n)
  • Wartości logiczne: boolean
  • Czasowe: timestamp [with/without time zone], date, time, interval
  • UUID: uuid
  • JSON: json, jsonb (zalecane)
  • Tablice (array): typ[], np. text[]
  • Sieciowe: inet, cidr, macaddr
  • Geometria: point, line, polygon itp.

Sprawdź wersję PostgreSQL

SELECT version();

Wersja serwera PostgreSQL:

pg_config --version

Wersja klienta PostgreSQL:

psql --version

DDL (Tworzenie / Modyfikacja)

-- Utwórz schemat i tabelę
CREATE SCHEMA IF NOT EXISTS app;
CREATE TABLE app.users (
  id           bigserial PRIMARY KEY,
  email        text NOT NULL UNIQUE,
  name         text,
  active       boolean NOT NULL DEFAULT true,
  created_at   timestamptz NOT NULL DEFAULT now(),
  profile      jsonb,
  tags         text[]
);

-- Modyfikacja
ALTER TABLE app.users ADD COLUMN last_login timestamptz;
ALTER TABLE app.users ALTER COLUMN name SET NOT NULL;
ALTER TABLE app.users DROP COLUMN tags;

-- Ograniczenia
ALTER TABLE app.users ADD CONSTRAINT email_lower_uk UNIQUE (lower(email));

-- Indeksy
CREATE INDEX ON app.users (email);
CREATE UNIQUE INDEX CONCURRENTLY users_email_uidx ON app.users (lower(email));
CREATE INDEX users_profile_gin ON app.users USING gin (profile);
CREATE INDEX users_created_at_idx ON app.users (created_at DESC);

DML (Wstawianie / Aktualizacja / Upsert / Usuwanie)

INSERT INTO app.users (email, name) VALUES
  ('a@x.com','A'), ('b@x.com','B')
RETURNING id;

-- Upsert (ON CONFLICT)
INSERT INTO app.users (email, name)
VALUES ('a@x.com','Alice')
ON CONFLICT (email)
DO UPDATE SET name = EXCLUDED.name, updated_at = now();

UPDATE app.users SET active = false WHERE last_login < now() - interval '1 year';
DELETE FROM app.users WHERE active = false AND last_login IS NULL;

Podstawy zapytań

SELECT * FROM app.users ORDER BY created_at DESC LIMIT 20 OFFSET 40; -- paginacja

-- Filtracja
SELECT * FROM app.users WHERE email ILIKE '%@example.%' AND active;

-- Agregacje i GROUP BY
SELECT active, count(*) AS n
FROM app.users
GROUP BY active
HAVING count(*) > 10;

-- Łączenia (Joins)
SELECT o.id, u.email, o.total
FROM app.orders o
JOIN app.users u ON u.id = o.user_id
LEFT JOIN app.discounts d ON d.id = o.discount_id;

-- DISTINCT ON (specyficzne dla Postgres)
SELECT DISTINCT ON (user_id) user_id, status, created_at
FROM app.events
ORDER BY user_id, created_at DESC;

-- CTE (Common Table Expressions)
WITH recent AS (
  SELECT * FROM app.orders WHERE created_at > now() - interval '30 days'
)
SELECT count(*) FROM recent;

-- Rekurencyjny CTE
WITH RECURSIVE t(n) AS (
  SELECT 1
  UNION ALL
  SELECT n+1 FROM t WHERE n < 10
)
SELECT sum(n) FROM t;

Funkcje okienkowe

SELECT
  user_id,
  created_at,
  sum(total)      OVER (PARTITION BY user_id ORDER BY created_at) AS running_total,
  row_number()    OVER (PARTITION BY user_id ORDER BY created_at) AS rn,
  lag(total, 1)   OVER (PARTITION BY user_id ORDER BY created_at) AS prev_total
FROM app.orders;

JSON / JSONB

-- Ekstrakcja
SELECT profile->>'company' AS company FROM app.users;
SELECT profile->'address'->>'city' FROM app.users;

-- Indeks dla zapytań jsonb
CREATE INDEX users_profile_company_gin ON app.users USING gin ((profile->>'company'));

-- Istnienie / zawieranie (containment)
SELECT * FROM app.users WHERE profile ? 'company';              -- klucz istnieje
SELECT * FROM app.users WHERE profile @> '{"role":"admin"}'; -- zawiera

-- Aktualizacja jsonb
UPDATE app.users
SET profile = jsonb_set(COALESCE(profile,'{}'::jsonb), '{prefs,theme}', '"dark"', true);

Tablice (Arrays)

-- Członkostwo i zawieranie
SELECT * FROM app.users WHERE 'vip' = ANY(tags);
SELECT * FROM app.users WHERE tags @> ARRAY['beta'];

-- Dodanie (append)
UPDATE app.users SET tags = array_distinct(tags || ARRAY['vip']);

Czas i daty

SELECT now() AT TIME ZONE 'Australia/Melbourne';
SELECT date_trunc('day', created_at) AS d, count(*)
FROM app.users GROUP BY d ORDER BY d;

-- Interwały
SELECT now() - interval '7 days';

Transakcje i blokada (Locks)

BEGIN;
UPDATE app.accounts SET balance = balance - 100 WHERE id = 1;
UPDATE app.accounts SET balance = balance + 100 WHERE id = 2;
COMMIT; -- albo ROLLBACK

-- Poziom izolacji
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;

-- Sprawdź blokady
SELECT * FROM pg_locks l JOIN pg_stat_activity a USING (pid);

Role i uprawnienia

-- Utwórz rolę/użytkownika
CREATE ROLE app_user LOGIN PASSWORD 'secret';
GRANT USAGE ON SCHEMA app TO app_user;
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA app TO app_user;
ALTER DEFAULT PRIVILEGES IN SCHEMA app GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO app_user;

Import / Eksport

-- Po stronie serwera (wymaga superużytkownika lub odpowiednich uprawnień)
COPY app.users TO '/tmp/users.csv' CSV HEADER;
COPY app.users(email,name) FROM '/tmp/users.csv' CSV HEADER;

-- Po stronie klienta (psql)
\copy app.users TO 'users.csv' CSV HEADER
\copy app.users(email,name) FROM 'users.csv' CSV HEADER

Wydajność i obserwowalność

EXPLAIN (ANALYZE, BUFFERS, VERBOSE) SELECT ...;  -- rzeczywisty czas wykonania

-- Widoki statystyk
SELECT * FROM pg_stat_user_tables;
SELECT * FROM pg_stat_statements ORDER BY total_time DESC LIMIT 20; -- wymaga rozszerzenia

-- Konserwacja
VACUUM [FULL] [VERBOSE] nazwa_tabeli;
ANALYZE nazwa_tabeli;
REINDEX TABLE nazwa_tabeli;

Dla szerszego przeglądu niezbędnych narzędzi developerskich, w tym Docker, Git i PostgreSQL, zobacz Narzędzia programisty: Kompletny przewodnik po nowoczesnych przepływach pracy.

Włączaj rozszerzenia w miarę potrzeby:

CREATE EXTENSION IF NOT EXISTS pg_stat_statements;
CREATE EXTENSION IF NOT EXISTS btree_gin;
CREATE EXTENSION IF NOT EXISTS pg_trgm; -- wyszukiwanie trigramowe

Wyszukiwanie pełnotekstowe (w skrócie)

ALTER TABLE app.docs ADD COLUMN tsv tsvector;
UPDATE app.docs SET tsv = to_tsvector('english', coalesce(title,'') || ' ' || coalesce(body,''));
CREATE INDEX docs_tsv_idx ON app.docs USING gin(tsv);
SELECT id FROM app.docs WHERE tsv @@ plainto_tsquery('english', 'quick brown fox');

Jeśli zastanawiasz się, kiedy natywne wyszukiwanie Postgres jest wystarczające, a kiedy warto uruchomić osobną stack wyszukiwania, to porównanie PostgreSQL full text search vs Elasticsearch szczegółowo omawia kompromisy.


Przydatne ustawienia psql

\pset pager off       -- wyłącz stronicowanie (pager)
\pset null '∅'
\pset format aligned  -- inne: unaligned, csv
\set ON_ERROR_STOP on
\timing on

Przydatne zapytania do katalogu (Catalog)

-- Rozmiar tabeli
SELECT relname, pg_size_pretty(pg_total_relation_size(relid)) AS size
FROM pg_catalog.pg_statio_user_tables ORDER BY pg_total_relation_size(relid) DESC;

-- Rozrost indeksu (przybliżenie)
SELECT schemaname, relname, indexrelname, idx_scan
FROM pg_stat_user_indexes ORDER BY idx_scan ASC NULLS FIRST LIMIT 20;

Kopie zapasowe / Przywracanie

# Dump logiczny
pg_dump -h HOST -U USER -d DB -F c -f db.dump        # format custom
pg_restore -h HOST -U USER -d NEW_DB -j 4 db.dump

# Zwykły SQL
pg_dump -h HOST -U USER -d DB > dump.sql
psql -h HOST -U USER -d DB -f dump.sql

Dla narzędzi GUI do zarządzania bazami danych, zobacz DBeaver vs Beekeeper - Narzędzia do zarządzania bazami danych SQL oraz Instalacja DBeaver na Linuksie - jak.

Dla przykładu z realnego zastosowania pg_dump/psql używanych równolegle z kopiami zapasowymi repozytoriów i plików, zobacz Kopia zapasowa i przywracanie serwera Gitea.


Replikacja (w ujęciu ogólnym)

  • Pliki WAL są streamowane z primarystandby
  • Kluczowe ustawienia: wal_level, max_wal_senders, hot_standby, primary_conninfo
  • Narzędzia: pg_basebackup, standby.signal (PG ≥12)

Pułapki i wskazówki

  • Zawsze używaj jsonb, a nie json, dla indeksów i operatorów.
  • Preferuj timestamptz (z uwzględnieniem strefy czasowej).
  • DISTINCT ON to złoto Postgresa dla “top-N per group”.
  • Używaj GENERATED ALWAYS AS IDENTITY zamiast serial.
  • Unikaj SELECT * w zapytaniach produkcyjnych.
  • Twórz indeksy dla selektywnych filtrów i kluczy łączących.
  • Mierz wydajność za pomocą EXPLAIN (ANALYZE) przed optymalizacją.

Przydatne funkcje specyficzne dla wersji (≥v12+)

-- Generowana identyfikacja
CREATE TABLE t (id bigINT GENERATED ALWAYS AS IDENTITY, ...);

-- UPSERT z cząstkowym indeksem
CREATE UNIQUE INDEX ON t (key) WHERE is_active;

Przydatne linki

Subskrybuj

Otrzymuj nowe wpisy o systemach, infrastrukturze i inżynierii AI.