Выберите пользователей, принадлежащих только определенным отделам
У меня есть следующая таблица с двумя полями a и b, как показано ниже:
create table employe
(
empID varchar(10),
department varchar(10)
);
Вставка некоторых записей:
insert into employe values('A101','Z'),('A101','X'),('A101','Y'),('A102','Z'),('A102','X'),
('A103','Z'),('A103','Y'),('A104','X'),('A104','Y'),('A105','Z'),('A106','X');
select * from employe;
empID department
------------------
A101 Z
A101 X
A101 Y
A102 Z
A102 X
A103 Z
A103 Y
A104 X
A104 Y
A105 Z
A106 X
Примечание. Теперь я хочу показать сотрудника, который принадлежит только отделу Z
и Y
. Поэтому в соответствии с условием должен отображаться только сотрудник A103
, потому что он принадлежит только в отдел Z
и Y
. Но сотрудник A101
не должен появляться, потому что он принадлежит Z,X, and Y
.
Ожидаемый результат:
Если условие равно: Z
и Y
, тогда результат должен быть:
empID
------
A103
Если условие равно: Z
и X
, тогда результат должен быть:
empID
------
A102
Если условие: Z
, X
и Y
, тогда результат должен быть:
empID
------
A101
Примечание. Я хочу сделать это только в предложении where
(не хочу использовать предложения group by
и having
), потому что я собираюсь включить это один в другом where
.
Ответы
Ответ 1
Это Реляционный раздел без проблемы останова (RDNR). См. статью Dwain Camps, которая предоставляет множество решений для этой проблемы.
Первое решение
SQL Fiddle
SELECT empId
FROM (
SELECT
empID, cc = COUNT(DISTINCT department)
FROM employe
WHERE department IN('Y', 'Z')
GROUP BY empID
)t
WHERE
t.cc = 2
AND t.cc = (
SELECT COUNT(*)
FROM employe
WHERE empID = t.empID
)
Второе решение
SQL Fiddle
SELECT e.empId
FROM employe e
WHERE e.department IN('Y', 'Z')
GROUP BY e.empID
HAVING
COUNT(e.department) = 2
AND COUNT(e.department) = (SELECT COUNT(*) FROM employe WHERE empID = e.empId)
Без использования GROUP BY
и HAVING
:
SELECT DISTINCT e.empID
FROM employe e
WHERE
EXISTS(
SELECT 1 FROM employe WHERE department = 'Z' AND empID = e.empID
)
AND EXISTS(
SELECT 1 FROM employe WHERE department = 'Y' AND empID = e.empID
)
AND NOT EXISTS(
SELECT 1 FROM employe WHERE department NOT IN('Y', 'Z') AND empID = e.empID
)
Ответ 2
Я знаю, что на этот вопрос уже был дан ответ, но это была забавная проблема, и я пытался сделать это так, как никто другой. Преимущества моего заключается в том, что вы можете вводить любой список строк, если каждое значение имеет запятую после этого, и вам не нужно беспокоиться о проверке счетчиков.
Примечание: Значения должны быть перечислены в алфавитном порядке.
XML-решение с помощью CROSS APPLY
select DISTINCT empID
FROM employe A
CROSS APPLY
(
SELECT department + ','
FROM employe B
WHERE A.empID = B.empID
ORDER BY department
FOR XML PATH ('')
) CA(Deps)
WHERE deps = 'Y,Z,'
Результаты:
empID
----------
A103
Ответ 3
Для условия 1: z и y
select z.empID from (select empID from employe where department = 'z' ) as z
inner join (select empID from employe where department = 'y' ) as y
on z.empID = y.empID
where z.empID Not in(select empID from employe where department = 'x' )
Для условия 1: z и x
select z.empID from (select empID from employe where department = 'z' ) as z
inner join (select empID from employe where department = 'x' ) as x
on z.empID = x.empID
where z.empID Not in(select empID from employe where department = 'y' )
Для условия 1: z, y и x
select z.empID from (select empID from employe where department = 'z' ) as z
inner join (select empID from employe where department = 'x' ) as x
on z.empID = x.empID
inner join (select empID from employe where department = 'y' ) as y on
y.empID=Z.empID
Ответ 4
Вы можете использовать GROUP BY
с having
следующим образом. SQL Fiddle
SELECT empID
FROM employe
GROUP BY empID
HAVING SUM(CASE WHEN department= 'Y' THEN 1 ELSE 0 END) > 0
AND SUM(CASE WHEN department= 'Z' THEN 1 ELSE 0 END) > 0
AND SUM(CASE WHEN department NOT IN('Y','Z') THEN 1 ELSE 0 END) = 0
Без GROUP BY
и having
SELECT empID
FROM employe E1
WHERE (SELECT COUNT(DISTINCT department) FROM employe E2 WHERE E2.empid = E1.empid and department IN ('Z','Y')) = 2
EXCEPT
SELECT empID
FROM employe
WHERE department NOT IN ('Z','Y')
Если вы хотите использовать любой из вышеуказанных запросов с другими таблицами, используя соединение, вы можете использовать CTE или производную таблицу, подобную этой.
;WITH CTE AS
(
SELECT empID
FROM employe
GROUP BY empID
HAVING SUM(CASE WHEN department= 'Y' THEN 1 ELSE 0 END) > 0
AND SUM(CASE WHEN department= 'Z' THEN 1 ELSE 0 END) > 0
AND SUM(CASE WHEN department NOT IN('Y','Z') THEN 1 ELSE 0 END) = 0
)
SELECT cols from CTE join othertable on col_cte = col_othertable
Ответ 5
попробуйте это
select empID from employe
where empId in (select empId from employe
where department = 'Z' and department = 'Y')
and empId not in (select empId from employe
where department = 'X') ;
Ответ 6
для условия If: Z и Y
SELECT EMPID FROM EMPLOYE WHERE DEPARTMENT='Z' AND
EMPID IN (SELECT EMPID FROM EMPLOYE WHERE DEPARTMENT ='Y')AND
EMPID NOT IN(SELECT EMPID FROM EMPLOYE WHERE DEPARTMENT NOT IN ('Z','Y'))
Ответ 7
Следующий запрос работает, когда вы хотите, чтобы сотрудники из отделов "Y" и "Z", а не "X".
select empId from employe
where empId in (select empId from employe
where department = 'Z')
and empId in (select empId from employe
where department = 'Y')
and empId not in (select empId from employe
where department = 'X') ;
Для вашего второго случая просто замените not in
на in
в последнем условии.
Ответ 8
Попробуйте это,
SELECT a.empId
FROM employe a
INNER JOIN
(
SELECT empId
FROM employe
WHERE department IN ('X', 'Y', 'Z')
GROUP BY empId
HAVING COUNT(*) = 3
)b ON a.empId = b.empId
GROUP BY a.empId
Счет должен основываться на количестве условий.
Ответ 9
Вы также можете использовать GROUP BY
и HAVING
— вам просто нужно сделать это в подзапросе.
Например, начните с простого запроса, чтобы найти всех сотрудников в отделах X и Y (а не в каких-либо других отделах):
SELECT empID,
GROUP_CONCAT(DISTINCT department ORDER BY department ASC) AS depts
FROM emp_dept GROUP BY empID
HAVING depts = 'X,Y'
Я использовал MySQL GROUP_CONCAT()
как удобный ярлык здесь, но вы можете получить те же результаты без него, например, например:
SELECT empID,
COUNT(DISTINCT department) AS all_depts,
COUNT(DISTINCT CASE
WHEN department IN ('X', 'Y') THEN department ELSE NULL
END) AS wanted_depts
FROM emp_dept GROUP BY empID
HAVING all_depts = wanted_depts AND wanted_depts = 2
Теперь, чтобы комбинировать это с другим условием запроса, просто возьмите запрос, который включает другие условия, и присоедините таблицу своих сотрудников к выходу запроса выше:
SELECT empID, name, depts
FROM employees
JOIN (
SELECT empID,
GROUP_CONCAT(DISTINCT department ORDER BY department ASC) AS depts
FROM emp_dept GROUP BY empID
HAVING depts = 'X,Y'
) AS tmp USING (empID)
WHERE -- ...add other conditions here...
Здесь SQLFiddle демонстрирует этот запрос.
Ps. Причина, по которой вам следует использовать JOIN
вместо подзапроса IN
для этого, заключается в том, что MySQL не так хорош в оптимизации подзапросов IN
.
В частности (по крайней мере, по версии 5.5) MySQL всегда преобразует подзапросы IN
в зависимые подзапросы, так что подзапрос должен быть повторно выполнен для каждой строки внешнего запроса, даже если исходный подзапрос был независимым, Например, следующий запрос (из приведенной выше документации):
SELECT ... FROM t1 WHERE t1.a IN (SELECT b FROM t2);
эффективно преобразуется в:
SELECT ... FROM t1 WHERE EXISTS (SELECT 1 FROM t2 WHERE t2.b = t1.a);
Это может быть достаточно быстро, если t2
мал и/или имеет индекс, позволяющий быстро искать. Однако, если (например, в исходном примере выше) выполнение подзапроса может занять много работы, производительность может сильно пострадать. Использование JOIN
вместо этого позволяет подзапросу выполнять только один раз и, как правило, обеспечивает гораздо лучшую производительность.
Ответ 10
Как насчет самостоятельного присоединения? (ANSI Compliant - работал более 20 лет)
SELECT * FROM employee e JOIN employee e2 ON e.empid = e2.empid
WHERE e.department = 'x' AND e2.department ='y'
Это показывает, что a101 и a104 работают в обоих отделах.
Ответ 11
Решение с использованием предложения where
:
select distinct e.empID
from employe e
where exists( select *
from employe
where empID = e.empID
having count(department) = count(case when department in('Y','X','Z') then department end)
and count(distinct department) = 3)
exists
проверяет, имеются ли записи для определенного EmpId
, которые имеют общий счет department
, равный условному счету только соответствия department
и что он также равен числу department
, предоставленному in
. Также стоит упомянуть, что здесь мы применяем предложение having
без предложения group by
во всем наборе, но с уже указанным, только одним EmpId
.
SQLFiddle
Вы можете добиться этого без коррелированного подзапроса, но с предложением group by
:
select e.empId
from employe e
group by e.empID
having count(department) = count(case when department in('Y','X','Z') then department end)
and count(distinct department) = 3
SQLFiddle
Вы также можете использовать другой вариант предложения having
для запроса выше:
having count(case when department not in('Y','X', 'Z') then department end) = 0
and count(distinct case when department in('Y','X','Z') then department end) = 3
SQLFiddle
Ответ 12
В Postgres это можно упростить с помощью массивов:
select empid
from employee
group by empid
having array_agg(department order by department)::text[] = array['Y','Z'];
Важно сортировать элементы в array_agg()
и сравнивать их с отсортированным списком отделов в том же порядке. В противном случае это не вернет правильные ответы.
например. array_agg(department) = array['Z', 'Y']
может привести к неправильным результатам.
Это можно сделать более гибким способом, используя CTE для обеспечения отделов:
with depts_to_check (dept) as (
values ('Z'), ('Y')
)
select empid
from employee
group by empid
having array_agg(department order by department) = array(select dept from depts_to_check order by dept);
Таким образом, сортировка элементов всегда выполняется с помощью базы данных и будет согласована между значениями в агрегированном массиве и той, с которой она сравнивается.
Параметр со стандартным SQL должен проверить, имеет ли по меньшей мере одну строку другой раздел вместе с подсчетом всех строк
select empid
from employee
group by empid
having min(case when department in ('Y','Z') then 1 else 0 end) = 1
and count(case when department in ('Y','Z') then 1 end) = 2;
Вышеупомянутое решение не будет работать, если возможно, что один сотрудник назначается дважды в тот же отдел!
having min (...)
можно упростить в Postgres, используя агрегат bool_and()
.
При применении стандартного условия filter()
для выполнения условной агрегации это также может быть сделано для работы с ситуацией, когда работник может быть назначен на тот же отдел дважды
select empid
from employee
group by empid
having bool_and(department in ('Y','Z'))
and count(distinct department) filter (where department in ('Y','Z')) = 2;
bool_and(department in ('Y','Z'))
возвращает true, если условие истинно для строк all в группе.
Другим решением со стандартным SQL является использование пересечения между теми сотрудниками, которые имеют по крайней мере эти два отдела и те, которые назначены ровно в два отдела:
-- employees with at least those two departments
select empid
from employee
where department in name in ('Y','Z')
group by empid
having count(distinct department) = 2
intersect
-- employees with exactly two departments
select empid
from employee
group by empid
having count(distinct department) = 2;