Как определить, существует ли группа данных в таблице, учитывая данные, которые должны появиться в строках группы?
Я записываю данные в таблицу и выделяю "идентификатор группы" для каждой записываемой партии данных. Для иллюстрации рассмотрим следующую таблицу.
GroupId Value
------- -----
1 a
1 b
1 c
2 a
2 b
3 a
3 b
3 c
3 d
В этом примере есть три группы данных, каждая из которых имеет похожие, но разные значения.
Как я могу запросить эту таблицу, чтобы найти группу, которая содержит данный набор значений? Например, если я запрашиваю (a,b,c), результат должен быть группой 1. Аналогично, запрос для (b,a) должен приводить к группе 2, и запрос для (a, b, c, e) должен привести к пустому набору.
Я могу написать хранимую процедуру, которая выполняет следующие шаги:
- выберите отдельный GroupId из групп и сохраните его локально
- для каждого отдельного GroupId: выполнить разность наборов (
except
) между входными и табличными значениями (для группы) и наоборот - вернуть GroupId, если обе операции с разностями множеств дали пустые множества
Это кажется немного чрезмерным, и я надеюсь использовать некоторые другие команды в SQL для упрощения. Есть ли более простой способ выполнить сравнение наборов в этом контексте или выбрать идентификатор группы, который содержит точные входные значения для запроса?
2 ответа
Это запрос внутри набора. Мне нравится решать это с помощью group by
а также having
:
select groupid
from GroupValues gv
group by groupid
having sum(case when value = 'a' then 1 else 0 end) > 0 and
sum(case when value = 'b' then 1 else 0 end) > 0 and
sum(case when value = 'c' then 1 else 0 end) > 0 and
sum(case when value not in ('a', 'b', 'c') then 1 else - end) = 0;
Первые три условия в having
условие проверки, что каждый элемент существует. Последнее условие проверяет, что других значений нет. Этот метод довольно гибкий, для различных исключений и условий включения для значений, которые вы ищете.
РЕДАКТИРОВАТЬ:
Если вы хотите передать список, вы можете использовать:
with thelist as (
select 'a' as value union all
select 'b' union all
select 'c'
)
select groupid
from GroupValues gv left outer join
thelist
on gv.value = thelist.value
group by groupid
having count(distinct gv.value) = (select count(*) from thelist) and
count(distinct (case when gv.value = thelist.value then gv.value end)) = count(distinct gv.value);
Здесь having
Предложение подсчитывает количество совпадающих значений и гарантирует, что это тот же размер, что и список.
РЕДАКТИРОВАТЬ: ошибка компиляции запроса из-за отсутствия псевдонима таблицы. обновлено с правильным псевдонимом таблицы.
Это некрасиво, но это работает. На больших наборах данных я не уверен, как будет выглядеть производительность, но вложенные экземпляры #GroupValues
выключить GroupID
в основной таблице, так что я думаю, пока у вас есть хороший индекс на GroupID
это вероятно не было бы слишком ужасно.
If Object_ID('tempdb..#GroupValues') Is Not Null Drop Table #GroupValues
Create Table #GroupValues (GroupID Int, Val Varchar(10));
Insert #GroupValues (GroupID, Val)
Values (1,'a'),(1,'b'),(1,'c'),(2,'a'),(2,'b'),(3,'a'),(3,'b'),(3,'c'),(3,'d');
If Object_ID('tempdb..#FindValues') Is Not Null Drop Table #FindValues
Create Table #FindValues (Val Varchar(10));
Insert #FindValues (Val)
Values ('a'),('b'),('c');
Select Distinct gv.GroupID
From (Select Distinct GroupID
From #GroupValues) gv
Where Not Exists (Select 1
From #FindValues fv2
Where Not Exists (Select 1
From #GroupValues gv2
Where gv.GroupID = gv2.GroupID
And fv2.Val = gv2.Val))
And Not Exists (Select 1
From #GroupValues gv3
Where gv3.GroupID = gv.GroupID
And Not Exists (Select 1
From #FindValues fv3
Where gv3.Val = fv3.Val))