Тип | Размер | Диапазон | Применение | Пример |
SMALLINT | 2 байта | -32,768 до 32,767 | Малые счётчики, статусы | Возраст, рейтинг 1-5 |
INTEGER (INT) | 4 байта | -2,147,483,648 до 2,147,483,647 | Стандартный выбор | Количество, ID небольших таблиц |
BIGINT | 8 байт | -9,223,372,036,854,775,808 до 9,223,372,036,854,775,807 | Большие ID, миллисекунды | User ID, timestamps |
NUMERIC(p,s) | переменный | до 131072 цифр до и 16383 после запятой | Точные вычисления | Деньги, проценты |
REAL | 4 байта | 6 десятичных знаков точности | Приблизительные научные | Координаты GPS |
DOUBLE PRECISION | 8 байт | 15 десятичных знаков точности | Научные расчёты | Физические константы |
SERIAL | 4 байта | Автоинкремент INT | Автоинкремент ID | id SERIAL PRIMARY KEY |
BIGSERIAL | 8 байт | Автоинкремент BIGINT | Автоинкремент для больших таблиц | id BIGSERIAL PRIMARY KEY |
2018 - Запуск проекта, выбран INT для user_id:
CREATE TABLE users (id SERIAL PRIMARY KEY); -- INT, макс 2.1 млрд
2020 - 100,000 пользователей ✅
2021 - 1,000,000 пользователей ✅
2022 - 10,000,000 пользователей ✅
2023 - 2,000,000,000 пользователей ⚠️ близко к лимиту!
Октябрь 2023 - user_id достиг 2,147,483,647 (максимум INT):
INSERT INTO users (name) VALUES ('New User');
-- ERROR: integer out of range ❌
Паника:
• Регистрация сломана
• Нельзя создать новых пользователей
• Миграция INT → BIGINT займёт 6 часов с даунтаймом
• Потеря $500,000 выручки за 6 часов -- Миграция занимает 6 часов на 2 млрд записей:
BEGIN;
-- 1. Создать новую колонку BIGINT:
ALTER TABLE users ADD COLUMN id_new BIGINT;
-- 2. Скопировать данные (долго!):
UPDATE users SET id_new = id; -- 6 часов!
-- 3. Удалить старую колонку, переименовать:
ALTER TABLE users DROP COLUMN id;
ALTER TABLE users RENAME COLUMN id_new TO id;
-- 4. Обновить все FOREIGN KEY (ещё 2 часа!):
ALTER TABLE orders ALTER COLUMN user_id TYPE BIGINT;
ALTER TABLE comments ALTER COLUMN user_id TYPE BIGINT;
-- ... ещё 50 таблиц
COMMIT;
-- Итого: 8 часов даунтайма, миллионные потери -- ✅ Используй BIGINT для ID с самого начала:
CREATE TABLE users (
id BIGSERIAL PRIMARY KEY, -- макс 9,223,372,036,854,775,807
name VARCHAR(100) NOT NULL
);
-- Хватит на:
-- • 9 квинтиллионов пользователей
-- • 293 миллиарда лет при 1 регистрации/секунду
-- • Больше, чем атомов в известной вселенной @Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id; // BIGSERIAL → Java Long (не Integer!)
private String name;
private Short age; // SMALLINT → Java Short (для экономии памяти)
private BigDecimal balance; // NUMERIC → Java BigDecimal (для денег!)
}
@Entity
@Table(name = "products")
public class Product {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id; // BIGSERIAL
private String name;
@Column(precision = 10, scale = 2) // NUMERIC(10,2)
private BigDecimal price; // ❗ BigDecimal, НЕ Double для денег!
private Integer stockQuantity; // INTEGER (до 2 млрд штук товара)
} @Service
public class ProductService {
public void updatePrice(Long productId, BigDecimal newPrice) {
// BigDecimal для точных вычислений:
BigDecimal discount = new BigDecimal("0.15"); // 15%
BigDecimal discountedPrice = newPrice.multiply(BigDecimal.ONE.subtract(discount));
jdbcTemplate.update(
"UPDATE products SET price = ? WHERE id = ?",
discountedPrice.setScale(2, RoundingMode.HALF_UP), // округлить до 2 знаков
productId
);
}
public void sellProduct(Long productId, Integer quantity) {
jdbcTemplate.update(
"UPDATE products SET stock_quantity = stock_quantity - ? WHERE id = ?",
quantity, // Integer для количества (до 2 млрд)
productId
);
}
} CREATE TABLE users (
age SMALLINT CHECK (age >= 0 AND age <= 120), -- возраст
rating SMALLINT CHECK (rating BETWEEN 1 AND 5) -- рейтинг 1-5 звёзд
);
-- Экономия: 2 байта вместо 4 (INTEGER) на каждую строку CREATE TABLE products (
stock_quantity INTEGER CHECK (stock_quantity >= 0), -- количество товара
views_count INTEGER DEFAULT 0 -- счётчик просмотров
);
-- Стандартный выбор для счётчиков и количеств CREATE TABLE users (
id BIGSERIAL PRIMARY KEY, -- ❗ ВСЕГДА для ID
created_at_ms BIGINT -- Unix timestamp в миллисекундах
);
CREATE TABLE events (
user_id BIGINT REFERENCES users(id), -- FOREIGN KEY тоже BIGINT
timestamp_ns BIGINT -- наносекунды с 1970 года
);
-- Правило: ID = BIGINT, точка. CREATE TABLE invoices (
amount NUMERIC(10, 2), -- до 99,999,999.99 (10 цифр, 2 после запятой)
tax_rate NUMERIC(5, 4), -- 0.1850 = 18.50%
total NUMERIC(12, 2)
);
-- ❗ ВСЕГДА для денег и процентов -- SMALLINT (2 байта):
CREATE TABLE test_small (id BIGSERIAL, value SMALLINT);
INSERT INTO test_small SELECT generate_series(1, 10000000), 100;
-- Размер: 342 MB
-- SELECT AVG(value): 234 ms
-- INTEGER (4 байта):
CREATE TABLE test_int (id BIGSERIAL, value INTEGER);
INSERT INTO test_int SELECT generate_series(1, 10000000), 100;
-- Размер: 422 MB (+23%)
-- SELECT AVG(value): 256 ms (+9%)
-- BIGINT (8 байт):
CREATE TABLE test_bigint (id BIGSERIAL, value BIGINT);
INSERT INTO test_bigint SELECT generate_series(1, 10000000), 100;
-- Размер: 574 MB (+68%)
-- SELECT AVG(value): 289 ms (+23%)
-- NUMERIC (переменный размер):
CREATE TABLE test_numeric (id BIGSERIAL, value NUMERIC(10,2));
INSERT INTO test_numeric SELECT generate_series(1, 10000000), 100.50;
-- Размер: 498 MB (+45%)
-- SELECT AVG(value): 1247 ms (+433%!) ← NUMERIC медленнее для вычислений -- ❌ НЕПРАВИЛЬНО:
CREATE TABLE orders (
total DOUBLE PRECISION -- ❌❌❌ НИКОГДА для денег!
);
INSERT INTO orders (total) VALUES (0.1 + 0.2); -- ожидаем 0.3
SELECT total FROM orders; -- получаем 0.30000000000000004 ❌
-- Проблема: накопление ошибок округления:
SELECT SUM(total) FROM orders; -- может отличаться на центы/рубли!
-- ✅ ПРАВИЛЬНО:
CREATE TABLE orders (
total NUMERIC(10, 2) -- точная арифметика
); -- ❌ ОПАСНО:
CREATE TABLE users (id SERIAL PRIMARY KEY); -- INT, лимит 2.1 млрд
-- ✅ БЕЗОПАСНО:
CREATE TABLE users (id BIGSERIAL PRIMARY KEY); -- BIGINT, 9 квинтиллионов -- ⚠️ Неоптимально:
CREATE TABLE users (
age BIGINT -- 8 байт для числа 0-120?!
);
-- На 10 млн пользователей = 80 MB вместо 20 MB (SMALLINT)
-- ✅ Оптимально:
CREATE TABLE users (
age SMALLINT CHECK (age >= 0 AND age <= 120) -- 2 байта
); Для чего нужно число?
┌──────────────────────────────────────────────────────────┐
│ PRIMARY KEY или FOREIGN KEY? │
└──────────────────┬───────────────────────────────────────┘
↓ ДА
BIGSERIAL (для PK) / BIGINT (для FK)
Не экономь 4 байта — переполнение INT обойдётся дороже!
┌──────────────────────────────────────────────────────────┐
│ Деньги, цены, проценты? │
└──────────────────┬───────────────────────────────────────┘
↓ ДА
NUMERIC(10, 2) для денег
NUMERIC(5, 4) для процентов
НИКОГДА FLOAT/DOUBLE!
┌──────────────────────────────────────────────────────────┐
│ Малые числа (возраст, рейтинг, счётчик до 30k)? │
└──────────────────┬───────────────────────────────────────┘
↓ ДА
SMALLINT (экономия памяти)
┌──────────────────────────────────────────────────────────┐
│ Счётчики, количества, обычные числа? │
└──────────────────┬───────────────────────────────────────┘
↓ ДА
INTEGER (стандартный выбор)
┌──────────────────────────────────────────────────────────┐
│ Научные вычисления, координаты, приблизительные данные? │
└──────────────────┬───────────────────────────────────────┘
↓ ДА
DOUBLE PRECISION (быстрая арифметика) Характеристика | NUMERIC(p,s) | FLOAT/DOUBLE PRECISION |
Точность | Абсолютная (десятичная арифметика) | Приблизительная (ошибки округления) |
Хранение | Десятичное (как люди пишут) | Двоичное (IEEE 754) |
Скорость | Медленнее (4-5x) | Быстрее (аппаратная поддержка) |
Размер | Переменный (2-16 байт) | 4 байта (REAL) / 8 байт (DOUBLE) |
Применение | Деньги, финансы, бизнес | Научные расчёты, координаты |
Примеры | Цены, балансы, проценты | Температура, GPS, физика |
День 1 - Разработчик создал таблицу платежей:
CREATE TABLE payments (amount DOUBLE PRECISION); -- ❌ FLOAT для денег!
Месяц 1 - 10,000 транзакций:
SELECT SUM(amount) FROM payments;
-- Ожидаем: 1,000,000.00 руб.
-- Получаем: 999,999.9999999876 руб.
-- Разница: 0.0000000124 руб. (игнорируют)
Месяц 6 - 500,000 транзакций:
SELECT SUM(amount) FROM payments;
-- Ожидаем: 50,000,000.00 руб.
-- Получаем: 49,999,987.34 руб.
-- Разница: -12.66 руб. ⚠️
Год 1 - 5,000,000 транзакций:
SELECT SUM(amount) FROM payments;
-- Ожидаем: 500,000,000.00 руб.
-- Получаем: 499,997,856.12 руб.
-- Разница: -2,143.88 руб.! ❌
Проблема обнаружена:
• Бухгалтерия: "В балансе недостача 2,143.88 руб.!"
• ЦБ РФ: "Штраф за неточность финансовой отчётности: 100,000 руб."
• Клиенты: "С моего счёта списалось 100.03 вместо 100.00!" -- Проблема 1: 0.1 + 0.2 ≠ 0.3
SELECT 0.1::float + 0.2::float = 0.3::float;
-- FALSE ❌
SELECT 0.1::float + 0.2::float;
-- 0.30000000000000004 (не 0.3!)
-- NUMERIC точен:
SELECT 0.1::numeric + 0.2::numeric = 0.3::numeric;
-- TRUE ✅
SELECT 0.1::numeric + 0.2::numeric;
-- 0.3 (ровно!) -- FLOAT: ошибки накапливаются
WITH RECURSIVE float_sum AS (
SELECT 1 AS n, 0.1::float AS sum
UNION ALL
SELECT n + 1, sum + 0.1::float
FROM float_sum
WHERE n < 10
)
SELECT n, sum, (n * 0.1) AS expected, sum - (n * 0.1) AS error
FROM float_sum;
-- Результат:
-- n │ sum (FLOAT) │ expected │ error
-- ────┼──────────────────────┼──────────┼─────────────────
-- 1 │ 0.1 │ 0.1 │ 0.0
-- 2 │ 0.2 │ 0.2 │ 0.0
-- 3 │ 0.30000000000000004 │ 0.3 │ 4e-18 ← начало ошибки
-- 5 │ 0.5000000000000001 │ 0.5 │ 1e-16
-- 10 │ 1.0000000000000007 │ 1.0 │ 7e-16 ← ошибка растёт!
-- NUMERIC: точность сохраняется
WITH RECURSIVE numeric_sum AS (
SELECT 1 AS n, 0.1::numeric AS sum
UNION ALL
SELECT n + 1, sum + 0.1::numeric
FROM numeric_sum
WHERE n < 10
)
SELECT n, sum, (n * 0.1) AS expected
FROM numeric_sum;
-- Результат:
-- n │ sum (NUMERIC) │ expected
-- ────┼───────────────┼──────────
-- 1 │ 0.1 │ 0.1
-- 2 │ 0.2 │ 0.2
-- 3 │ 0.3 │ 0.3 ← точно!
-- 5 │ 0.5 │ 0.5
-- 10 │ 1.0 │ 1.0 ← идеально! -- ❌ FLOAT для денег (катастрофа):
CREATE TABLE accounts_wrong (
id SERIAL PRIMARY KEY,
balance DOUBLE PRECISION
);
INSERT INTO accounts_wrong (balance) VALUES (100.00);
UPDATE accounts_wrong SET balance = balance - 0.10 WHERE id = 1; -- снять 10 копеек
-- Повторить 1000 раз...
SELECT balance FROM accounts_wrong WHERE id = 1;
-- Ожидаем: 0.00
-- Получаем: -0.0000000000000142 ❌ (отрицательный баланс из-за ошибок!)
-- ✅ NUMERIC для денег (правильно):
CREATE TABLE accounts_correct (
id SERIAL PRIMARY KEY,
balance NUMERIC(10, 2)
);
INSERT INTO accounts_correct (balance) VALUES (100.00);
UPDATE accounts_correct SET balance = balance - 0.10 WHERE id = 1;
-- Повторить 1000 раз...
SELECT balance FROM accounts_correct WHERE id = 1;
-- 0.00 ✅ (идеально!) @Service
public class OrderCalculationService {
// ❌ НЕПРАВИЛЬНО: Double для денег
public Double calculateTotalWrong(List<OrderItem> items) {
Double total = 0.0;
for (OrderItem item : items) {
total += item.getPrice() * item.getQuantity(); // накопление ошибок!
}
return total;
}
// ✅ ПРАВИЛЬНО: BigDecimal для денег
public BigDecimal calculateTotalCorrect(List<OrderItem> items) {
BigDecimal total = BigDecimal.ZERO;
for (OrderItem item : items) {
BigDecimal itemTotal = item.getPrice()
.multiply(BigDecimal.valueOf(item.getQuantity()));
total = total.add(itemTotal);
}
return total.setScale(2, RoundingMode.HALF_UP); // округлить до копеек
}
// Пример использования:
public void processOrder(Long orderId) {
List<OrderItem> items = getOrderItems(orderId);
// Расчёт с BigDecimal:
BigDecimal total = calculateTotalCorrect(items);
// Применить скидку 15%:
BigDecimal discount = new BigDecimal("0.15");
BigDecimal discountAmount = total.multiply(discount)
.setScale(2, RoundingMode.HALF_UP);
BigDecimal finalTotal = total.subtract(discountAmount);
// Сохранить в БД (NUMERIC):
jdbcTemplate.update(
"UPDATE orders SET total = ?, discount = ? WHERE id = ?",
finalTotal, discountAmount, orderId
);
}
} public class FloatProblemDemo {
public static void main(String[] args) {
// ❌ Double (FLOAT):
double sumDouble = 0.0;
for (int i = 0; i < 10; i++) {
sumDouble += 0.1;
}
System.out.println("Double: " + sumDouble); // 0.9999999999999999 ❌
System.out.println("Equals 1.0? " + (sumDouble == 1.0)); // false ❌
// ✅ BigDecimal (NUMERIC):
BigDecimal sumDecimal = BigDecimal.ZERO;
for (int i = 0; i < 10; i++) {
sumDecimal = sumDecimal.add(new BigDecimal("0.1"));
}
System.out.println("BigDecimal: " + sumDecimal); // 1.0 ✅
System.out.println("Equals 1.0? " + sumDecimal.compareTo(BigDecimal.ONE) == 0); // true ✅
}
} -- Тест 1: FLOAT
CREATE TABLE test_float (id BIGSERIAL, amount REAL);
INSERT INTO test_float SELECT generate_series(1, 10000000), random() * 1000;
EXPLAIN ANALYZE SELECT SUM(amount) FROM test_float;
-- Execution Time: 387 ms
-- Тест 2: DOUBLE PRECISION
CREATE TABLE test_double (id BIGSERIAL, amount DOUBLE PRECISION);
INSERT INTO test_double SELECT generate_series(1, 10000000), random() * 1000;
EXPLAIN ANALYZE SELECT SUM(amount) FROM test_double;
-- Execution Time: 412 ms
-- Тест 3: NUMERIC
CREATE TABLE test_numeric (id BIGSERIAL, amount NUMERIC(10,2));
INSERT INTO test_numeric SELECT generate_series(1, 10000000), (random() * 1000)::numeric(10,2);
EXPLAIN ANALYZE SELECT SUM(amount) FROM test_numeric;
-- Execution Time: 1847 ms ← в 4.5x медленнее! CREATE TABLE financial_data (
-- Деньги (ВСЕГДА NUMERIC):
price NUMERIC(10, 2), -- цена до 99,999,999.99
balance NUMERIC(15, 2), -- баланс счёта
tax_amount NUMERIC(12, 2), -- сумма налога
-- Проценты (NUMERIC для точности):
interest_rate NUMERIC(5, 4), -- 0.0350 = 3.50%
tax_rate NUMERIC(4, 2), -- 18.50%
discount_percent NUMERIC(5, 2), -- до 100.00%
-- Точные измерения:
weight_kg NUMERIC(8, 3), -- вес до 99999.999 кг
length_m NUMERIC(10, 4) -- длина до 999999.9999 м
); CREATE TABLE scientific_data (
-- Координаты (GPS):
latitude DOUBLE PRECISION, -- 55.7558° N (не критично для копеек)
longitude DOUBLE PRECISION, -- 37.6173° E
-- Научные измерения:
temperature REAL, -- температура °C
pressure DOUBLE PRECISION, -- давление Па
velocity DOUBLE PRECISION, -- скорость м/с
-- Статистика (не финансы):
avg_rating REAL, -- средний рейтинг 4.7853
score DOUBLE PRECISION -- очки в игре
); -- ❌ НИКОГДА:
CREATE TABLE orders (total FLOAT); -- ❌❌❌
-- ✅ ВСЕГДА:
CREATE TABLE orders (total NUMERIC(10, 2)); -- ✅✅✅ -- ❌ НЕПРАВИЛЬНО:
SELECT * FROM products WHERE price::float = 19.99; -- может не найти!
-- (19.99 может быть 19.990000000000002)
-- ✅ ПРАВИЛЬНО: используй диапазон
SELECT * FROM products
WHERE price::float BETWEEN 19.98 AND 20.00;
-- Или лучше: не используй FLOAT для денег!
SELECT * FROM products WHERE price::numeric = 19.99; -- точно // ❌ НЕПРАВИЛЬНО:
BigDecimal wrong = new BigDecimal(0.1); // создаёт 0.1000000000000000055511151231257827021181583404541015625
System.out.println(wrong); // WTF?!
// ✅ ПРАВИЛЬНО:
BigDecimal correct = new BigDecimal("0.1"); // создаёт ровно 0.1
BigDecimal also_correct = BigDecimal.valueOf(0.1); // тоже правильно Задай себе вопрос: "Это деньги?"
┌──────────────────────────────────────────────────────────┐
│ Деньги, цены, балансы, финансовые расчёты? │
└──────────────────┬───────────────────────────────────────┘
↓ ДА
NUMERIC(10, 2) — ВСЕГДА!
Java: BigDecimal
Никаких FLOAT/Double для денег!
┌──────────────────────────────────────────────────────────┐
│ Проценты, коэффициенты (нужна точность)? │
└──────────────────┬───────────────────────────────────────┘
↓ ДА
NUMERIC(5, 4) — для точности
18.5000% (не 18.500000000002%)
┌──────────────────────────────────────────────────────────┐
│ Координаты GPS, температура, физические величины? │
└──────────────────┬───────────────────────────────────────┘
↓ ДА
DOUBLE PRECISION — скорость важнее точности
Ошибка в 0.0001° = 10 см (приемлемо)
┌──────────────────────────────────────────────────────────┐
│ Статистика, средние, не критичные метрики? │
└──────────────────┬───────────────────────────────────────┘
↓ ДА
REAL или DOUBLE PRECISION
Средний рейтинг 4.7 или 4.700001 — неважно Тип | Описание |
CHAR (n) | Фиксированная длина, дополняется пробелами |
VARCHAR (n) | Переменная длина до n символов |
TEXT | Без ограничения длины |
-- Создание колонки с collation:
CREATE TABLE users (
name VARCHAR(100) COLLATE "ru_RU.UTF-8"
);
-- Сортировка с явным collation:
SELECT * FROM users ORDER BY name COLLATE "ru_RU.UTF-8"; Collation | Применение |
"C" | Побайтовое сравнение (быстро) |
"ru_RU.UTF-8" | Русский язык |
"en_US.UTF-8" | Английский язык |
Тип | Содержит | Пример |
DATE | Только дата | '2024-01-15' |
TIME | Только время | '14:30:00' |
TIMESTAMP | Дата + время | '2024-01-15 14:30:00' |
TIMESTAMPTZ | С timezone | '2024-01-15 14:30:00+03' |
INTERVAL | Промежуток | '2 days 3 hours' |
-- BOOLEAN
CREATE TABLE users (is_active BOOLEAN DEFAULT TRUE);
SELECT * FROM users WHERE is_active; -- без = TRUE
-- SERIAL (автоинкремент)
id SERIAL PRIMARY KEY -- 1, 2, 3, ...
id BIGSERIAL PRIMARY KEY -- для больших таблиц
💡 **Интересный факт:** SERIAL — это 32-битное число, максимум **2,147,483,647**. Звучит много, но Instagram исчерпал лимит INT для лайков за несколько лет! Для больших таблиц используй BIGSERIAL (до 9 квинтиллионов).
-- UUID
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
id UUID DEFAULT uuid_generate_v4()
-- JSONB
data JSONB NOT NULL
-- Массивы
tags TEXT[] SELECT * FROM users; -- все колонки
SELECT name, email FROM users; -- конкретные колонки
SELECT name AS user_name FROM users; -- алиас колонки
SELECT u.name FROM users u; -- алиас таблицы FROM → ON → JOIN → WHERE → GROUP BY → HAVING → SELECT → DISTINCT → ORDER BY → LIMIT/OFFSET -- НЕ работает:
SELECT salary * 12 AS annual
FROM employees
WHERE annual > 100000;
-- ERROR: column "annual" does not exist
-- Обходные пути:
-- 1. Повторить выражение
WHERE salary * 12 > 100000
-- 2. Подзапрос
SELECT * FROM (
SELECT salary * 12 AS annual FROM employees
) sub WHERE annual > 100000;
-- 3. CTE
WITH salaries AS (
SELECT salary * 12 AS annual FROM employees
)
SELECT * FROM salaries WHERE annual > 100000; -- Сравнения
WHERE age > 25
WHERE status = 'active'
WHERE created_at >= '2024-01-01'
-- Логические операторы
WHERE age > 25 AND status = 'active'
WHERE age > 25 OR city = 'Москва'
WHERE NOT is_deleted
-- BETWEEN (включает обе границы)
WHERE price BETWEEN 10 AND 100
-- Эквивалент: price >= 10 AND price <= 100
-- IN
WHERE status IN ('active', 'pending', 'processing')
-- LIKE / ILIKE
WHERE name LIKE 'John%' -- начинается с John (регистрозависимо)
WHERE email LIKE '%@gmail.com' -- заканчивается на @gmail.com
WHERE name ILIKE 'john%' -- регистронезависимо (PostgreSQL)
-- % = любое количество символов
-- _ = ровно один символ WHERE column IS NULL
WHERE column IS NOT NULL
-- НЕ работает:
WHERE column = NULL -- всегда вернёт NULL (не TRUE) SELECT NULL = NULL; -- NULL (не TRUE!)
SELECT NULL <> 5; -- NULL
SELECT 5 > NULL; -- NULL Выражение | Результат |
TRUE AND NULL | NULL |
FALSE AND NULL | FALSE |
TRUE OR NULL | TRUE |
FALSE OR NULL | NULL |
NOT NULL | NULL |
-- COALESCE: первое не-NULL значение
SELECT COALESCE(phone, mobile, 'Нет контакта') FROM users;
-- NULLIF: NULL если значения равны (защита от деления на 0)
SELECT amount / NULLIF(count, 0) FROM stats;
-- CASE с NULL
SELECT CASE WHEN value IS NULL THEN 'пусто' ELSE value END FROM data; -- Если в списке есть NULL:
WHERE id NOT IN (1, 2, NULL)
-- Превращается в: id <> 1 AND id <> 2 AND id <> NULL
-- id <> NULL = NULL, поэтому всё выражение = NULL
-- Результат: 0 строк!
-- Решение: использовать NOT EXISTS или фильтровать NULL
WHERE id NOT IN (SELECT x FROM t WHERE x IS NOT NULL) SELECT * FROM products ORDER BY price; -- по возрастанию (ASC)
SELECT * FROM products ORDER BY price DESC; -- по убыванию
SELECT * FROM products ORDER BY category, price DESC; -- несколько колонок
SELECT * FROM products ORDER BY 2; -- по номеру колонки (не рекомендуется) ORDER BY column NULLS FIRST
ORDER BY column NULLS LAST SELECT * FROM products LIMIT 10; -- первые 10
SELECT * FROM products LIMIT 10 OFFSET 20; -- с 21 по 30
-- Стандартный SQL (PostgreSQL тоже поддерживает):
SELECT * FROM products
FETCH FIRST 10 ROWS ONLY;
SELECT * FROM products
OFFSET 20 ROWS FETCH NEXT 10 ROWS ONLY; -- Вместо OFFSET 1000000:
WHERE id > last_seen_id ORDER BY id LIMIT 10 SELECT DISTINCT city FROM users;
-- DISTINCT ON (PostgreSQL) — первая строка из каждой группы
SELECT DISTINCT ON (department) *
FROM employees
ORDER BY department, salary DESC;
-- Вернёт сотрудника с максимальной зарплатой из каждого отдела -- Медленно на больших таблицах без индекса:
SELECT DISTINCT city FROM users;
-- Ускоряем с индексом:
CREATE INDEX idx_users_city ON users(city);
-- Теперь PostgreSQL может использовать Index Only Scan
-- GROUP BY может быть быстрее:
SELECT city FROM users GROUP BY city;
-- PostgreSQL может выбрать Hash Aggregate вместо Sort CREATE TABLE orders (
id BIGSERIAL PRIMARY KEY,
user_id INT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
status VARCHAR(20) DEFAULT 'pending' CHECK (status IN ('pending', 'completed', 'cancelled')),
amount NUMERIC(12, 2) NOT NULL CHECK (amount > 0),
created_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE INDEX idx_orders_user ON orders(user_id);
CREATE INDEX idx_orders_status ON orders(status) WHERE status = 'pending'; SELECT
u.name,
COUNT(o.id) AS order_count,
COALESCE(SUM(o.amount), 0) AS total_amount
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
WHERE u.created_at >= '2024-01-01'
AND u.status = 'active'
GROUP BY u.id, u.name
HAVING COUNT(o.id) > 0
ORDER BY total_amount DESC
LIMIT 10; 1. FROM — откуда берём данные
2. JOIN — соединяем таблицы
3. WHERE — фильтруем строки
4. GROUP BY — группируем
5. HAVING — фильтруем группы
6. SELECT — выбираем колонки
7. DISTINCT — убираем дубликаты
8. ORDER BY — сортируем
9. LIMIT — ограничиваем