П’ять SQL-патернів, які замінюють цикл на Python – і чому вони виграють
Найдорожчий Python, який я рев’юю, – це for order in orders після SELECT *. Останній рядок у групі, місяць до місяця, умовні лічильники й upsert – це запити. Як я пишу їх у Postgres, замість тягнути таблицю.
Найдорожчий Python, який я рев’юю, – не хендлер FastAPI без типів. Це for order in orders, який живе, бо ніхто не написав віконну функцію. Цикл виглядає чесно. Так само звіт, що тривав чотири секунди, стає сорока, а в понеділок після вдалого вікенда продажів – таймаутом.
Я вже писала, як обираю тип бази. Це наступний шар: SQL, який я справді пишу, коли питання продукту – «останнє замовлення клієнта», «виручка цього місяця проти минулого» або «встав бронювання, якщо слот вільний». Postgres відповідає за один round trip. Python теж – після того як завантажив таблицю. Парна стаття – як цей запит має вийти з Python, не тягнучи купу назад.
1. Цикл, який завантажує таблицю
Типовий скрипт: SELECT * FROM orders, далі групування в dict, далі найсвіжіший created_at на customer_id. На 800 рядках здається нормальним. На 800 000 – це тікет у підтримку. База вже має індекс, сортування і пам’ять для цього. Ви за це заплатили. Python як другий рушій запитів – так вартість вилазить у затримці, не в рахунку, який планували.
- Якщо результат менший за джерело, а правило має назву – last, sum, rank, exists – це запит.
- Якщо правило – бізнес-есе (повернення з людським винятком, абзац політики) – може лишитись у Python.
2. Останній рядок у групі – DISTINCT ON і ROW_NUMBER
Поточна підписка на робочий простір. Останній статус інвойсу на замовлення. Водій, який пінгував востаннє. Це перший цикл, який я видаляю. У Postgres пишу DISTINCT ON (workspace_id) і ORDER BY workspace_id, created_at DESC. Портативний родич – ROW_NUMBER() OVER (PARTITION BY workspace_id ORDER BY created_at DESC), далі лишаю rn = 1.
DISTINCT ON коротший, і я ставлю його, коли Postgres мій. ROW_NUMBER пишу, коли той самий запит має пережити переїзд, або коли потрібні ранг 1 і 2 – попередній план, другий результат. Не тягніть усі підписки й не зводьте їх у Python. На нічиїх помилитеся і завантажите колонки, які ніколи не малюєте.
3. Накопичувальні суми і «з минулого місяця» – SUM OVER і LAG
Місяць до місяця – не джойн таблиці на себе всередині циклу. LAG(revenue) OVER (PARTITION BY workspace_id ORDER BY month) кладе минулий місяць у той самий рядок. SUM(amount) OVER (PARTITION BY customer_id ORDER BY created_at) – накопичувальний баланс. Віконні функції лишають зерно рядка і все одно бачать сусідів. GROUP BY згортає. Цикл на Python збирає сусідів у dict і забуває часові пояси.
Якщо потрібні і детальний рядок, і підсумок групи, SUM(amount) OVER (PARTITION BY order_id) б’є другий запит і б’є N+1 з ORM. Увесь трюк: лишити зерно, яке малюватимете, а сусідство порахувати в рушії.
4. Умовні лічильники – FILTER замість if
COUNT(*) FILTER (WHERE status = paid) поруч із COUNT(*) FILTER (WHERE status = refunded) – один прохід. Версія на Python – один прохід і два лічильники, доки хтось не додасть третій статус у другому циклі або експорт не виросте і ви не пройдете двічі. FILTER читається. CASE у SUM працює на рушіях без FILTER. У Postgres я ставлю FILTER, бо намір на сторінці: цей підрахунок має предикат.
5. Upsert замість select-потім-insert
Гонка: прочитати «чи вільний цей email», потім вставити. Дві вкладки, два воркери, один unique, який знаходите в проді. INSERT ... ON CONFLICT (email) DO UPDATE – або DO NOTHING – транзакція, яку база вже вміє. Та сама родина: UPDATE ... WHERE version = $version для оптимістичного лока. Check-then-write у Python – це гірша база, написана вручну.
- Слоти бронювання, ключі ідемпотентності, «створи клієнта, якщо немає» – це upsert, не if.
- Унікальний індекс – частина продукту. Без нього ON CONFLICT – театр.
6. Як я перевіряю, що не вигадала повільніший запит
EXPLAIN (ANALYZE, BUFFERS) на повільному шляху. Якщо бачите sequential scan на таблиці, яка виросла, потрібен індекс під партицію і порядок – (workspace_id, created_at DESC) для DISTINCT ON. Віконна функція не безкоштовна. Вона все одно дешевша за відправку купи в застосунок. Якщо план сортує два мільйони рядків для дашборда на двадцять – додайте межу по даті. SQL, що замінює цикл, усе ще може бути поганим циклом усередині рушія.
Висновок: назвіть результат – напишіть запит
Python – добре місце для HTTP, політик і речення, яке бачить користувач. Погане місце, щоб заново винаходити GROUP BY. П’ять форм – last-per-group, lag, накопичувальна сума, умовні агрегати, upsert – і більшість «розумних» скриптів, які я рев’юю, стають в’ю або одним запитом у функції репозиторію. Інша стаття – Python, який відмовляється це скасувати.
Найняти в 1 клік
Найміть мене як розробницю в один клік. Безпечний контракт на Upwork - і зробимо вебрішення з високою конверсією.
Готові обговорити проєкт?
Я senior веброзробниця, спеціалізуюсь на React і Next.js - відкрита до фриланс-проєктів по всьому світу.
Забронювати кол у Google Календар
Виберіть дату й час — посилання на Google Meet створиться автоматично.
Місто
Київ, Україна
Upwork
Переглянути профільTelegram
Напишіть меніViber
Напишіть мені