Как написать SQL-запрос для вывода фамилий, встречающихся более одного раза, или полного списка с именами и фамилиями
Что проверяют
Базовое владение группировкой: разницу между WHERE и HAVING и умение вернуть не только агрегат, но и исходные строки. Следом часто просят решить вторую часть без IN или спрашивают, как выполнить запрос из Go.
Как отвечать
Фамилии с повторами:
SELECT last_name, COUNT(*) AS cnt FROM users GROUP BY last_name HAVING COUNT(*) > 1;
HAVING фильтрует уже сгруппированные строки, в WHERE агрегатную функцию поставить нельзя.
Полный список людей с такими фамилиями получают тремя способами:
- подзапрос:
WHERE last_name IN (SELECT last_name FROM users GROUP BY last_name HAVING COUNT(*) > 1). Читается проще всего; - оконная функция:
COUNT(*) OVER (PARTITION BY last_name)в CTE, снаружиWHERE cnt > 1. Один проход по таблице, и сразу видно число однофамильцев; EXISTS:WHERE EXISTS (SELECT 1 FROM users u2 WHERE u2.last_name = u.last_name AND u2.id <> u.id). Хорошо работает с индексом поlast_name.
Добавьте ORDER BY last_name, first_name, чтобы однофамильцы шли рядом. Если данные вводили люди, сравнивайте нормализованное значение lower(trim(last_name)), иначе «Иванов» и «иванов » окажутся разными.
Со стороны Go: db.QueryContext, цикл по rows.Next(), defer rows.Close() и проверка rows.Err() после цикла. Колонку, допускающую NULL, сканируйте в sql.NullString или *string, иначе Scan вернёт ошибку. С pgx логика та же.
Где спотыкаются
Пишут WHERE COUNT(*) > 1 или добавляют в SELECT колонку first_name, которой нет в GROUP BY. PostgreSQL такой запрос не выполнит.
Забывают про NULL: GROUP BY соберёт все пустые фамилии в одну группу, и она попадёт в «повторы», а IN по подзапросу эти строки уже не вернёт. Нужно явно решить, что делать с пустой фамилией.
В Go пропускают rows.Close() и rows.Err(). При раннем выходе из цикла соединение не возвращается в пул, а ошибка, прервавшая чтение на середине, теряется молча.