null

Нужно меньше JOIN'ов

Как-то раз встала задача реализовать модуль, который будет отвечать за выдачу достижений пользователям. В итоге реализация свелась к скедулеру, который по ночам вызывает пачку plsql'ых процедур. Достижений было множество, но их было можно разделить на группы, которые имели общую логику проверки, а отличались лишь количеством или содержимым необходимым.  Например, пачка достижений на убийство кабанчиков: убил одного - новичов, убил пять - охотник, сто - хеминг эрнестуэй. Фактически это разные уровни одного и того же достижения, отличаются они лишь необходимым количеством у.е. для их получения.

Примерно по такому принципу все достижения были разбиты на группы и создана общая модель для всех достижений, в которую добавились поля для категории, уровня достижения и кол-во необходимых у.е. для его получения. 

После создания модели, постепенно стали писаться функции для определения пользователей, которые подходят под критерий и выдачу им достижений, если таковые ещё не имеются в их копилке. Получавшиеся запросы ужасали своей сложностью, т.к. представляли из себя insert into select, внутри которого было множество join'ов, подзапросы. Основной проблемой стало выдача только тех уровней достижений из данной категории, которые ещё не получил пользователь.

По иронию, наиболее элегантное решение этой проблемы я нашёл в самом конце, когда пытался обработать пачку достижений, которые на созданную модель натягивались насильно. В этот момент возникла задача, сделать разность двух множеств на SQL.

Ниже найденный ответ:

В стандарте ANSI SQL-92 кроме оператора объединения наборов данных (UNION) есть оператор вычитания (EXCEPT) и пересечения (INTERSECT).

Как можно догодаться необходимо воспользоваться оператором EXCEPT, который вычитает из первого множества второе.

Картинки по запросу разность множеств 

 

Пример запроса с использованием EXCEPT

SELECT user_id, achiev_id from /*куча логики по выборке тех, кто подходит под критерии достижения*/
EXCEPT (user_achieves)

 

Благодаря этому оператору отпала необходимость сложной логики выборки у пользователя уже полученных им достижений данной категории и определения среди них с максимальным уже полученным уровнем и уровнем до которого он смог достичь. Фактически можно было вообще отказаться от уровня достижения и просто выбирать каждый раз все из категории на которую он насобирал у.е., а EXCEPT уже сам бы отсеял полученные.
 
В итоге, хочется сказать, что порой стоит вспомнить, что SQL основан на алгебре множеств и он обладает встроенными возможностями по пересечению, объединению, вычитанию и стоит ими пользоваться, вместо того, чтобы писать нечитаемые костыли и велосипеды.
На этом на сегодня откланяюсь.