
Запись из дневника доктора Ватсона
1. Тревожный звонок
Был хмурый лондонский вечер, когда в нашу скромную квартиру на Бейкер-стрит ворвался взволнованный инспектор Лестрейд.
— Холмс! Нам срочно нужна ваша помощь! — воскликнул он, сбрасывая с плеч дождевик. — В городе орудует хитрый вор. Он крадёт предметы, но уносит их только в одном рюкзаке ограниченной вместимости. Нам нужно вычислить, какие именно вещи он унесёт, чтобы максимизировать свою добычу!

Шерлок поднял бровь.
— Задача о рюкзаке… Классика. Лестрейд, вы принесли данные?
— Да! Вот список предметов с их весом и стоимостью!
Холмс бросил взгляд на листок:
Предмет | Вес (кг) | Ценность (£) |
Золотой слиток | 5 | 400 |
Драгоценный фарфор | 3 | 300 |
Старинная книга | 2 | 150 |
Серебряный подсвечник | 1 | 100 |
— Максимальный переносимый вес рюкзака не должен превышать 6 кг, — добавил Лестрейд.
2. SQL вступает в игру
Холмс ухмыльнулся.
— Ватсон, достаньте мой ноутбук. Мы решим это с помощью SQL.
Я удивился:
— Но, Холмс, разве это не задача для динамического программирования?
— Возможно, но давайте посмотрим, как её можно неправильно решить на SQL, а потом найдём оптимальный способ.
2.1. Наивный подход: перебор всех комбинаций
WITH RECURSIVE all_combinations AS ( SELECT 0 AS total_weight, 0 AS total_value, '' AS items UNION ALL SELECT ac.total_weight + i.weight, ac.total_value + i.value, ac.items || ', ' || i.item FROM all_combinations ac JOIN items i ON i.item NOT IN (SELECT unnest(string_to_array(ac.items, ', '))) WHERE ac.total_weight + i.weight <= 6 ) SELECT * FROM all_combinations WHERE total_weight <= 6 ORDER BY total_value DESC LIMIT 1;
— Временная сложность: близка к O(2ⁿ) — ужасно! — воскликнул я.
— Именно, Ватсон. Этот запрос перебирает все возможные комбинации, что для большого набора данных превратится в кошмар.
2.2. Оптимизация: через оконные функции
Холмс задумался.
— Давайте попробуем более умный подход.
WITH ranked_items AS ( SELECT item, weight, value, value / weight AS value_density, ROW_NUMBER() OVER (ORDER BY value / weight DESC) AS rank FROM items ), knapsack AS ( SELECT item, weight, value, SUM(weight) OVER (ORDER BY rank) AS cumulative_weight, SUM(value) OVER (ORDER BY rank) AS cumulative_value FROM ranked_items WHERE SUM(weight) OVER (ORDER BY rank) <= 6 ) SELECT * FROM knapsack WHERE cumulative_weight <= 6 ORDER BY cumulative_value DESC LIMIT 1;
— Лучше, но не идеально, — заметил я. — Это жадный алгоритм, который не гарантирует лучший результат.
— Верно, Ватсон. Настоящий воришка не будет брать просто самые ценные вещи — он выберет оптимальную комбинацию.
2.3. Идеальное решение: CTE + перебор с ограничением
— Тогда давайте ограничим глубину рекурсии.
WITH RECURSIVE knapsack AS ( SELECT 0 AS total_weight, 0 AS total_value, ARRAY[]::TEXT[] AS items UNION ALL SELECT k.total_weight + i.weight, k.total_value + i.value, k.items || i.item FROM knapsack k JOIN items i ON i.item > ALL(k.items) -- избегаем повторов WHERE k.total_weight + i.weight <= 6 ) SELECT * FROM knapsack WHERE total_weight <= 6 ORDER BY total_value DESC LIMIT 1;
— O(n²), уже неплохо, — пробормотал я.
— Но всё равно не идеально, — вздохнул Холмс.

3. Мораль для начинающих разработчиков
Лестрейд, потрясённый, спросил:
— Так как же всё-таки правильно решать такие задачи?
Холмс откинулся в кресле.
— 1. Не доверяйте слепо LLM. ChatGPT или Copilot могут предложить наивный SQL-запрос, который убьёт вашу базу данных.
— 2. Вникайте в алгоритмическую сложность. Если задача NP-трудная (как рюкзак), SQL — не лучший инструмент.
— 3. Выбирайте правильный подход. Динамическое программирование на Python решит задачу за O(n), а не за O(2ⁿ).
— Но… а что насчёт LLM? — робко спросил Лестрейд.
— LLM — это инструмент, а не волшебная палочка. Если вы не понимаете, как работает алгоритм, вы получите медленное или неверное решение.
4. Развязка
В итоге мы нашли оптимальный набор: фарфор (3кг, £300) + книга (2кг, £150) + подсвечник (1кг, £100) = £550.
Лестрейд арестовал вора, а Холмс, довольный, зак��рил трубку.
— Мораль? Прежде чем писать код, подумайте. Иначе ваш запрос станет преступлением против производительности.
Конец записи.
🚬 P.S. Если ваш SQL-запрос выполняется дольше, чем расследование Холмса, — пора пересмотреть подход.
