SELECT
Selecting data from a table
SELECT first_name, last_name, email
FROM employees
WHERE department_id = 10;Syntax, examples, and differences for PostgreSQL, Greenplum, and MS SQL Server. From basic SELECT to advanced window functions.
Basic data selection operations
Selecting data from a table
SELECT first_name, last_name, email
FROM employees
WHERE department_id = 10;Selecting all columns from a table
SELECT * FROM products;Avoid SELECT * in production — specify columns explicitly
Selecting unique values
SELECT DISTINCT department_id, job_title
FROM employees;First row for each unique value
SELECT DISTINCT ON (department_id)
department_id, employee_name, salary
FROM employees
ORDER BY department_id, salary DESC;Limiting the number of returned rows
-- LIMIT с OFFSET
SELECT * FROM products
ORDER BY price DESC
LIMIT 10 OFFSET 20;
-- FETCH FIRST (SQL стандарт)
SELECT * FROM products
ORDER BY price DESC
OFFSET 20 ROWS
FETCH FIRST 10 ROWS ONLY;Assigning aliases to columns and tables
SELECT
e.first_name AS "Имя",
e.last_name AS "Фамилия",
d.name AS "Отдел"
FROM employees e
JOIN departments d ON e.dept_id = d.id;Creating a new table from query results
SELECT employee_id, first_name, salary
INTO high_earners
FROM employees
WHERE salary > 100000;Data filtering conditions
Basic row filtering
SELECT * FROM orders
WHERE status = 'pending'
AND total_amount > 1000;Comparing values
SELECT * FROM products
WHERE price >= 100
AND price <= 500
AND stock <> 0;Logical operators for combining conditions
SELECT * FROM employees
WHERE (department = 'IT' OR department = 'HR')
AND NOT is_terminated
AND hire_date >= '2020-01-01';Checking membership in a list of values
SELECT * FROM orders
WHERE status IN ('pending', 'processing', 'shipped');
SELECT * FROM products
WHERE category_id NOT IN (
SELECT id FROM categories WHERE is_archived = true
);Checking if value falls within a range (inclusive)
SELECT * FROM orders
WHERE order_date BETWEEN '2024-01-01' AND '2024-12-31';
SELECT * FROM products
WHERE price NOT BETWEEN 100 AND 500;Pattern matching in strings
-- % - любое количество символов
-- _ - один символ
SELECT * FROM customers
WHERE email LIKE '%@gmail.com';
-- ILIKE - регистронезависимый (PostgreSQL)
SELECT * FROM products
WHERE name ILIKE '%phone%';Checking for NULL values
SELECT * FROM employees
WHERE manager_id IS NULL;
SELECT * FROM orders
WHERE shipped_date IS NOT NULL;NULL != NULL, use IS NULL instead of = NULL
NULL-safe comparison (NULL = NULL → true)
-- IS DISTINCT FROM: NULL-safe сравнение
SELECT * FROM t1
WHERE col1 IS DISTINCT FROM col2;
-- Эквивалент: col1 <> col2 OR (col1 IS NULL) <> (col2 IS NULL)Checking for row existence in a subquery
-- Клиенты с заказами
SELECT c.* FROM customers c
WHERE EXISTS (
SELECT 1 FROM orders o
WHERE o.customer_id = c.id
);
-- Товары без продаж
SELECT p.* FROM products p
WHERE NOT EXISTS (
SELECT 1 FROM order_items oi
WHERE oi.product_id = p.id
);Comparing with subquery results
-- ANY: хотя бы одно значение
SELECT * FROM products
WHERE price > ANY (
SELECT price FROM products WHERE category = 'Electronics'
);
-- ALL: все значения
SELECT * FROM products
WHERE price > ALL (
SELECT price FROM products WHERE category = 'Books'
);Ordering results
Sorting query results
SELECT * FROM employees
ORDER BY department_id ASC, salary DESC;Controlling NULL position when sorting
SELECT * FROM employees
ORDER BY manager_id NULLS FIRST;
SELECT * FROM orders
ORDER BY shipped_date DESC NULLS LAST;Sorting by calculated values
SELECT name, price, quantity
FROM products
ORDER BY price * quantity DESC;
SELECT * FROM employees
ORDER BY EXTRACT(YEAR FROM hire_date) DESC;Locale-aware sorting
SELECT * FROM customers
ORDER BY name COLLATE "ru_RU";
SELECT * FROM products
ORDER BY name COLLATE "C"; -- байтовая сортировкаCalculations over groups of rows
Counting the number of rows
SELECT COUNT(*) as total_rows FROM orders;
SELECT COUNT(DISTINCT customer_id) as unique_customers
FROM orders;
SELECT COUNT(shipped_date) as shipped_orders
FROM orders; -- не считает NULLSum of numeric values
SELECT SUM(total_amount) as revenue
FROM orders
WHERE EXTRACT(YEAR FROM order_date) = 2024;
SELECT SUM(DISTINCT price) FROM products;Arithmetic mean
SELECT AVG(salary) as avg_salary
FROM employees
WHERE department_id = 10;
-- Среднее с округлением
SELECT ROUND(AVG(price)::numeric, 2) FROM products;Minimum and maximum values
SELECT
MIN(price) as cheapest,
MAX(price) as most_expensive,
MAX(price) - MIN(price) as price_range
FROM products;
SELECT MIN(order_date), MAX(order_date)
FROM orders;String concatenation within a group
SELECT department_id,
STRING_AGG(employee_name, ', ' ORDER BY employee_name) as employees
FROM employees
GROUP BY department_id;Aggregating values into an array
SELECT department_id,
ARRAY_AGG(employee_id ORDER BY hire_date) as emp_ids
FROM employees
GROUP BY department_id;Logical aggregates
SELECT order_id,
BOOL_AND(is_shipped) as all_shipped,
BOOL_OR(is_returned) as any_returned
FROM order_items
GROUP BY order_id;Percentile calculation
SELECT
PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY salary) as median,
PERCENTILE_CONT(0.95) WITHIN GROUP (ORDER BY salary) as p95
FROM employees;Variance and standard deviation
SELECT
VARIANCE(salary) as variance,
VAR_POP(salary) as population_variance,
VAR_SAMP(salary) as sample_variance,
STDDEV(salary) as stddev,
STDDEV_POP(salary) as pop_stddev
FROM employees;Grouping rows for aggregation
Grouping rows by columns
SELECT department_id, COUNT(*) as emp_count
FROM employees
GROUP BY department_id;Filtering groups after aggregation
SELECT department_id, AVG(salary) as avg_sal
FROM employees
GROUP BY department_id
HAVING AVG(salary) > 50000;HAVING применяется после GROUP BY, WHERE - до
Multiple groupings in a single query
SELECT
department_id, job_title,
SUM(salary) as total_salary
FROM employees
GROUP BY GROUPING SETS (
(department_id, job_title),
(department_id),
(job_title),
()
);Hierarchical grouping with subtotals
SELECT
year, quarter, month,
SUM(revenue) as total
FROM sales
GROUP BY ROLLUP (year, quarter, month);
-- Создает: (year,quarter,month), (year,quarter), (year), ()Full grouping across all combinations
SELECT
region, product_category,
SUM(sales) as total
FROM sales_data
GROUP BY CUBE (region, product_category);
-- Создает все комбинации: (r,p), (r), (p), ()Determining if NULL is a summary row
SELECT
CASE WHEN GROUPING(department_id) = 1
THEN 'Всего'
ELSE department_id::text END as dept,
SUM(salary)
FROM employees
GROUP BY ROLLUP (department_id);Combining data from multiple tables
Returns rows with matches in both tables
SELECT e.name, d.department_name
FROM employees e
INNER JOIN departments d ON e.dept_id = d.id;All rows from left table + matches from right
SELECT c.name, o.order_id
FROM customers c
LEFT JOIN orders o ON c.id = o.customer_id;All rows from right table + matches from left
SELECT e.name, d.department_name
FROM employees e
RIGHT JOIN departments d ON e.dept_id = d.id;All rows from both tables
SELECT e.name, d.department_name
FROM employees e
FULL OUTER JOIN departments d ON e.dept_id = d.id;Cartesian product (all combinations)
SELECT c.color, s.size
FROM colors c
CROSS JOIN sizes s;Joining a table with itself
SELECT e.name as employee, m.name as manager
FROM employees e
LEFT JOIN employees m ON e.manager_id = m.id;Automatic join on same-named columns
SELECT * FROM orders
NATURAL JOIN customers;
-- Соединяет по всем столбцам с одинаковыми именамиJoin on a same-named column
SELECT * FROM orders
JOIN customers USING (customer_id);Subquery with access to outer query columns
SELECT d.name, top_emp.*
FROM departments d,
LATERAL (
SELECT * FROM employees e
WHERE e.dept_id = d.id
ORDER BY salary DESC
LIMIT 3
) top_emp;MSSQL-specific LATERAL JOIN
-- PostgreSQL: используйте LATERAL
SELECT d.name, e.*
FROM departments d,
LATERAL (SELECT * FROM employees
WHERE dept_id = d.id LIMIT 1) e;Nested queries
Subquery returning a single value
SELECT name, salary,
(SELECT AVG(salary) FROM employees) as avg_salary,
salary - (SELECT AVG(salary) FROM employees) as diff
FROM employees;Subquery as a virtual table in FROM
SELECT dept_name, avg_salary
FROM (
SELECT d.name as dept_name, AVG(e.salary) as avg_salary
FROM departments d
JOIN employees e ON d.id = e.dept_id
GROUP BY d.name
) dept_stats
WHERE avg_salary > 50000;Subquery referencing the outer query
-- Сотрудники с зарплатой выше средней в их отделе
SELECT * FROM employees e
WHERE salary > (
SELECT AVG(salary) FROM employees
WHERE dept_id = e.dept_id
);Выполняется для каждой строки внешнего запроса - может быть медленным
Common Table Expressions - named subqueries
Defining a named subquery
WITH dept_stats AS (
SELECT dept_id, AVG(salary) as avg_sal
FROM employees
GROUP BY dept_id
)
SELECT e.name, e.salary, ds.avg_sal
FROM employees e
JOIN dept_stats ds ON e.dept_id = ds.dept_id
WHERE e.salary > ds.avg_sal;Several CTEs in a single query
WITH
active_customers AS (
SELECT * FROM customers WHERE is_active = true
),
recent_orders AS (
SELECT * FROM orders
WHERE order_date > CURRENT_DATE - INTERVAL '30 days'
)
SELECT c.name, COUNT(o.id) as order_count
FROM active_customers c
LEFT JOIN recent_orders o ON c.id = o.customer_id
GROUP BY c.name;Recursive queries for hierarchies and graphs
-- Иерархия сотрудников
WITH RECURSIVE emp_hierarchy AS (
-- Базовый случай: топ-менеджеры
SELECT id, name, manager_id, 1 as level
FROM employees WHERE manager_id IS NULL
UNION ALL
-- Рекурсивный случай
SELECT e.id, e.name, e.manager_id, h.level + 1
FROM employees e
JOIN emp_hierarchy h ON e.manager_id = h.id
)
SELECT * FROM emp_hierarchy ORDER BY level;Creating numeric/time series
-- Числа от 1 до 100
WITH RECURSIVE numbers AS (
SELECT 1 as n
UNION ALL
SELECT n + 1 FROM numbers WHERE n < 100
)
SELECT * FROM numbers;
-- Даты за последний год
WITH RECURSIVE dates AS (
SELECT CURRENT_DATE - INTERVAL '1 year' as d
UNION ALL
SELECT d + INTERVAL '1 day' FROM dates
WHERE d < CURRENT_DATE
)
SELECT * FROM dates;Calculations over row groups without collapsing
Basic window function syntax
SELECT name, department, salary,
SUM(salary) OVER () as total_salary,
SUM(salary) OVER (PARTITION BY department) as dept_salary,
AVG(salary) OVER (PARTITION BY department) as dept_avg
FROM employees;Assigning a unique number to each row
SELECT
ROW_NUMBER() OVER (ORDER BY salary DESC) as rank,
name, salary
FROM employees;
-- Нумерация внутри групп
SELECT
ROW_NUMBER() OVER (
PARTITION BY department
ORDER BY salary DESC
) as dept_rank,
name, department, salary
FROM employees;Ranking with handling of ties
SELECT name, salary,
RANK() OVER (ORDER BY salary DESC) as rank,
DENSE_RANK() OVER (ORDER BY salary DESC) as dense_rank
FROM employees;
-- RANK: 1,2,2,4 (пропускает номера)
-- DENSE_RANK: 1,2,2,3 (не пропускает)Splitting into n equal groups
-- Разбиение на квартили
SELECT name, salary,
NTILE(4) OVER (ORDER BY salary) as quartile
FROM employees;Accessing next/previous row
SELECT order_date, amount,
LAG(amount, 1, 0) OVER (ORDER BY order_date) as prev_amount,
LEAD(amount, 1, 0) OVER (ORDER BY order_date) as next_amount,
amount - LAG(amount, 1, 0) OVER (ORDER BY order_date) as diff
FROM orders;First/last value in a window
SELECT name, department, salary,
FIRST_VALUE(name) OVER (
PARTITION BY department
ORDER BY salary DESC
) as top_earner,
LAST_VALUE(name) OVER (
PARTITION BY department
ORDER BY salary DESC
ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
) as lowest_earner
FROM employees;Defining window boundaries
SELECT date, amount,
-- Скользящее среднее за 7 дней
AVG(amount) OVER (
ORDER BY date
ROWS BETWEEN 6 PRECEDING AND CURRENT ROW
) as moving_avg_7d,
-- Накопительная сумма
SUM(amount) OVER (
ORDER BY date
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
) as running_total
FROM sales;Relative rank as percentage
SELECT name, salary,
PERCENT_RANK() OVER (ORDER BY salary) as pct_rank,
CUME_DIST() OVER (ORDER BY salary) as cume_dist
FROM employees;
-- PERCENT_RANK: (rank-1)/(count-1)
-- CUME_DIST: count(<=value)/count(*)Conditional aggregation in window functions
SELECT department, month, revenue,
SUM(revenue) FILTER (WHERE revenue > 0) OVER (
PARTITION BY department
) as positive_revenue_total,
COUNT(*) FILTER (WHERE status = 'completed') OVER (
PARTITION BY department
) as completed_count
FROM sales;Union, intersection, and difference of results
Combining query results
-- UNION: без дубликатов
SELECT name FROM customers
UNION
SELECT name FROM suppliers;
-- UNION ALL: с дубликатами (быстрее)
SELECT email FROM users
UNION ALL
SELECT email FROM newsletter_subscribers;Intersection of results (common rows)
-- Клиенты, которые также являются поставщиками
SELECT email FROM customers
INTERSECT
SELECT email FROM suppliers;Set difference (rows in first but not second)
-- Клиенты без заказов
SELECT customer_id FROM customers
EXCEPT
SELECT DISTINCT customer_id FROM orders;Conditional expressions and NULL handling
Conditional expression
SELECT name, salary,
CASE
WHEN salary >= 100000 THEN 'Senior'
WHEN salary >= 50000 THEN 'Middle'
ELSE 'Junior'
END as level
FROM employees;Simple value comparison
SELECT order_id,
CASE status
WHEN 'P' THEN 'Pending'
WHEN 'S' THEN 'Shipped'
WHEN 'D' THEN 'Delivered'
ELSE 'Unknown'
END as status_name
FROM orders;First non-NULL value from a list
SELECT
COALESCE(phone, mobile, email, 'No contact') as contact
FROM customers;
SELECT
COALESCE(discount, 0) as final_discount
FROM orders;Returns NULL if values are equal
-- Избежание деления на ноль
SELECT total / NULLIF(count, 0) as average
FROM stats;
-- Замена пустых строк на NULL
SELECT NULLIF(TRIM(name), '') as clean_name
FROM products;Maximum/minimum from a list of values
SELECT
GREATEST(price1, price2, price3) as max_price,
LEAST(stock1, stock2) as min_stock
FROM products;Short CASE form for MSSQL
-- PostgreSQL: используйте CASE
SELECT
CASE WHEN stock > 0 THEN 'In Stock' ELSE 'Out of Stock' END
FROM products;Working with strings
String concatenation
SELECT first_name || ' ' || last_name as full_name
FROM employees;
SELECT CONCAT(first_name, ' ', last_name) as full_name
FROM employees;String length
SELECT name, LENGTH(name) as name_length
FROM products;
SELECT CHAR_LENGTH(description) as char_count
FROM articles;Extracting a substring
SELECT SUBSTRING(phone FROM 1 FOR 3) as area_code
FROM customers;
SELECT SUBSTR(email, 1, POSITION('@' IN email) - 1) as username
FROM users;First/last n characters
SELECT LEFT(name, 10) as short_name,
RIGHT(phone, 4) as last_four
FROM customers;Removing spaces and characters
SELECT TRIM(' hello ') as trimmed;
SELECT LTRIM(' hello') as left_trimmed;
SELECT RTRIM('hello ') as right_trimmed;
-- Удаление конкретных символов
SELECT TRIM(BOTH '-' FROM '--hello--') as trimmed;Case conversion
SELECT
UPPER(name) as upper_name,
LOWER(email) as lower_email,
INITCAP(title) as title_case
FROM users;Substring replacement
SELECT REPLACE(phone, '-', '') as clean_phone
FROM customers;
SELECT REPLACE(REPLACE(text, '
', ' '), ' ', ' ')
FROM documents;Finding substring position
SELECT POSITION('@' IN email) as at_position
FROM users;
SELECT STRPOS(url, '://') as protocol_end
FROM links;Padding a string to a given length
SELECT LPAD(CAST(id AS TEXT), 6, '0') as padded_id
FROM orders; -- '000123'
SELECT RPAD(name, 20, '.') as formatted_name
FROM products;Splitting a string by delimiter
SELECT SPLIT_PART(email, '@', 1) as username,
SPLIT_PART(email, '@', 2) as domain
FROM users;
-- Разворачивание в строки
SELECT id, tag
FROM products,
UNNEST(STRING_TO_ARRAY(tags, ',')) as tag;Working with regular expressions
-- Проверка паттерна
SELECT * FROM users WHERE email ~ '^[a-z]+@';
-- Замена
SELECT REGEXP_REPLACE(phone, '[^0-9]', '', 'g') as digits_only
FROM customers;
-- Извлечение
SELECT REGEXP_MATCHES(log, '\d{4}-\d{2}-\d{2}')
FROM logs;Mathematical operations
Rounding a number
SELECT ROUND(price, 2) as rounded_price
FROM products;
SELECT ROUND(123.456, 0); -- 123
SELECT ROUND(123.456, -1); -- 120Rounding up/down
SELECT CEIL(4.2) as ceiling, -- 5
FLOOR(4.8) as floor_val; -- 4
SELECT CEIL(price) as min_whole_price
FROM products;Absolute value
SELECT ABS(-15) as absolute; -- 15
SELECT * FROM transactions
WHERE ABS(amount) > 1000;Remainder of division
SELECT MOD(17, 5) as remainder; -- 2
SELECT 17 % 5 as remainder; -- 2
-- Четные/нечетные
SELECT * FROM products
WHERE id % 2 = 0;Power and square root
SELECT POWER(2, 10) as two_to_ten; -- 1024
SELECT SQRT(144) as square_root; -- 12
SELECT POWER(base, 2) as squared,
SQRT(value) as root
FROM numbers;Logarithms and exponent
SELECT LN(2.718281828) as natural_log; -- ~1
SELECT LOG(10, 100) as log_base_10; -- 2
SELECT EXP(1) as e; -- ~2.718Random number
SELECT RANDOM(); -- 0.0 до 1.0
-- Случайное целое от 1 до 100
SELECT FLOOR(RANDOM() * 100 + 1)::int;
-- Случайная выборка
SELECT * FROM products
ORDER BY RANDOM()
LIMIT 5;Truncating a number (without rounding)
SELECT TRUNC(123.789, 2); -- 123.78
SELECT TRUNC(123.789, 0); -- 123
SELECT TRUNC(123.789, -1); -- 120Working with dates and time
Current date and time
SELECT CURRENT_DATE; -- 2024-03-15
SELECT CURRENT_TIME; -- 10:30:45
SELECT CURRENT_TIMESTAMP; -- 2024-03-15 10:30:45
SELECT NOW(); -- То же что CURRENT_TIMESTAMPExtracting a date component
SELECT
EXTRACT(YEAR FROM order_date) as year,
EXTRACT(MONTH FROM order_date) as month,
EXTRACT(DAY FROM order_date) as day,
EXTRACT(DOW FROM order_date) as day_of_week,
EXTRACT(EPOCH FROM created_at) as unix_timestamp
FROM orders;Truncating date to specified precision
SELECT
DATE_TRUNC('month', order_date) as month_start,
DATE_TRUNC('year', order_date) as year_start,
DATE_TRUNC('hour', created_at) as hour_start
FROM orders;
-- Группировка по месяцам
SELECT DATE_TRUNC('month', order_date), COUNT(*)
FROM orders
GROUP BY 1;Adding a period to a date
SELECT
order_date + INTERVAL '7 days' as week_later,
order_date + INTERVAL '1 month' as month_later,
order_date - INTERVAL '1 year' as year_ago
FROM orders;
SELECT NOW() + INTERVAL '2 hours 30 minutes';Calculating difference between dates
SELECT shipped_date - order_date as days_to_ship
FROM orders;
SELECT AGE(NOW(), hire_date) as tenure
FROM employees;
SELECT EXTRACT(EPOCH FROM (end_time - start_time)) as seconds
FROM events;Converting date to string
SELECT
TO_CHAR(order_date, 'DD.MM.YYYY') as formatted,
TO_CHAR(order_date, 'YYYY-MM-DD HH24:MI:SS') as iso,
TO_CHAR(order_date, 'Day, DD Month YYYY') as full_date
FROM orders;Creating a date from components
SELECT MAKE_DATE(2024, 3, 15);
SELECT MAKE_TIMESTAMP(2024, 3, 15, 10, 30, 0);
SELECT MAKE_DATE(year, 1, 1) as year_start
FROM (SELECT DISTINCT EXTRACT(YEAR FROM order_date)::int as year
FROM orders) y;Working with time zones
SELECT
created_at,
created_at AT TIME ZONE 'UTC' as utc,
created_at AT TIME ZONE 'Europe/Moscow' as moscow
FROM events;
-- Конвертация
SELECT timestamp '2024-03-15 10:00:00' AT TIME ZONE 'America/New_York';Data type conversion
Standard type conversion
SELECT CAST('123' AS INTEGER);
SELECT CAST(price AS NUMERIC(10,2));
SELECT CAST(created_at AS DATE);Short CAST form for PostgreSQL
SELECT '123'::integer;
SELECT price::numeric(10,2);
SELECT '2024-03-15'::date;
SELECT row_to_json(t)::text FROM table t;MSSQL-specific conversion with styles
-- PostgreSQL: используйте CAST или TO_CHAR
SELECT TO_CHAR(date, 'DD.MM.YYYY');Safe conversion (NULL on error)
-- PostgreSQL: функция-обертка или обработка ошибок
CREATE OR REPLACE FUNCTION try_cast_int(text)
RETURNS integer AS $$
BEGIN
RETURN $1::integer;
EXCEPTION WHEN OTHERS THEN
RETURN NULL;
END;
$$ LANGUAGE plpgsql;Working with JSON data
Getting values from JSON
-- -> возвращает JSON, ->> возвращает текст
SELECT
data->'name' as json_name,
data->>'name' as text_name,
data->'address'->>'city' as city
FROM users;
-- #> для пути, #>> для текста
SELECT data#>>'{address,city}' as city FROM users;Creating JSON objects
SELECT JSON_BUILD_OBJECT(
'id', id,
'name', name,
'tags', JSON_BUILD_ARRAY('tag1', 'tag2')
) FROM products;
SELECT JSON_AGG(JSON_BUILD_OBJECT('id', id, 'name', name))
FROM products;Checking JSONB contents (PostgreSQL)
-- @> содержит
SELECT * FROM products
WHERE tags @> '["electronics"]';
-- ? содержит ключ
SELECT * FROM users
WHERE profile ? 'premium';
-- ?& все ключи, ?| любой ключ
SELECT * FROM data WHERE info ?& ARRAY['a','b'];Converting JSON to rows/columns
-- Ключи и значения
SELECT key, value FROM users,
JSONB_EACH(profile);
-- Массив в строки
SELECT id, elem
FROM products,
JSONB_ARRAY_ELEMENTS(tags) as elem;INSERT, UPDATE, DELETE, MERGE
Inserting rows with explicit values
INSERT INTO products (name, price, category)
VALUES
('Product A', 99.99, 'Electronics'),
('Product B', 49.99, 'Books');Inserting from query results
INSERT INTO archive_orders (id, customer, total)
SELECT id, customer_id, total_amount
FROM orders
WHERE order_date < '2023-01-01';Insert or update on conflict
INSERT INTO products (sku, name, price)
VALUES ('SKU123', 'Widget', 29.99)
ON CONFLICT (sku)
DO UPDATE SET
name = EXCLUDED.name,
price = EXCLUDED.price,
updated_at = NOW();
-- Игнорировать конфликт
INSERT INTO logs (id, message)
VALUES (1, 'test')
ON CONFLICT DO NOTHING;Updating existing rows
UPDATE products
SET price = price * 1.1,
updated_at = NOW()
WHERE category = 'Electronics';
-- UPDATE с JOIN
UPDATE orders o
SET status = 'VIP'
FROM customers c
WHERE o.customer_id = c.id AND c.is_vip = true;Deleting rows
DELETE FROM sessions
WHERE expires_at < NOW();
-- DELETE с USING (JOIN)
DELETE FROM order_items oi
USING orders o
WHERE oi.order_id = o.id AND o.status = 'cancelled';Returning modified rows
INSERT INTO users (email, name)
VALUES ('test@test.com', 'Test')
RETURNING id, created_at;
UPDATE products
SET price = price * 0.9
WHERE category = 'Sale'
RETURNING id, name, price as new_price;
DELETE FROM sessions
WHERE expires_at < NOW()
RETURNING user_id;Fast deletion of all rows
TRUNCATE TABLE logs;
-- С каскадом для зависимых таблиц
TRUNCATE TABLE orders CASCADE;
-- Сброс sequence
TRUNCATE TABLE users RESTART IDENTITY;Быстрее DELETE, но не вызывает триггеры
CREATE, ALTER, DROP tables and objects
Creating a table
CREATE TABLE users (
id SERIAL PRIMARY KEY,
email VARCHAR(255) UNIQUE NOT NULL,
name VARCHAR(100),
created_at TIMESTAMP DEFAULT NOW(),
is_active BOOLEAN DEFAULT true
);Creating a table from query results
CREATE TABLE active_users AS
SELECT id, email, name
FROM users
WHERE is_active = true;Modifying table structure
-- Добавление столбца
ALTER TABLE users ADD COLUMN phone VARCHAR(20);
-- Удаление столбца
ALTER TABLE users DROP COLUMN phone;
-- Изменение типа
ALTER TABLE products ALTER COLUMN price TYPE DECIMAL(12,2);
-- Установка NOT NULL
ALTER TABLE users ALTER COLUMN email SET NOT NULL;Deleting a table
DROP TABLE IF EXISTS temp_data;
DROP TABLE orders CASCADE; -- Удаляет зависимые объектыCreating an index
CREATE INDEX idx_users_email ON users (email);
CREATE UNIQUE INDEX idx_products_sku ON products (sku);
-- Частичный индекс
CREATE INDEX idx_active_users ON users (id) WHERE is_active = true;
-- Составной индекс
CREATE INDEX idx_orders_customer_date ON orders (customer_id, order_date DESC);Creating a view
CREATE OR REPLACE VIEW active_orders AS
SELECT o.*, c.name as customer_name
FROM orders o
JOIN customers c ON o.customer_id = c.id
WHERE o.status != 'cancelled';PRIMARY KEY, FOREIGN KEY, UNIQUE, CHECK
Table primary key
-- При создании таблицы
CREATE TABLE users (
id SERIAL PRIMARY KEY
);
-- Составной первичный ключ
CREATE TABLE order_items (
order_id INT,
product_id INT,
PRIMARY KEY (order_id, product_id)
);
-- Добавление к существующей таблице
ALTER TABLE users ADD PRIMARY KEY (id);Foreign key (reference to another table)
CREATE TABLE orders (
id SERIAL PRIMARY KEY,
customer_id INT REFERENCES customers(id),
product_id INT,
FOREIGN KEY (product_id) REFERENCES products(id)
ON DELETE SET NULL
ON UPDATE CASCADE
);
-- Добавление FK
ALTER TABLE orders
ADD CONSTRAINT fk_customer
FOREIGN KEY (customer_id) REFERENCES customers(id);Value uniqueness
CREATE TABLE users (
email VARCHAR(255) UNIQUE
);
-- Составной уникальный ключ
ALTER TABLE employee_assignments
ADD CONSTRAINT uq_emp_project
UNIQUE (employee_id, project_id);Condition check for values
CREATE TABLE products (
price DECIMAL CHECK (price > 0),
quantity INT CHECK (quantity >= 0),
CONSTRAINT valid_discount CHECK (discount BETWEEN 0 AND 100)
);Required field and default value
CREATE TABLE users (
email VARCHAR(255) NOT NULL,
is_active BOOLEAN NOT NULL DEFAULT true,
created_at TIMESTAMP DEFAULT NOW()
);
ALTER TABLE users ALTER COLUMN name SET NOT NULL;
ALTER TABLE users ALTER COLUMN name DROP NOT NULL;BEGIN, COMMIT, ROLLBACK, isolation levels
Transaction management
BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
UPDATE accounts SET balance = balance + 100 WHERE id = 2;
COMMIT;
-- При ошибке
BEGIN;
INSERT INTO orders (...) VALUES (...);
-- Если что-то пошло не так
ROLLBACK;Savepoints within a transaction
BEGIN;
INSERT INTO orders (...) VALUES (...);
SAVEPOINT order_created;
INSERT INTO order_items (...) VALUES (...);
-- Ошибка в items
ROLLBACK TO order_created;
-- Продолжаем с другими данными
INSERT INTO order_items (...) VALUES (...);
COMMIT;Controlling visibility of changes between transactions
-- Уровни: READ UNCOMMITTED, READ COMMITTED (default),
-- REPEATABLE READ, SERIALIZABLE
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
BEGIN;
SELECT * FROM accounts WHERE id = 1;
-- Гарантированно не изменится до COMMIT
COMMIT;
-- Или при старте
BEGIN ISOLATION LEVEL REPEATABLE READ;Locking rows when reading
-- Эксклюзивная блокировка (для UPDATE/DELETE)
SELECT * FROM accounts WHERE id = 1 FOR UPDATE;
-- Разделяемая блокировка (для чтения)
SELECT * FROM products WHERE id = 1 FOR SHARE;
-- Не ждать, если заблокировано
SELECT * FROM inventory WHERE sku = 'ABC' FOR UPDATE NOWAIT;
-- Пропустить заблокированные (для очередей)
SELECT * FROM tasks WHERE status = 'pending'
FOR UPDATE SKIP LOCKED LIMIT 1;Metadata, version, current user
DBMS version
SELECT VERSION();
-- PostgreSQL 15.2 on x86_64-pc-linux-gnu...Current user
SELECT CURRENT_USER;
SELECT SESSION_USER;
SELECT CURRENT_DATABASE();
SELECT CURRENT_SCHEMA();Query execution plan
EXPLAIN SELECT * FROM orders WHERE customer_id = 1;
-- С реальным выполнением
EXPLAIN ANALYZE SELECT * FROM orders WHERE customer_id = 1;
-- Подробный вывод
EXPLAIN (ANALYZE, BUFFERS, FORMAT JSON)
SELECT * FROM orders WHERE customer_id = 1;Standard metadata views
-- Список таблиц
SELECT table_name FROM information_schema.tables
WHERE table_schema = 'public';
-- Столбцы таблицы
SELECT column_name, data_type, is_nullable
FROM information_schema.columns
WHERE table_name = 'users';Working with NULL values
Checking for NULL values
SELECT * FROM employees
WHERE manager_id IS NULL; -- топ-менеджеры
SELECT * FROM orders
WHERE shipped_date IS NOT NULL; -- отгруженныеNULL != NULL, нельзя сравнивать через = или <>
First non-NULL value from a list
SELECT
COALESCE(phone, mobile, email, 'Нет контакта') as contact
FROM customers;
-- Замена NULL на 0
SELECT COALESCE(discount, 0) as discount FROM orders;Returns NULL if values are equal
-- Защита от деления на ноль
SELECT total / NULLIF(count, 0) as average
FROM stats;
-- Пустая строка → NULL
SELECT NULLIF(TRIM(name), '') as clean_name
FROM products;Replacing NULL with a specified value
-- PostgreSQL: используйте COALESCE
SELECT COALESCE(phone, 'N/A') FROM customers;NULL-safe comparison (NULL = NULL → true)
-- Стандартное сравнение: NULL = NULL → NULL (false)
-- IS NOT DISTINCT FROM: NULL = NULL → true
SELECT * FROM t1
WHERE col1 IS NOT DISTINCT FROM col2;
-- Эквивалент:
-- WHERE col1 = col2 OR (col1 IS NULL AND col2 IS NULL)Controlling NULL position when sorting
-- NULL в начале
SELECT * FROM employees
ORDER BY manager_id NULLS FIRST;
-- NULL в конце (по умолчанию для ASC)
SELECT * FROM orders
ORDER BY shipped_date DESC NULLS LAST;Working with arrays (PostgreSQL/Greenplum)
Создание массива
SELECT ARRAY[1, 2, 3];
SELECT ARRAY['a', 'b', 'c'];
SELECT '{1,2,3}'::int[];
-- Многомерный массив
SELECT ARRAY[[1,2], [3,4]];Aggregating values into an array
SELECT department_id,
ARRAY_AGG(employee_name ORDER BY hire_date) as employees
FROM employees
GROUP BY department_id;
-- С DISTINCT
SELECT ARRAY_AGG(DISTINCT category) FROM products;Expanding an array into rows
SELECT UNNEST(ARRAY[1,2,3]) as num;
-- С сохранением индекса
SELECT * FROM UNNEST(ARRAY['a','b','c'])
WITH ORDINALITY as t(val, idx);
-- Разворачивание столбца
SELECT id, UNNEST(tags) as tag
FROM products;Getting array elements by index
SELECT (ARRAY['a','b','c'])[1]; -- 'a' (индексы с 1)
SELECT (ARRAY['a','b','c'])[2:3]; -- {'b','c'}
-- Для столбца
SELECT tags[1] as first_tag FROM products;Array operations
-- Длина
SELECT ARRAY_LENGTH(ARRAY[1,2,3], 1); -- 3
SELECT CARDINALITY(ARRAY[1,2,3]); -- 3
-- Конкатенация
SELECT ARRAY[1,2] || ARRAY[3,4]; -- {1,2,3,4}
SELECT ARRAY_CAT(ARRAY[1,2], ARRAY[3,4]);
-- Добавление элементов
SELECT ARRAY_APPEND(ARRAY[1,2], 3); -- {1,2,3}
SELECT ARRAY_PREPEND(0, ARRAY[1,2]); -- {0,1,2}
-- Удаление
SELECT ARRAY_REMOVE(ARRAY[1,2,2,3], 2); -- {1,3}
-- Позиция элемента
SELECT ARRAY_POSITION(ARRAY['a','b','c'], 'b'); -- 2Array comparison and checking
-- Содержит
SELECT ARRAY[1,2,3] @> ARRAY[2,3]; -- true
-- Содержится в
SELECT ARRAY[2] <@ ARRAY[1,2,3]; -- true
-- Пересечение (есть общие элементы)
SELECT ARRAY[1,2] && ARRAY[2,3]; -- true
-- ANY: элемент в массиве
SELECT * FROM products
WHERE 'electronics' = ANY(tags);
-- ALL: все элементы удовлетворяют
SELECT * FROM data
WHERE 100 > ALL(values);Converting between array and string
-- Массив → строка
SELECT ARRAY_TO_STRING(ARRAY['a','b','c'], ', ');
-- 'a, b, c'
-- Строка → массив
SELECT STRING_TO_ARRAY('a,b,c', ',');
-- {a,b,c}
-- С NULL обработкой
SELECT ARRAY_TO_STRING(ARRAY['a',NULL,'c'], ',', 'N/A');
-- 'a,N/A,c'Creating and managing indexes
Standard index for comparisons and sorting
CREATE INDEX idx_users_email ON users (email);
-- Составной индекс
CREATE INDEX idx_orders_customer_date
ON orders (customer_id, order_date DESC);
-- Уникальный индекс
CREATE UNIQUE INDEX idx_products_sku ON products (sku);Index for a subset of rows only
-- Индекс только для активных пользователей
CREATE INDEX idx_active_users
ON users (email)
WHERE is_active = true;
-- Индекс для необработанных заказов
CREATE INDEX idx_pending_orders
ON orders (created_at)
WHERE status = 'pending';Index on a computed expression
-- Индекс по нижнему регистру
CREATE INDEX idx_users_email_lower
ON users (LOWER(email));
-- Индекс по году
CREATE INDEX idx_orders_year
ON orders (EXTRACT(YEAR FROM order_date));
-- Использование:
SELECT * FROM users WHERE LOWER(email) = 'test@test.com';Index with extra columns for index-only scan
-- Покрывающий индекс: все нужные данные в индексе
CREATE INDEX idx_orders_customer
ON orders (customer_id)
INCLUDE (order_date, total_amount);
-- Запрос использует только индекс:
SELECT order_date, total_amount
FROM orders
WHERE customer_id = 123;Specialized index types
-- Hash: только для равенства
CREATE INDEX idx_hash ON users USING hash (email);
-- GIN: для массивов, JSONB, полнотекстового поиска
CREATE INDEX idx_tags ON products USING gin (tags);
CREATE INDEX idx_data ON docs USING gin (data jsonb_path_ops);
-- GiST: для геометрии, диапазонов, полнотекста
CREATE INDEX idx_location ON places USING gist (coordinates);
-- BRIN: для больших таблиц с естественной сортировкой
CREATE INDEX idx_created ON logs USING brin (created_at);Creating an index without locking the table
-- Без блокировки записи (но медленнее)
CREATE INDEX CONCURRENTLY idx_users_email
ON users (email);
-- Удаление без блокировки
DROP INDEX CONCURRENTLY idx_users_email;Rebuilding and analyzing indexes
-- Перестроение индекса
REINDEX INDEX idx_users_email;
REINDEX TABLE users;
-- Обновление статистики
ANALYZE users;
-- Проверка использования индексов
SELECT * FROM pg_stat_user_indexes
WHERE relname = 'users';Unique capabilities of each DBMS
Unique PostgreSQL features
-- DISTINCT ON
SELECT DISTINCT ON (department_id) *
FROM employees ORDER BY department_id, salary DESC;
-- FILTER в агрегатах
SELECT
COUNT(*) FILTER (WHERE status = 'active') as active,
COUNT(*) FILTER (WHERE status = 'pending') as pending
FROM users;
-- RETURNING
INSERT INTO users (email) VALUES ('test@test.com')
RETURNING id, created_at;
-- Upsert
INSERT INTO products (sku, name) VALUES ('A1', 'Product')
ON CONFLICT (sku) DO UPDATE SET name = EXCLUDED.name;
-- Generate series
SELECT generate_series(1, 10);
SELECT generate_series('2024-01-01'::date, '2024-12-31', '1 month');
-- Lateral join
SELECT d.*, e.* FROM departments d,
LATERAL (SELECT * FROM employees
WHERE dept_id = d.id LIMIT 3) e;Unique Greenplum MPP features
-- См. PostgreSQL спецификаUnique MS SQL Server features
-- См. PostgreSQL спецификаMain syntax differences table
-- Ограничение строк
SELECT * FROM t LIMIT 10 OFFSET 20;
SELECT * FROM t FETCH FIRST 10 ROWS ONLY;
-- Конкатенация
SELECT 'Hello' || ' ' || 'World';
SELECT CONCAT('Hello', ' ', 'World');
-- Текущее время
SELECT NOW(), CURRENT_TIMESTAMP;
-- Автоинкремент
CREATE TABLE t (id SERIAL PRIMARY KEY);
CREATE TABLE t (id INT GENERATED ALWAYS AS IDENTITY);
-- Boolean
SELECT * FROM t WHERE flag = true;
SELECT * FROM t WHERE flag IS TRUE;
-- Комментарии
-- однострочный
/* многострочный */