SELECT
Выборка данных из таблицы
SELECT first_name, last_name, email
FROM employees
WHERE department_id = 10;Sintaxis, ejemplos y diferencias para PostgreSQL, Greenplum y MS SQL Server. Desde SELECT básico hasta funciones de ventana avanzadas.
Operaciones básicas de selección de datos
Выборка данных из таблицы
SELECT first_name, last_name, email
FROM employees
WHERE department_id = 10;Выборка всех столбцов таблицы
SELECT * FROM products;Избегайте SELECT * в production - указывайте конкретные столбцы
Выборка уникальных значений
SELECT DISTINCT department_id, job_title
FROM employees;Первая строка для каждого уникального значения
SELECT DISTINCT ON (department_id)
department_id, employee_name, salary
FROM employees
ORDER BY department_id, salary DESC;Ограничение количества возвращаемых строк
-- 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;Назначение псевдонимов столбцам и таблицам
SELECT
e.first_name AS "Имя",
e.last_name AS "Фамилия",
d.name AS "Отдел"
FROM employees e
JOIN departments d ON e.dept_id = d.id;Создание новой таблицы из результата запроса
SELECT employee_id, first_name, salary
INTO high_earners
FROM employees
WHERE salary > 100000;Condiciones de filtrado de datos
Базовая фильтрация строк
SELECT * FROM orders
WHERE status = 'pending'
AND total_amount > 1000;Сравнение значений
SELECT * FROM products
WHERE price >= 100
AND price <= 500
AND stock <> 0;Логические операторы для комбинации условий
SELECT * FROM employees
WHERE (department = 'IT' OR department = 'HR')
AND NOT is_terminated
AND hire_date >= '2020-01-01';Проверка вхождения в список значений
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
);Проверка попадания в диапазон (включительно)
SELECT * FROM orders
WHERE order_date BETWEEN '2024-01-01' AND '2024-12-31';
SELECT * FROM products
WHERE price NOT BETWEEN 100 AND 500;Поиск по шаблону строки
-- % - любое количество символов
-- _ - один символ
SELECT * FROM customers
WHERE email LIKE '%@gmail.com';
-- ILIKE - регистронезависимый (PostgreSQL)
SELECT * FROM products
WHERE name ILIKE '%phone%';Проверка на NULL значения
SELECT * FROM employees
WHERE manager_id IS NULL;
SELECT * FROM orders
WHERE shipped_date IS NOT NULL;NULL != NULL, используйте IS NULL, а не = NULL
Сравнение с учетом NULL (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)Проверка существования строк в подзапросе
-- Клиенты с заказами
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
);Сравнение с результатом подзапроса
-- 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'
);Ordenar resultados
Сортировка результатов запроса
SELECT * FROM employees
ORDER BY department_id ASC, salary DESC;Управление позицией NULL при сортировке
SELECT * FROM employees
ORDER BY manager_id NULLS FIRST;
SELECT * FROM orders
ORDER BY shipped_date DESC NULLS LAST;Сортировка по вычисляемым значениям
SELECT name, price, quantity
FROM products
ORDER BY price * quantity DESC;
SELECT * FROM employees
ORDER BY EXTRACT(YEAR FROM hire_date) DESC;Сортировка с учетом локали
SELECT * FROM customers
ORDER BY name COLLATE "ru_RU";
SELECT * FROM products
ORDER BY name COLLATE "C"; -- байтовая сортировкаCálculos sobre grupos de filas
Подсчет количества строк
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; -- не считает NULLСумма числовых значений
SELECT SUM(total_amount) as revenue
FROM orders
WHERE EXTRACT(YEAR FROM order_date) = 2024;
SELECT SUM(DISTINCT price) FROM products;Среднее арифметическое
SELECT AVG(salary) as avg_salary
FROM employees
WHERE department_id = 10;
-- Среднее с округлением
SELECT ROUND(AVG(price)::numeric, 2) FROM products;Минимальное и максимальное значения
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;Конкатенация строк в группе
SELECT department_id,
STRING_AGG(employee_name, ', ' ORDER BY employee_name) as employees
FROM employees
GROUP BY department_id;Агрегация значений в массив
SELECT department_id,
ARRAY_AGG(employee_id ORDER BY hire_date) as emp_ids
FROM employees
GROUP BY department_id;Логические агрегаты
SELECT order_id,
BOOL_AND(is_shipped) as all_shipped,
BOOL_OR(is_returned) as any_returned
FROM order_items
GROUP BY order_id;Вычисление перцентилей
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;Дисперсия и стандартное отклонение
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;Agrupar filas para agregación
Группировка строк по столбцам
SELECT department_id, COUNT(*) as emp_count
FROM employees
GROUP BY department_id;Фильтрация групп после агрегации
SELECT department_id, AVG(salary) as avg_sal
FROM employees
GROUP BY department_id
HAVING AVG(salary) > 50000;HAVING применяется после GROUP BY, WHERE - до
Множественная группировка в одном запросе
SELECT
department_id, job_title,
SUM(salary) as total_salary
FROM employees
GROUP BY GROUPING SETS (
(department_id, job_title),
(department_id),
(job_title),
()
);Иерархическая группировка с промежуточными итогами
SELECT
year, quarter, month,
SUM(revenue) as total
FROM sales
GROUP BY ROLLUP (year, quarter, month);
-- Создает: (year,quarter,month), (year,quarter), (year), ()Полная группировка по всем комбинациям
SELECT
region, product_category,
SUM(sales) as total
FROM sales_data
GROUP BY CUBE (region, product_category);
-- Создает все комбинации: (r,p), (r), (p), ()Определение является ли NULL итоговой строкой
SELECT
CASE WHEN GROUPING(department_id) = 1
THEN 'Всего'
ELSE department_id::text END as dept,
SUM(salary)
FROM employees
GROUP BY ROLLUP (department_id);Combinar datos de múltiples tablas
Возвращает строки с совпадениями в обеих таблицах
SELECT e.name, d.department_name
FROM employees e
INNER JOIN departments d ON e.dept_id = d.id;Все строки левой таблицы + совпадения справа
SELECT c.name, o.order_id
FROM customers c
LEFT JOIN orders o ON c.id = o.customer_id;Все строки правой таблицы + совпадения слева
SELECT e.name, d.department_name
FROM employees e
RIGHT JOIN departments d ON e.dept_id = d.id;Все строки обеих таблиц
SELECT e.name, d.department_name
FROM employees e
FULL OUTER JOIN departments d ON e.dept_id = d.id;Декартово произведение (все комбинации)
SELECT c.color, s.size
FROM colors c
CROSS JOIN sizes s;Соединение таблицы с самой собой
SELECT e.name as employee, m.name as manager
FROM employees e
LEFT JOIN employees m ON e.manager_id = m.id;Автоматическое соединение по одноименным столбцам
SELECT * FROM orders
NATURAL JOIN customers;
-- Соединяет по всем столбцам с одинаковыми именамиСоединение по одноименному столбцу
SELECT * FROM orders
JOIN customers USING (customer_id);Подзапрос с доступом к столбцам внешнего запроса
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-специфичный LATERAL JOIN
-- PostgreSQL: используйте LATERAL
SELECT d.name, e.*
FROM departments d,
LATERAL (SELECT * FROM employees
WHERE dept_id = d.id LIMIT 1) e;Consultas anidadas
Подзапрос, возвращающий одно значение
SELECT name, salary,
(SELECT AVG(salary) FROM employees) as avg_salary,
salary - (SELECT AVG(salary) FROM employees) as diff
FROM employees;Подзапрос как виртуальная таблица в 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;Подзапрос, ссылающийся на внешний запрос
-- Сотрудники с зарплатой выше средней в их отделе
SELECT * FROM employees e
WHERE salary > (
SELECT AVG(salary) FROM employees
WHERE dept_id = e.dept_id
);Выполняется для каждой строки внешнего запроса - может быть медленным
Expresiones de tabla comunes: subconsultas con nombre
Определение именованного подзапроса
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;Несколько CTE в одном запросе
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;Рекурсивные запросы для иерархий и графов
-- Иерархия сотрудников
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;Создание числовых/временных рядов
-- Числа от 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;Cálculos sobre grupos de filas sin colapsarlas
Базовый синтаксис оконных функций
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;Присвоение уникального номера каждой строке
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;Ранжирование с обработкой одинаковых значений
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 (не пропускает)Разбиение на n равных групп
-- Разбиение на квартили
SELECT name, salary,
NTILE(4) OVER (ORDER BY salary) as quartile
FROM employees;Доступ к следующей/предыдущей строке
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;Первое/последнее значение в окне
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;Определение границ окна
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;Относительный ранг в процентах
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(*)Условная агрегация в оконных функциях
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;Unión, intersección y diferencia de resultados
Объединение результатов запросов
-- UNION: без дубликатов
SELECT name FROM customers
UNION
SELECT name FROM suppliers;
-- UNION ALL: с дубликатами (быстрее)
SELECT email FROM users
UNION ALL
SELECT email FROM newsletter_subscribers;Пересечение результатов (общие строки)
-- Клиенты, которые также являются поставщиками
SELECT email FROM customers
INTERSECT
SELECT email FROM suppliers;Разность множеств (строки в первом, но не во втором)
-- Клиенты без заказов
SELECT customer_id FROM customers
EXCEPT
SELECT DISTINCT customer_id FROM orders;Expresiones condicionales y manejo de NULL
Условное выражение
SELECT name, salary,
CASE
WHEN salary >= 100000 THEN 'Senior'
WHEN salary >= 50000 THEN 'Middle'
ELSE 'Junior'
END as level
FROM employees;Простое сравнение значений
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;Первое не-NULL значение из списка
SELECT
COALESCE(phone, mobile, email, 'No contact') as contact
FROM customers;
SELECT
COALESCE(discount, 0) as final_discount
FROM orders;Возвращает NULL если значения равны
-- Избежание деления на ноль
SELECT total / NULLIF(count, 0) as average
FROM stats;
-- Замена пустых строк на NULL
SELECT NULLIF(TRIM(name), '') as clean_name
FROM products;Максимальное/минимальное из списка значений
SELECT
GREATEST(price1, price2, price3) as max_price,
LEAST(stock1, stock2) as min_stock
FROM products;Короткая форма CASE для MSSQL
-- PostgreSQL: используйте CASE
SELECT
CASE WHEN stock > 0 THEN 'In Stock' ELSE 'Out of Stock' END
FROM products;Trabajo con cadenas
Конкатенация строк
SELECT first_name || ' ' || last_name as full_name
FROM employees;
SELECT CONCAT(first_name, ' ', last_name) as full_name
FROM employees;Длина строки
SELECT name, LENGTH(name) as name_length
FROM products;
SELECT CHAR_LENGTH(description) as char_count
FROM articles;Извлечение подстроки
SELECT SUBSTRING(phone FROM 1 FOR 3) as area_code
FROM customers;
SELECT SUBSTR(email, 1, POSITION('@' IN email) - 1) as username
FROM users;Первые/последние n символов
SELECT LEFT(name, 10) as short_name,
RIGHT(phone, 4) as last_four
FROM customers;Удаление пробелов и символов
SELECT TRIM(' hello ') as trimmed;
SELECT LTRIM(' hello') as left_trimmed;
SELECT RTRIM('hello ') as right_trimmed;
-- Удаление конкретных символов
SELECT TRIM(BOTH '-' FROM '--hello--') as trimmed;Преобразование регистра
SELECT
UPPER(name) as upper_name,
LOWER(email) as lower_email,
INITCAP(title) as title_case
FROM users;Замена подстроки
SELECT REPLACE(phone, '-', '') as clean_phone
FROM customers;
SELECT REPLACE(REPLACE(text, '
', ' '), ' ', ' ')
FROM documents;Поиск позиции подстроки
SELECT POSITION('@' IN email) as at_position
FROM users;
SELECT STRPOS(url, '://') as protocol_end
FROM links;Дополнение строки до заданной длины
SELECT LPAD(CAST(id AS TEXT), 6, '0') as padded_id
FROM orders; -- '000123'
SELECT RPAD(name, 20, '.') as formatted_name
FROM products;Разбиение строки по разделителю
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;Работа с регулярными выражениями
-- Проверка паттерна
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;Operaciones matemáticas
Округление числа
SELECT ROUND(price, 2) as rounded_price
FROM products;
SELECT ROUND(123.456, 0); -- 123
SELECT ROUND(123.456, -1); -- 120Округление вверх/вниз
SELECT CEIL(4.2) as ceiling, -- 5
FLOOR(4.8) as floor_val; -- 4
SELECT CEIL(price) as min_whole_price
FROM products;Абсолютное значение
SELECT ABS(-15) as absolute; -- 15
SELECT * FROM transactions
WHERE ABS(amount) > 1000;Остаток от деления
SELECT MOD(17, 5) as remainder; -- 2
SELECT 17 % 5 as remainder; -- 2
-- Четные/нечетные
SELECT * FROM products
WHERE id % 2 = 0;Возведение в степень и корень
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;Логарифмы и экспонента
SELECT LN(2.718281828) as natural_log; -- ~1
SELECT LOG(10, 100) as log_base_10; -- 2
SELECT EXP(1) as e; -- ~2.718Случайное число
SELECT RANDOM(); -- 0.0 до 1.0
-- Случайное целое от 1 до 100
SELECT FLOOR(RANDOM() * 100 + 1)::int;
-- Случайная выборка
SELECT * FROM products
ORDER BY RANDOM()
LIMIT 5;Усечение числа (без округления)
SELECT TRUNC(123.789, 2); -- 123.78
SELECT TRUNC(123.789, 0); -- 123
SELECT TRUNC(123.789, -1); -- 120Trabajo con fechas y hora
Текущая дата и время
SELECT CURRENT_DATE; -- 2024-03-15
SELECT CURRENT_TIME; -- 10:30:45
SELECT CURRENT_TIMESTAMP; -- 2024-03-15 10:30:45
SELECT NOW(); -- То же что CURRENT_TIMESTAMPИзвлечение компонента даты
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;Усечение даты до указанной точности
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;Добавление периода к дате
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';Вычисление разницы между датами
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;Преобразование даты в строку
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;Создание даты из компонентов
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;Работа с часовыми поясами
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';Conversión de tipos de datos
Стандартное преобразование типа
SELECT CAST('123' AS INTEGER);
SELECT CAST(price AS NUMERIC(10,2));
SELECT CAST(created_at AS DATE);Короткая форма CAST для PostgreSQL
SELECT '123'::integer;
SELECT price::numeric(10,2);
SELECT '2024-03-15'::date;
SELECT row_to_json(t)::text FROM table t;MSSQL-специфичное преобразование со стилями
-- PostgreSQL: используйте CAST или TO_CHAR
SELECT TO_CHAR(date, 'DD.MM.YYYY');Безопасное преобразование (NULL при ошибке)
-- 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;Trabajo con datos JSON
Получение значений из 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;Создание JSON объектов
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;Проверка содержимого JSONB (PostgreSQL)
-- @> содержит
SELECT * FROM products
WHERE tags @> '["electronics"]';
-- ? содержит ключ
SELECT * FROM users
WHERE profile ? 'premium';
-- ?& все ключи, ?| любой ключ
SELECT * FROM data WHERE info ?& ARRAY['a','b'];Преобразование JSON в строки/столбцы
-- Ключи и значения
SELECT key, value FROM users,
JSONB_EACH(profile);
-- Массив в строки
SELECT id, elem
FROM products,
JSONB_ARRAY_ELEMENTS(tags) as elem;INSERT, UPDATE, DELETE, MERGE
Вставка строк с явными значениями
INSERT INTO products (name, price, category)
VALUES
('Product A', 99.99, 'Electronics'),
('Product B', 49.99, 'Books');Вставка из результата запроса
INSERT INTO archive_orders (id, customer, total)
SELECT id, customer_id, total_amount
FROM orders
WHERE order_date < '2023-01-01';Вставка или обновление при конфликте
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;Обновление существующих строк
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;Удаление строк
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';Возврат измененных строк
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;Быстрое удаление всех строк
TRUNCATE TABLE logs;
-- С каскадом для зависимых таблиц
TRUNCATE TABLE orders CASCADE;
-- Сброс sequence
TRUNCATE TABLE users RESTART IDENTITY;Быстрее DELETE, но не вызывает триггеры
CREATE, ALTER, DROP de tablas y objetos
Создание таблицы
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
);Создание таблицы из результата запроса
CREATE TABLE active_users AS
SELECT id, email, name
FROM users
WHERE is_active = true;Изменение структуры таблицы
-- Добавление столбца
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;Удаление таблицы
DROP TABLE IF EXISTS temp_data;
DROP TABLE orders CASCADE; -- Удаляет зависимые объектыСоздание индекса
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);Создание представления
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
Первичный ключ таблицы
-- При создании таблицы
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);Внешний ключ (ссылка на другую таблицу)
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);Уникальность значений
CREATE TABLE users (
email VARCHAR(255) UNIQUE
);
-- Составной уникальный ключ
ALTER TABLE employee_assignments
ADD CONSTRAINT uq_emp_project
UNIQUE (employee_id, project_id);Проверка условия для значений
CREATE TABLE products (
price DECIMAL CHECK (price > 0),
quantity INT CHECK (quantity >= 0),
CONSTRAINT valid_discount CHECK (discount BETWEEN 0 AND 100)
);Обязательность и значение по умолчанию
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, niveles de aislamiento
Управление транзакциями
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;Точки сохранения внутри транзакции
BEGIN;
INSERT INTO orders (...) VALUES (...);
SAVEPOINT order_created;
INSERT INTO order_items (...) VALUES (...);
-- Ошибка в items
ROLLBACK TO order_created;
-- Продолжаем с другими данными
INSERT INTO order_items (...) VALUES (...);
COMMIT;Управление видимостью изменений между транзакциями
-- Уровни: 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;Блокировка строк при чтении
-- Эксклюзивная блокировка (для 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;Metadatos, versión, usuario actual
Версия СУБД
SELECT VERSION();
-- PostgreSQL 15.2 on x86_64-pc-linux-gnu...Текущий пользователь
SELECT CURRENT_USER;
SELECT SESSION_USER;
SELECT CURRENT_DATABASE();
SELECT CURRENT_SCHEMA();План выполнения запроса
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;Стандартные представления метаданных
-- Список таблиц
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';Trabajo con valores NULL
Проверка на NULL значения
SELECT * FROM employees
WHERE manager_id IS NULL; -- топ-менеджеры
SELECT * FROM orders
WHERE shipped_date IS NOT NULL; -- отгруженныеNULL != NULL, нельзя сравнивать через = или <>
Первое не-NULL значение из списка
SELECT
COALESCE(phone, mobile, email, 'Нет контакта') as contact
FROM customers;
-- Замена NULL на 0
SELECT COALESCE(discount, 0) as discount FROM orders;Возвращает NULL если значения равны
-- Защита от деления на ноль
SELECT total / NULLIF(count, 0) as average
FROM stats;
-- Пустая строка → NULL
SELECT NULLIF(TRIM(name), '') as clean_name
FROM products;Замена NULL на указанное значение
-- PostgreSQL: используйте COALESCE
SELECT COALESCE(phone, 'N/A') FROM customers;NULL-безопасное сравнение (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)Управление позицией NULL при сортировке
-- NULL в начале
SELECT * FROM employees
ORDER BY manager_id NULLS FIRST;
-- NULL в конце (по умолчанию для ASC)
SELECT * FROM orders
ORDER BY shipped_date DESC NULLS LAST;Trabajo con arreglos (PostgreSQL/Greenplum)
Создание массива
SELECT ARRAY[1, 2, 3];
SELECT ARRAY['a', 'b', 'c'];
SELECT '{1,2,3}'::int[];
-- Многомерный массив
SELECT ARRAY[[1,2], [3,4]];Агрегация значений в массив
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;Разворачивание массива в строки
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;Получение элементов массива по индексу
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;Операции над массивами
-- Длина
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'); -- 2Сравнение и проверка массивов
-- Содержит
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);Конвертация между массивом и строкой
-- Массив → строка
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'Creación y gestión de índices
Стандартный индекс для сравнений и сортировки
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);Индекс только для подмножества строк
-- Индекс только для активных пользователей
CREATE INDEX idx_active_users
ON users (email)
WHERE is_active = true;
-- Индекс для необработанных заказов
CREATE INDEX idx_pending_orders
ON orders (created_at)
WHERE status = 'pending';Индекс по вычисляемому выражению
-- Индекс по нижнему регистру
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-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;Специализированные типы индексов
-- 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);Создание индекса без блокировки таблицы
-- Без блокировки записи (но медленнее)
CREATE INDEX CONCURRENTLY idx_users_email
ON users (email);
-- Удаление без блокировки
DROP INDEX CONCURRENTLY idx_users_email;Перестроение и анализ индексов
-- Перестроение индекса
REINDEX INDEX idx_users_email;
REINDEX TABLE users;
-- Обновление статистики
ANALYZE users;
-- Проверка использования индексов
SELECT * FROM pg_stat_user_indexes
WHERE relname = 'users';Capacidades únicas de cada SGBD
Уникальные возможности PostgreSQL
-- 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;Уникальные возможности Greenplum для MPP
-- См. PostgreSQL спецификаУникальные возможности MS SQL Server
-- См. PostgreSQL спецификаТаблица различий основного синтаксиса
-- Ограничение строк
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;
-- Комментарии
-- однострочный
/* многострочный */