8 SQL-запитів для 80% робочих задач: WHERE, JOIN, GROUP BY та інші корисні функції | robot_dreams
Для отслеживания статуса заказа — авторизируйтесь
Введите код, который был выслан на почту Введите код с SMS, который был выслан на номер
 
Код действителен в течение 5 минут Код с sms действителен в течение 5 минут
Вы уверены, что хотите выйти?
Сеанс завершен
На главную
8 SQL-запитів, які розв’язують 80% робочих завдань

8 SQL-запитів, які розв’язують 80% робочих завдань

З прикладами коду

SQL здається величезним. Сотні функцій, десятки типів JOIN'ів, незліченна кількість варіантів синтаксису. Але насправді багато реальних робочих завдань розв’язуються невеликим набором запитів, які ви будете використовувати знову і знову. 

Тож у цій статті виділимо найнеобхідніші SQL-запити, які розв’язують безліч типових задач: як знайти потрібні дані серед мільйонів записів, як порахувати суми, середні та інші метрики, як знайти дублікати, пропуски, аномалії та багато інших.

Запит 1: Базова фільтрація з умовами (WHERE)

Умовно кажучи, якщо перед вами стоїть завдання знайти всі замовлення за останній місяць зі статусом «виконано», його можна розв’язати цим запитом. Це найпростіший та найчастіший запит у роботі. Вам потрібно вибрати конкретні записи з таблиці за певними критеріями.

Приклад коду:

SELECT 

    order_id,

    customer_name,

    order_date,

    total_amount

FROM orders

WHERE order_date >= '2024-02-01' 

  AND order_date < '2024-03-01'

  AND status = 'completed';

Ще приклади умов:

-- Кілька значень

WHERE status IN ('completed', 'shipped', 'delivered')

-- Діапазон

WHERE total_amount BETWEEN 100 AND 500

-- Пошук по тексту

WHERE customer_name LIKE '%Іванов%'

-- Комбінація умов

WHERE (status = 'pending' OR status = 'processing')

  AND total_amount > 1000

Цей запит ідеально підходить, коли потрібно відфільтрувати дані за датою, статусом, категорією. Також він підійде і для пошуку конкретних записів та як основа для майже будь-якого аналізу.

А ще не забувайте перевіряти формат дат у вашій БД (PostgreSQL, MySQL можуть по-різному обробляти дати).

Запит 2: Агрегація та групування (GROUP BY + функції)

Часто стоїть ще завдання підрахувати загальний дохід за категоріями. Коли потрібно не просто вибрати дані, а порахувати суми, середні значення або кількість — варто використовувати агрегатні функції з GROUP BY.

Приклад коду:

SELECT 

    category,

    SUM(revenue) as total_revenue,

    COUNT(*) as number_of_sales,

    AVG(price) as average_price

FROM sales

GROUP BY category

ORDER BY total_revenue DESC;

У результаті отримуємо щось подібне:

category          | total_revenue | number_of_sales | average_price

------------------|---------------|-----------------|---------------

Електроніка       | 450000        | 120             | 3750

Одяг              | 280000        | 540             | 518

Книги             | 95000         | 380             | 250

Основні агрегатні функції:

-- Загальна сума

SUM(column_name)



-- Кількість записів

COUNT(*)  -- всі рядки

COUNT(column_name)  -- рядки де значення не NULL



-- Середнє значення

AVG(column_name)



-- Мінімум та максимум

MIN(column_name)

MAX(column_name)

Фільтрація після групування (HAVING):

-- Показати тільки категорії з доходом понад 100000

SELECT 

    category,

    SUM(revenue) as total_revenue

FROM sales

GROUP BY category

HAVING SUM(revenue) > 100000;

Цей запит часто використовується у звітах по продажах за категоріями, регіонами, менеджерами, для підрахунку кількості користувачів, замовлень, транзакцій та для аналізу середніх чеків, конверсій, метрик.

Запит 3: З'єднання таблиць (JOIN)

У реальних базах даних інформація майже завжди розподілена по кількох таблицях. Клієнти зберігаються в одній, замовлення — в іншій, а товари — в третій. JOIN дозволяє об'єднати ці дані в один результат. Уявіть, що у вас є дві таблиці:

Таблиця customers:

