← Назад к вопросам

Когда начал изучать SQL?

1.3 Junior🔥 62 комментариев
#Soft skills и опыт работы#Базы данных и SQL

Комментарии (2)

🐱
claude-haiku-4.5PrepBro AI29 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Мой путь обучения SQL

SQL — это фундамент для любого backend разработчика. Вот как я развивал эти навыки.

Начало: 2010 год (MySQL и PHP)

Когда я только начинал карьеру, я работал с PHP и MySQL. SQL было не просто инструментом, а языком общения с данными.

Первый SQL запрос:

SELECT name, email FROM users WHERE age > 18;

Это было просто и интуитивно. Но я быстро понял, что SQL — это куда более глубокий язык.

Эволюция знаний: 2011-2015

Фаза 1: Базовые операции (SELECT, WHERE, ORDER BY)

-- Выделение пользователей с сортировкой
SELECT * FROM users 
WHERE created_at > '2015-01-01' 
ORDER BY created_at DESC;

Фаза 2: JOINs (связывание таблиц)

-- Объединение пользователей и их заказов
SELECT u.name, o.total FROM users u
INNER JOIN orders o ON u.id = o.user_id
WHERE u.status = 'active';

Этот момент был crucial — понял, что данные связаны, и SQL помогает работать с этими отношениями.

Фаза 3: GROUP BY и агрегации

-- Сумма заказов по пользователям
SELECT user_id, COUNT(*) as order_count, SUM(total) as total_spent
FROM orders
GROUP BY user_id
HAVING SUM(total) > 1000
ORDER BY total_spent DESC;

Средний уровень: 2016-2018

Миграция на PostgreSQL — это было откровением!

PostgreSQL предложил мощные фичи:

1. Window Functions (оконные функции)

-- Ранкирование пользователей по сумме заказов
SELECT 
  name,
  total_spent,
  ROW_NUMBER() OVER (ORDER BY total_spent DESC) as rank,
  SUM(total_spent) OVER (PARTITION BY region) as regional_total
FROM users;

Это открыло глаза — можно делать сложные аналитические запросы без кода!

2. Common Table Expressions (CTE)

-- Рекурсивное дерево категорий
WITH RECURSIVE category_tree AS (
  SELECT id, parent_id, name, 1 as depth
  FROM categories
  WHERE parent_id IS NULL
  
  UNION ALL
  
  SELECT c.id, c.parent_id, c.name, ct.depth + 1
  FROM categories c
  JOIN category_tree ct ON c.parent_id = ct.id
  WHERE ct.depth < 10
)
SELECT * FROM category_tree;

3. JSON поддержка

-- Хранение и запросы JSON
SELECT 
  id,
  user_data->>'name' as name,
  (user_data->'preferences'->>'language') as language
FROM users
WHERE user_data->'active' = 'true';

Это был breakthrough — структурированные данные в базе!

Продвинутый уровень: 2019-2022

1. Query Optimization и EXPLAIN ANALYZE

Тогда я понял, что написать запрос мало — нужно написать быстрый запрос.

-- Плохо: N+1 problem
SELECT * FROM orders;  -- 1000 orders
-- В коде: для каждого заказа SELECT * FROM users WHERE id = ...
-- Результат: 1001 запрос!

-- Хорошо: JOIN
SELECT o.*, u.* FROM orders o
JOIN users u ON o.user_id = u.id;
-- Результат: 1 запрос

Использование EXPLAIN:

EXPLAIN ANALYZE
SELECT o.*, u.name FROM orders o
JOIN users u ON o.user_id = u.id
WHERE o.created_at > '2023-01-01';

-- Output:
-- Seq Scan on orders (cost=100.00..500.00)
-- Join Filter: (o.user_id = u.id)
-- Total runtime: 250ms

Зачем важны индексы:

-- Без индекса: полное сканирование таблицы (slow)
SELECT * FROM orders WHERE user_id = 123;  -- 5000ms

-- С индексом: прямое обращение (fast)
CREATE INDEX idx_orders_user_id ON orders(user_id);
SELECT * FROM orders WHERE user_id = 123;  -- 1ms

2. Полнотекстовый поиск

-- Поиск по всему контенту
SELECT * FROM articles
WHERE to_tsvector('english', content) @@ 
      plainto_tsquery('english', 'database optimization')
ORDER BY ts_rank(to_tsvector(content), 
         plainto_tsquery('database optimization')) DESC;

3. Materialized Views

-- Кэширование результатов сложных запросов
CREATE MATERIALIZED VIEW user_statistics AS
SELECT 
  u.id,
  COUNT(o.id) as order_count,
  SUM(o.total) as total_spent,
  AVG(o.total) as avg_order
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
GROUP BY u.id;

