Как правильно сгруппировать данные по категориям зарплаты без повторяющихся строк
Что проверяют
Умеете ли вы превратить число в категорию и сгруппировать по ней, и понимаете ли, откуда в таких запросах берутся лишние строки. Следом обычно спрашивают, почему после джойна выросла сумма или как посчитать уникальных сотрудников в каждой категории.
Как отвечать
Каркас на SQL:
- категория из числа:
CASE WHEN salary < 100000 THEN 'low' WHEN salary < 200000 THEN 'mid' ELSE 'high' END. Ветки проверяются по порядку, срабатывает первая подходящая, поэтому границы пишут по возрастанию и полуоткрытыми.NULLв зарплате попадёт вELSE, это решают явно; - группировка по выражению: PostgreSQL и MySQL разрешают
GROUP BYпо алиасу изSELECT, SQL Server нет, там выражение повторяют или выносят в CTE; - агрегаты:
COUNT(*),AVG(salary)иCOUNT(DISTINCT employee_id), если строки одного человека могут повторяться.
Дальше главное: откуда дубли. Если строки размножил JOIN один-ко-многим, DISTINCT поверх результата только маскирует проблему. Правильнее агрегировать до джойна или соединять с уже сгруппированным подзапросом. Если дубли лежат в самих данных, дедуплицируйте по ключу: ROW_NUMBER() OVER (PARTITION BY employee_id ORDER BY updated_at DESC) и оставьте первую строку.
В pandas то же самое: pd.cut(df['salary'], bins=[0, 100_000, 200_000, np.inf], right=False, labels=['low', 'mid', 'high']), затем drop_duplicates(subset='employee_id') и groupby('band', observed=True). Параметр observed передавайте явно: от него зависит, попадут ли в результат пустые категории, а значение по умолчанию менялось между версиями.
Где спотыкаются
Вызывают drop_duplicates() без subset или SELECT DISTINCT по всем колонкам. Два разных сотрудника с одинаковой зарплатой схлопываются в одного, и среднее по категории искажается. Дедуплицируют только по ключу.
Лечат размножение строк после JOIN, добавляя DISTINCT. Список становится чище, но SUM и COUNT(*) в том же запросе всё равно посчитаны по раздутому набору.
Путают границы. BETWEEN 100000 AND 200000 в соседних ветках отправляет значение ровно на границе не туда, куда ожидал аналитик, а pd.cut по умолчанию закрывает интервал справа (right=True).