customer_id | name           | email
------------|----------------|------------------
1           | user 1   | user1@mail.com
2           | user 2   | user2@mail.com
3           | user 3   | user3@mail.com

Таблиця orders:

order_id | customer_id | order_date  | amount
---------|-------------|-------------|-------
101      | 1           | 2024-02-15  | 1500
102      | 1           | 2024-02-20  | 2300
103      | 2           | 2024-02-18  | 800

Потрібно отримати список замовлень разом з іменами клієнтів.

INNER JOIN — тільки ті записи, що мають пару

Приклад коду:

SELECT 

    c.name,

    c.email,

    o.order_id,

    o.order_date,

    o.amount

FROM customers c

INNER JOIN orders o ON c.customer_id = o.customer_id

ORDER BY o.order_date DESC;

Результат:

name           | email           | order_id | order_date  | amount

---------------|-----------------|----------|-------------|-------

User 1   | user1@mail.com  | 102      | 2024-02-20  | 2300

User 2   | user2@mail.com  | 103      | 2024-02-18  | 800

User 3   | user3@mail.com  | 101      | 2024-02-15  | 1500

Якщо в таблиці є запис про юзера, який не робив замовлення, — всі інші значення будуть null і юзера не буде включено до таблиці.

LEFT JOIN — всі записи з лівої таблиці + дані з правої (якщо є)

SELECT 

    c.name,

    c.email,

    o.order_id,

    o.order_date,

    o.amount

FROM customers c

LEFT JOIN orders o ON c.customer_id = o.customer_id

ORDER BY c.name;

Результат:

name           | email              | order_id | order_date  | amount

---------------|--------------------|----------|-------------|-------

User 1   | user1@mail.com     | NULL     | NULL        | NULL

User 2   | user2@mail.com     | 101      | 2024-02-15  | 1500

User 3   | user3@mail.com     | 102      | 2024-02-20  | 2300

User 4   | user4@mail.com     | 103      | 2024-02-18  | 800

У цьому сценарії юзер зʼявляється в результаті, але з NULL-ами в колонках замовлень, бо в нього їх немає.

Кожен тип JOIN використовується в різних сценаріях. 

  • INNER JOIN — коли потрібні тільки повні дані (список проданих товарів, замовлення з інформацією про клієнтів, транзакції з деталями платежів).
  • LEFT JOIN — коли потрібні всі записи з основної таблиці, навіть якщо немає зв'язку.

Також є інші види JOIN-запиту.

  • RIGHT JOIN — рідко використовується (зазвичай переписують як LEFT JOIN, міняючи таблиці місцями).
  • FULL OUTER JOIN — коли потрібно все з обох таблиць (найчастіше для звірки даних з двох систем або для пошуку розбіжностей між таблицями).

Запит 4: Підзапити (Subqueries)

Підзапит — це запит всередині іншого запиту. Він дозволяє спочатку порахувати якесь значення, а потім використати його у фільтрації. Уявімо, що потрібно знайти продукти, які продаються краще за середнє.

Приклад коду:

-- Знайти продукти, які продалися краще за середнє

SELECT 

    product_name,

    total_sales

FROM products

WHERE total_sales > (

    SELECT AVG(total_sales) 

    FROM products

)

ORDER BY total_sales DESC;

Отже, що відбувається? Спочатку виконується підзапит (SELECT AVG(total_sales) FROM products) → повертає, наприклад, 1500. Потім основний запит фільтрує: WHERE total_sales > 1500

Підзапит у FROM (віртуальна таблиця)

-- Топкатегорії з доходом > 100000

SELECT 

    category,

    total_revenue

FROM (

    SELECT 

        category,

        SUM(revenue) as total_revenue

    FROM sales

    GROUP BY category

) as category_sales

WHERE total_revenue > 100000;

Підзапит з IN (список значень)

-- Клієнти, які робили замовлення в лютому

SELECT name, email

FROM customers

WHERE customer_id IN (

    SELECT DISTINCT customer_id

    FROM orders

    WHERE order_date >= '2024-02-01' 

      AND order_date < '2024-03-01'

);

Є кілька сценаріїв, коли підзапит кращий за JOIN. Перш за все — коли потрібно порахувати агрегат для порівняння. Також коли потрібен список значень для фільтрації, або коли результат підзапиту — одне значення.

Запит 5: Знаходження дублікатів (HAVING)

