Модуль 26

Custom queries та raw SQL

Ось готовий урок, написаний у стилі CS50, спеціально для тебе. Ми розберемося, що робити, коли стандартних інструментів стає замало.


🎓 Урок: Custom Queries та Raw SQL (Коли магія ORM закінчується)

Привіт, друзі! Радий бачити вас знову.

Сьогодні ми зазирнемо під капот нашої програми. Ми звикли працювати з базами даних через зручні інструменти — ORM (Object-Relational Mapping). Ви пишете User.objects.all(), і магія стається сама собою, так? Це як їздити на машині з автопілотом: ви кажете "додому", і вона їде.

Але уявіть ситуацію: ви на гоночному треку. Вам треба увійти в поворот на шаленій швидкості, і ваш автопілот каже: "Вибач, я так не вмію, це небезпечно, я скину швидкість до 40 км/год"... Ви програєте гонку.

У розробці те саме. Що робити, коли ORM генерує повільний запит? Або коли вам потрібно використати специфічну функцію бази даних, про яку ваша бібліотека навіть не чула?

Сьогодні ми вимкнемо автопілот. Ми беремо кермо у свої руки. Ми говоримо про Custom Queries та Raw SQL.


1. 🔥 Вступ: Проблема та мотивація

Уявіть, що ви прийшли в ресторан (це наша База Даних). Зазвичай ви замовляєте через офіціанта (це ORM). Ви кажете: "Хочу салат". Офіціант йде на кухню, перекладає це кухарю, чекає, приносить вам.

Це зручно. Але що, якщо у вас дуже специфічне замовлення?

"Мені, будь ласка, стейк, просмажений рівно 3 хвилини 12 секунд, посолений сіллю з Гімалаїв, але тільки з лівого боку, і поданий на тарілці, нагрітій до 45 градусів."

Офіціант (ORM) зависне. Він або принесе вам звичайний стейк, або буде бігати туди-сюди 10 разів, уточнюючи деталі. Це довго. Це неефективно.

У реальному проекті це виглядає так: Ви хочете побудувати аналітичний звіт: "Скільки користувачів купили червоні шкарпетки у дощові вівторки за останні 5 років, і яка середня сума чека?"

Якщо ви спробуєте зробити це стандартними методами ORM: 1. Ви витягнете з бази мільйони рядків. 2. Ваш сервер (Python/Node/Java) почне це перебирати в циклах. 3. Пам'ять закінчиться. Сервер впаде. Клієнт піде.

Навіщо нам Raw SQL? Щоб зайти на кухню самому і сказати кухарю (Базі Даних) прямо в обличчя, що саме нам треба. База даних зробить це в тисячі разів швидше, ніж ваш код.


2. 🧠 Теоретична база (Логіка, а не суха наука)

Давайте розберемося з поняттями.

ORM (Object-Relational Mapping) — це перекладач. Він бере ваш код (наприклад, Python) і перетворює його на SQL. Raw SQL (Сирий SQL) — це коли ви пишете SQL-запит вручну, прямо в коді, оминаючи перекладача.

Як це працює "під капотом"?

Коли ви пишете User.find(1), ORM генерує: SELECT * FROM users WHERE id = 1. Коли ви пишете Raw SQL, ви самі пишете цей рядок. Ви надсилаєте його базі, вона виконує його і повертає "сирі" дані (список кортежів або словників), а не красиві об'єкти.

⚠️ Що треба запам'ятати (Червона зона!)

Є одна річ, яку ви зобов'язані запам'ятати на все життя. Це — SQL Injection.

Уявіть, що ви пишете запит так (ніколи так не робіть!):

"SELECT * FROM users WHERE name = '" + user_input + "'"

Якщо користувач введе своє ім'я як: Ivan'; DROP TABLE users; -- Ваш запит перетвориться на: SELECT * FROM users WHERE name = 'Ivan'; DROP TABLE users; --'

Бум! Ваша база даних зникла. Тому ми завжди використовуємо параметризацію (placeholders), а не склеювання рядків. Але про це — у прикладах.


3. 🧪 Приклади (Від простого до реального)

Припустимо, ми використовуємо щось схоже на Python/Django або SQLAlchemy, але логіка універсальна.

Приклад 1: Простий SELECT (Для розминки)

Припустимо, ми хочемо знайти користувача за email.

ORM спосіб:

user = User.objects.get(email="david@harvard.edu")

Raw SQL спосіб:

query = "SELECT * FROM users WHERE email = %s"
user = execute_sql(query, ["david@harvard.edu"])

🤔 Питання до вас: Чи варто тут використовувати Raw SQL? Відповідь: Ні! ORM тут працює чудово, код чистіший і безпечніший. Не ускладнюйте життя без причини.


