Одной из самых базовых и часто используемых операций в работе с реляционными базами данных является выборка списка сотрудников. Независимо от того, используете ли вы Microsoft SQL Server, MySQL, PostgreSQL или Oracle, синтаксис базового запроса остается практически идентичным благодаря стандарту SQL-92. В этой статье мы разберем основные подходы, нюансы соединения таблиц (JOIN) и примеры из реальной практики, основанные на актуальных обсуждениях разработчиков (данные на ).
Базовый синтаксис: SELECT * FROM table
Самый простой способ вывести всех сотрудников — обратиться к таблице, хранящей кадровые данные. Часто она называется Staff, Employees, Users или Personal.
SELECT * FROM Staff; Однако, как отмечается в профессиональной литературе (например, в курсах Coursehunters и книгах Алан Бьюли «Изучаем SQL»), использование звездочки (*) в продакшн-коде считается плохой практикой. Явное перечисление колонок делает запрос предсказуемее, быстрее (меньше трафика сети) и устойчивее к изменениям схемы БД.
Рекомендуемый вариант:
SELECT
T_number, -- Табельный номер (PK)
Lastname, -- Фамилия
Name, -- Имя
Surname, -- Отчество
Dept_id, -- ID отдела
Position_id -- ID должности
FROM Staff; Примечание: В примерах из сборников упражнений (Страница 2, Единое окно) часто используется таблица Staff с первичным ключом T_number и полями Name, Lastname, Surname.
Если в таблице могут дублироваться записи (например, история изменений зарплат в таблице Paies), для получения чистого списка людей используется ключевое слово DISTINCT.
Пример из учебных материалов (Упражнение 30):
SELECT DISTINCT
Name,
Lastname,
Surname
FROM Staff; Это гарантирует, что каждый сотрудник встретится в результате ровно один раз, даже если у него несколько записей в связанных таблицах (зарплаты, надбавки, истории переводов).
Связь с другими таблицами (JOIN): Отделы, Должности, Руководители
Реальные задачи почти всегда требуют не просто «ФИО», а контекст: название отдела, имя начальника, зарплатную статью. Здесь на помощь приходят JOIN.
Внутреннее соединение (INNER JOIN), только те, у кого есть связь
SELECT
s.Lastname,
s.Name,
s.Surname,
d.DeptName AS Department
FROM Staff s
INNER JOIN Departments d ON s.Dept_id = d.Dept_id; Левое соединение (LEFT JOIN) — все сотрудники, даже без отдела
Критически важно для HR-отчетов: если у сотрудника еще не проставлен отдел (Dept_id IS NULL), INNER JOIN его «съест». LEFT JOIN оставит строку, подставив NULL для полей отдела.
SELECT
s.Lastname,
s.Name,
d.DeptName
FROM Staff s
LEFT JOIN Departments d ON s.Dept_id = d.Dept_id; Самосоединение (Self-JOIN) — вывод руководителя
Частая задача с Stack Overflow (mysql, sql-server): вывести сотрудника и его начальника в одной строке. Таблица ссылается на саму себя через Chief_ID (или Manager_ID).
SELECT
e1.Lastname AS Employee_Lastname,
e1.Name AS Employee_Name,
e2.Lastname AS Manager_Lastname,
e2.Name AS Manager_Name
FROM Employee e1
LEFT JOIN Employee e2 ON e1.Chief_ID = e2.ID; Совет с Хабр QA (2022): «Никак не сделать в одну строку данные один-ко-многим без агрегации». Если у одного начальника 10 подчиненных, будет 10 строк. Для «одной строки на начальника» нужны STRING_AGG (PostgreSQL/SQL Server 2017+), GROUP_CONCAT (MySQL) или LISTAGG (Oracle).
Рекурсивные запросы (CTE) — иерархия подчинения
Задача: «Вывести всех сотрудников в виде: Менеджер — сотрудники, менеджер — сотрудники (одним списком)». Обсуждалась на Хабр QA и Киберфорум. Решается через Рекурсивное CTE (Common Table Expression).
Пример для SQL Server / PostgreSQL / Oracle (стандарт ANSI SQL):
WITH RECURSIVE Hierarchy AS (
-- Якорь: топ-менеджеры (у которых нет chefe)
SELECT
ID,
Lastname,
Name,
Chief_ID,
0 AS Level,
CAST(Lastname + ' ' + Name AS VARCHAR(MAX)) AS Path
FROM Employee
WHERE Chief_ID IS NULL
UNION ALL
-- Рекурсивная часть: подчиненные
SELECT
e.ID,
e.Lastname,
e.Name,
e.Chief_ID,
h.Level + 1,
CAST(h.Path + ' -> ' + e.Lastname + ' ' + e.Name AS VARCHAR(MAX))
FROM Employee e
INNER JOIN Hierarchy h ON e.Chief_ID = h.ID
)
SELECT * FROM Hierarchy ORDER BY Path; Важно (Microsoft Learn, ): «Так как существует два уровня в иерархии, следует написать два запроса SELECT и применить предложение UNION ALL». Это упрощенный подход для фиксированной глубины, но рекурсивное CTE универсальнее.
Фильтрация и бизнес-логика (WHERE)
Часто нужно вывести не «всех», а выборку: «сотрудники с вычетом за бездетность», «сотрудники главного подразделения» и т.д.
Пример из сборника упражнений (Упражнение 29, 30):
-- Сотрудники, имевшие в зарплате статью "вычет за бездетность"
SELECT DISTINCT
s.Name,
s.Lastname,
s.Surname
FROM Staff s
INNER JOIN Paies p ON s.T_number = p.T_number
INNER JOIN Items_pay ip ON p.Code_pay = ip.Code_pay
WHERE ip.Item_pay = 'за бездетность'; Проблема с подотделами (Stack Overflow, 2018):
Если нужно вывести сотрудников главного отдела, исключая подотделы:
SELECT s.*
FROM Staff s
INNER JOIN Depts d ON s.Dept_id = d.Dept_id
WHERE d.Parent_Dept_ID IS NULL; -- Только главные отделы Если убрать условие AND s.DeptName = 'Главное подразделение', запрос «раздувается» всеми подотделами. GROUP BY здесь не помогает, так как меняет гранулярность. Нужно правильно построить WHERE или использовать рекурсивное CTE для сбора дерева отделов.
Специфика СУБД: LIMIT / TOP / FETCH
Для вывода «первых N сотрудников» (например, для пагинации или теста) синтаксис разный:
- MySQL / PostgreSQL / SQLite:
SELECT * FROM Staff LIMIT 10; - SQL Server (до 2012):
SELECT TOP 10 * FROM Staff; - SQL Server (2012+) / Oracle 12c+ / PostgreSQL (стандарт):
SELECT * FROM Staff ORDER BY T_number OFFSET 0 ROWS FETCH NEXT 10 ROWS ONLY;
Продвинутые сценарии: Динамический SQL и временные таблицы
Согласно обсуждению на Хабр QA : «Только хранимая процедура, с временными таблицами и динамическим SQL. Про шанс выполнить эту задачу в виде одного запроса даже не заикайся». Это относится к задачам пивот-таблиц (Crosstab), когда колонки результата зависят от данных (например, «Вывести зарплаты за каждый месяц года колонками»). Для простого списка сотрудников это избыточно.
Чек-лист: Как написать идеальный запрос «Вывести всех сотрудников»
- Явно перечислите колонки (не используйте
*). - Используйте псевдонимы (алиасы) для таблиц (
s,d,m), читаемость и скорость парсинга. - Выбирайте правильный JOIN:
INNER JOIN— если связь обязательна (у всех есть отдел).LEFT JOIN— если связь опциональна (есть сотрудники без отдела/начальника).
- Добавьте ORDER BY (по фамилии, табельному номеру или иерархии), иначе порядок строк не гарантирован.
- Используйте DISTINCT только если есть риск дублей из-за JOIN (один-ко-многим), и вам нужен плоский список уникальных людей.
- Маскируйте ПДн (Персональные Данные) в логах и тестовых выгрузках:
*вместо телефонов, адресов, полных ФИО (если не нужно для отчета).
-- Универсальный запрос списка сотрудников с контекстом
SELECT
s;T_number AS TabNumber,
s.Lastname AS LastName,
s.Name AS FirstName,
s.Surname AS MiddleName,
d.DeptName AS Department,
pos.PositionName AS Position,
m.Lastname AS ManagerLastName,
m.Name AS ManagerFirstName
FROM Staff s
LEFT JOIN Departments d ON s.Dept_id = d.Dept_id
LEFT JOIN Positions pos ON s.Position_id = pos.Position_id
LEFT JOIN Staff m ON s.Manager_ID = m.T_number -- Self-join для руководителя
WHERE s.Is_Active = 1 -- Только действующие (если есть флаг)
ORDER BY d.DeptName, s.Lastname, s.Name; Резюме
Запрос SELECT * FROM Staff — это только вершина айсберга. Профессиональный подход подразумевает понимание структуры данных (PK/FK), выбор правильного типа соединения (JOIN), обработку иерархий (Recursive CTE) и учет специфики диалекта SQL вашей СУБД. Используйте приведенные шаблоны как отправную точку, адаптируя имена таблиц и колонок под вашу схему БД (например, для схемы HR_Schema на сервере ).
Статья подготовлена на основе актуальных данных сообществ Stack Overflow, Киберфорум, Хабр QA, Microsoft Learn и учебных материалов по SQL на .