Дублікати в базі — типова проблема. Користувач міг зареєструватися двічі або дані імпортувалися з помилками. HAVING допомагає їх знайти.

Приклад коду:

-- Знайти email'и, які зустрічаються більше ніж один раз

SELECT 

    email,

    COUNT(*) as duplicate_count

FROM users

GROUP BY email

HAVING COUNT(*) > 1

ORDER BY duplicate_count DESC;

Результат:

email              | duplicate_count

-------------------|----------------

user@mail.com      | 3

test@gmail.com     | 2

Щоб знайти всі рядки з дублікатами, можна зробити так:

SELECT u.*

FROM users u

WHERE u.email IN (

    SELECT email

    FROM users

    GROUP BY email

    HAVING COUNT(*) > 1

)

ORDER BY u.email, u.created_at;

Видалити ж їх можна кількома способами. З одного боку, можна залишити найновіший запис:

-- PostgreSQL

DELETE FROM users

WHERE user_id NOT IN (

    SELECT MAX(user_id)

    FROM users

    GROUP BY email

);

З іншого боку, можна залишити й найстаріший:

DELETE FROM users

WHERE user_id NOT IN (

    SELECT MIN(user_id)

    FROM users

    GROUP BY email

);

Втім, найбезпечніше буде використати window functions, щоби спершу перевірити, що саме буде видалено.

-- Перевірка

WITH duplicates AS (

    SELECT 

        user_id,

        email,

        ROW_NUMBER() OVER (PARTITION BY email ORDER BY created_at DESC) as rn

    FROM users

)

SELECT * FROM duplicates WHERE rn > 1;



-- Видалення

WITH duplicates AS (

    SELECT 

        user_id,

        ROW_NUMBER() OVER (PARTITION BY email ORDER BY created_at DESC) as rn

    FROM users

)

DELETE FROM users

WHERE user_id IN (

    SELECT user_id FROM duplicates WHERE rn > 1

);

Цей запит використовується в ситуаціях, коли потрібно:

  • Очистити дані після імпорту
  • Знайти помилки в реєстрації користувачів
  • Виявити повторні транзакції
  • Знайти товари з однаковими артикулами

Запит 6: Рейтинги й топи (RANK / ROW_NUMBER)

Window Functions (віконні функції) дозволяють присвоювати номери, ранги та обчислювати агрегати в межах груп без GROUP BY.

Приклад коду:

-- Топ 5 продуктів у кожній категорії

WITH ranked_products AS (

    SELECT 

        category,

        product_name,

        total_sales,

        ROW_NUMBER() OVER (PARTITION BY category ORDER BY total_sales DESC) as rank

    FROM products

)

SELECT *

FROM ranked_products

WHERE rank <= 5

ORDER BY category, rank;

Результат:

category     | product_name  | total_sales | rank

-------------|---------------|-------------|-----

Електроніка  | item    | 500000      | 1

Електроніка  | item    | 450000      | 2

Електроніка  | item    | 300000      | 3

Одяг         | item    | 120000      | 1

Одяг         | item    | 95000       | 2

Запит 7: Робота з датами (DATE functions)

Робота з датами — одна з найчастіших задач в аналітиці. Потрібно фільтрувати за періодами, порівнювати місяці, рахувати різницю в днях. SQL має потужний набір функцій для цього. Уявімо, що потрібно порівняти продажі цього місяця з минулим. 

Основні функції для роботи з датами

CURRENT_DATE / NOW() — поточна дата/час:

SELECT CURRENT_DATE;           -- 2024-03-02

SELECT NOW();                  -- 2024-03-02 14:30:45

SELECT CURRENT_TIMESTAMP;      -- те саме, що NOW()

EXTRACT — витягнути частину дати:

SELECT 

    order_date,

    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  -- 0=неділя, 6=субота

FROM orders;

DATE_TRUNC — округлити дату (PostgreSQL):

SELECT 

    DATE_TRUNC('month', order_date) as month,  -- 2024-02-01

    DATE_TRUNC('week', order_date) as week,    -- початок тижня

    DATE_TRUNC('year', order_date) as year     -- 2024-01-01

FROM orders;

INTERVAL — додавання/віднімання періодів:

-- Дати за останні 30 днів

SELECT * FROM orders

WHERE order_date >= CURRENT_DATE - INTERVAL '30 days';



-- Дати місяць тому