Приклад 2: Складна агрегація (Реальна задача)

Уявіть, що у нас є таблиця Orders. Ми хочемо отримати список топ-10 клієнтів, які витратили найбільше грошей, але тільки за товари категорії "Electronics".

ORM може зробити це, але запит буде виглядати як монстр на 10 рядків з купою Annotate, Filter, Subquery. Це важко читати.

Raw SQL рятує ситуацію:

SELECT 
    customer_id, 
    SUM(total_price) as money_spent
FROM orders 
WHERE category = 'Electronics' 
GROUP BY customer_id 
ORDER BY money_spent DESC 
LIMIT 10;

Ви вставляєте цей запит у свій код. Він читабельний. Будь-який аналітик зрозуміє його. Він працює миттєво.


Приклад 3: Використання специфічних функцій БД

Уявіть, що ви використовуєте PostgreSQL і хочете знайти всі події, що сталися на відстані 5 км від вашого офісу (гео-дані). ORM часто не мають вбудованих функцій для складних гео-розрахунків.

Raw SQL:

# %s — це безпечний плейсхолдер для параметрів!
sql = """
    SELECT name, earth_distance(ll_to_earth(lat, lng), ll_to_earth(%s, %s)) as dist
    FROM places
    WHERE earth_distance(ll_to_earth(lat, lng), ll_to_earth(%s, %s)) < 5000
    ORDER BY dist;
"""
params = [my_lat, my_lng, my_lat, my_lng]
results = db.execute(sql, params)

Чому результат саме такий? Ми використали функцію earth_distance, яка живе всередині бази даних. Python про неї не знає, але база — знає. Ми використали силу інструмента на 100%.


4. 🛠 Практична частина

Час забруднити руки. Уявіть, що ви працюєте над системою університету. У нас є таблиці Students (id, name, gpa) та Enrollments (student_id, course_name).

Завдання 1: Безпека понад усе Ось код новачка: sql = f"SELECT * FROM students WHERE name = '{input_name}'" Завдання: Перепишіть цей рядок так, щоб хакер не міг видалити нашу базу. (Підказка: використовуйте параметри).

Завдання 2: "Поганий" запит Напишіть Raw SQL запит, який знайде всіх студентів, чий середній бал (GPA) нижче 2.0, і змінить їхнє ім'я на "Треба вчитися краще". (Це update-запит, будьте обережні!)

Завдання 3: Аналітика Напишіть SQL-запит (не код), щоб порахувати, скільки студентів записано на кожен курс. Очікуваний результат: CS50: 300, Math101: 150.

Завдання 4: Міні-кейс Ваш колега каже: "Я написав Raw SQL, щоб просто отримати список усіх студентів, бо так я почуваюся хакером". Поясніть йому, чому в даному випадку це погана ідея і чому краще повернутися до ORM.

Завдання 5: А що, якщо... Що станеться, якщо ви напишете Raw SQL з синтаксисом для PostgreSQL, а потім ваша компанія вирішить перейти на MySQL? Чи буде працювати ваш код?


5. 💡 Мислення як у розробника

Як відрізнити новачка від сеньйора у цій темі?

1. Новачок: * Боїться SQL і намагається зробити все циклами в Python (повільно). * АБО пише Raw SQL всюди, навіть для простих вибірок (код перетворюється на кашу). * Забуває про SQL Injection (найгірший гріх).

2. Досвідчений розробник: * Починає з ORM. Це стандарт. Це швидко пишеться. * Якщо бачить проблеми зі швидкістю — дивиться на згенерований SQL (через print(query) або профайлер). * Якщо ORM генерує нісенітницю — пише оптимізований Raw SQL. * Він завжди документує складні SQL-запити в коді, щоб інші зрозуміли, що тут відбувається.

Порада з практики:

Якщо ваш SQL-запит займає більше 10 рядків, можливо, варто створити для нього View (уявлення) у самій базі даних і звертатися до нього через ORM як до звичайної таблиці. Це елегантне рішення.


6. 🧩 Підсумок

Отже, друзі, що ми маємо?

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

Ви більше не обмежені можливостями бібліотек. Якщо база даних вміє це робити — ви теж вмієте це робити.

Що далі? Тепер, коли ми вміємо писати потужні запити, виникає питання: а як база даних шукає інформацію серед мільйонів рядків так швидко? Чому один запит займає 0.01 секунди, а інший — 10 хвилин? На наступному уроці ми поговоримо про Індекси та Оптимізацію. Це буде ваша суперсила.

А поки що — це був CS50! Побачимось! 👋