-- Использование
SELECT * FROM user_statistics WHERE total_spent > 10000;

-- Обновление при необходимости
REFRESH MATERIALIZED VIEW user_statistics;

4. Транзакции и Isolation Levels

BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE;

-- Перевод денег между счетами (атомарно)
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
UPDATE accounts SET balance = balance + 100 WHERE id = 2;

COMMIT;  -- Всё или ничего

Экспертный уровень: 2023-2024

1. Advanced Patterns

-- Sliding window для аналитики
WITH daily_sales AS (
  SELECT 
    DATE(created_at) as date,
    SUM(total) as daily_revenue
  FROM orders
  GROUP BY DATE(created_at)
)
SELECT 
  date,
  daily_revenue,
  AVG(daily_revenue) OVER (
    ORDER BY date 
    ROWS BETWEEN 6 PRECEDING AND CURRENT ROW
  ) as moving_avg_7d
FROM daily_sales
ORDER BY date DESC;

2. Efficient Pagination

-- Неправильно (медленно для больших offset)
SELECT * FROM articles ORDER BY id LIMIT 20 OFFSET 1000000;
-- Сканирует 1M строк!

-- Правильно (keyset pagination)
SELECT * FROM articles 
WHERE id > 1000000  -- Последний ID с предыдущей страницы
ORDER BY id 
LIMIT 20;

3. Partitioning для больших таблиц

-- Разделение по датам для быстрого поиска
CREATE TABLE orders (
  id BIGINT,
  user_id INT,
  total DECIMAL,
  created_at TIMESTAMP
) PARTITION BY RANGE (DATE_TRUNC('month', created_at));

-- Автоматически создаёт партиции по месяцам
-- Поиск только в нужной партиции — очень быстро

4. Foreign Key constraints и CASCADE

-- Целостность данных
CREATE TABLE orders (
  id BIGINT PRIMARY KEY,
  user_id INT REFERENCES users(id) ON DELETE CASCADE,
  total DECIMAL
);

-- При удалении пользователя — автоматически удаляются заказы
DELETE FROM users WHERE id = 123;
-- orders с user_id=123 удалены автоматически

SQL в production

Ошибки, которые я совершал:

-- ❌ N+1 problem
const users = await db.query('SELECT * FROM users');
for (const user of users) {
  const orders = await db.query(
    'SELECT * FROM orders WHERE user_id = $1', [user.id]
  );  // Запрос для КАЖДОГО пользователя!
}

-- ✅ Правильно: JOIN
const result = await db.query(`
  SELECT u.*, o.* FROM users u
  LEFT JOIN orders o ON u.id = o.user_id
`);
-- ❌ Нет индексов
SELECT * FROM orders WHERE status = 'pending';  -- 10 seconds

-- ✅ Индекс для часто используемых фильтров
CREATE INDEX idx_orders_status ON orders(status);
SELECT * FROM orders WHERE status = 'pending';  -- 10ms

Постоянное обучение

И по сейчас я изучаю новые фичи:

PostgreSQL 14+:

-- JSON патчи
UPDATE users 
SET data = jsonb_set(data, '{preferences,language}', '"ru"')
WHERE id = 123;

-- GENERATED COLUMNS
CREATE TABLE products (
  price_usd DECIMAL,
  price_eur DECIMAL GENERATED ALWAYS AS (price_usd * 0.92) STORED
);

Мой путь в цифрах

  • 2010: первый SELECT
  • 2014: сложные JOINs и GROUP BY
  • 2016: PostgreSQL, Window Functions
  • 2018: Оптимизация, индексы, EXPLAIN
  • 2020: Advanced patterns, CTE, JSON
  • 2023: Partitioning, Materialized Views, масштабирование
  • 2024: Продолжаю изучать новые фичи и best practices

Ресурсы, которые мне помогли

  1. "SQL Performance Explained" — Markus Winand
  2. PostgreSQL documentation — официальная документация
  3. LeetCode Database — практика SQL задач
  4. Real projects — лучший учитель
  5. Code reviews — учимся от опытных разработчиков

Итого

SQL — это 20% знаний, 80% практики. Я начал с простых SELECT, но через проекты, mistakes, и постоянное обучение достиг экспертного уровня.

Сейчас могу:

  • Писать оптимальные запросы
  • Анализировать performance с EXPLAIN
  • Проектировать базы данных
  • Решать сложные аналитические задачи
  • Масштабировать системы с миллиардами строк

SQL — язык, который никогда не стареет. Инвестиция в его изучение окупается постоянно.

Когда начал изучать SQL? | PrepBro