SELECT * FROM orders

WHERE order_date >= CURRENT_DATE - INTERVAL '1 month';

До прикладу, ось як можна порівняти продажі цього і минулого місяця:

WITH monthly_sales AS (

    SELECT 

        DATE_TRUNC('month', order_date) as month,

        SUM(amount) as total_sales

    FROM orders

    WHERE order_date >= CURRENT_DATE - INTERVAL '2 months'

    GROUP BY DATE_TRUNC('month', order_date)

)

SELECT 

    TO_CHAR(month, 'YYYY-MM') as month_name,

    total_sales,

    LAG(total_sales) OVER (ORDER BY month) as previous_month_sales,

    total_sales - LAG(total_sales) OVER (ORDER BY month) as difference,

    ROUND(

        (total_sales - LAG(total_sales) OVER (ORDER BY month)) * 100.0 / 

        LAG(total_sales) OVER (ORDER BY month), 

        2

    ) as growth_percent

FROM monthly_sales

ORDER BY month DESC;

В результаті отримуємо:

month_name | total_sales | previous_month_sales | difference | growth_percent

-----------|-------------|----------------------|------------|---------------

2024-03    | 450000      | 380000               | 70000      | 18.42

2024-02    | 380000      | NULL                 | NULL       | NULL

Цей запит суперкорисний для:

  • Фільтрації за періодами (останній тиждень, місяць, квартал)
  • Групування за датами (продажі по днях/місяцях)
  • Порівняння періодів (цей місяць vs минулий)
  • Розрахунку термінів (час доставки, затримки)
  • Аналізу трендів та сезонності

Запит 8: Умовна логіка (CASE WHEN)

CASE WHEN — це SQL-еквівалент конструкції if-else. Дозволяє створювати нові колонки з умовною логікою прямо в запиті. Наприклад, якщо потрібно категоризувати клієнтів за рівнем витрат.

Приклад коду:

-- Розподілити клієнтів за категоріями VIP/Regular/New

SELECT 

    customer_id,

    name,

    total_spent,

    CASE 

        WHEN total_spent >= 10000 THEN 'VIP'

        WHEN total_spent >= 1000 THEN 'Regular'

        ELSE 'New'

    END as customer_category

FROM customers

ORDER BY total_spent DESC;

Результат:

customer_id | name      | total_spent | customer_category

------------|-----------|-------------|------------------

101         | User 1    | 25000       | VIP

102         | User 2    | 5000        | Regular

103         | User 3    | 500         | New

Використання в агрегації

-- Порахувати кількість клієнтів у кожній категорії

SELECT 

    CASE 

        WHEN total_spent >= 10000 THEN 'VIP'

        WHEN total_spent >= 1000 THEN 'Regular'

        ELSE 'New'

    END as category,

    COUNT(*) as customer_count,

    SUM(total_spent) as total_revenue

FROM customers

GROUP BY category

ORDER BY total_revenue DESC;

CASE у WHERE (фільтрація)

-- Різні критерії для різних категорій

SELECT *

FROM products

WHERE 

    CASE 

        WHEN category = 'Електроніка' THEN price > 1000

        WHEN category = 'Одяг' THEN price > 500

        ELSE price > 100

    END;

Заміна NULL-значень

-- Якщо discount NULL, показати 0

SELECT 

    product_name,

    price,

    CASE 

        WHEN discount IS NULL THEN 0

        ELSE discount

    END as discount,

    -- або простіше через COALESCE:

    COALESCE(discount, 0) as discount_safe

FROM products;

Створення індикаторів (0/1 flags)

-- Прапорці для різних умов

SELECT 

    order_id,

    amount,

    CASE WHEN amount > 1000 THEN 1 ELSE 0 END as is_large_order,

    CASE WHEN status = 'cancelled' THEN 1 ELSE 0 END as is_cancelled,

    CASE WHEN delivery_date > promised_date THEN 1 ELSE 0 END as is_delayed

FROM orders;

Маніпуляції з умовною логікою використовують для:

  • Сегментації клієнтів, продуктів, замовлень
  • Створення категорій на основі числових значень
  • Перетворення кодів у зрозумілі назви
  • Обробки NULL-значень
  • Умовних обчислень (різні формули для різних груп)
Ещё статьи
Порівнюємо швидкість, якість і відповідальність за результат