Для этого сценария существует ли единое самосоединение?
-
21-09-2019 - |
Вопрос
У меня есть таблица ALPHA с 2 полями groupId,Member:
GroupId | Member;
A1----------A;
A1----------B;
A1----------C;
A2----------A;
A2----------B;
A3----------A;
A3----------D;
A3----------E;
Цель: Учитывая входные данные - A, B, C - я должен запросить таблицу, чтобы узнать, существует ли groupId для этого точного набора участников.Итак, это то, что я планирую сделать:
- Запросите таблицу для всех идентификаторов групп, количество которых равно 3 (поскольку мой inpt равен A, B, C .. я знаю его 3).
- Это даст мне A1, A3.Теперь запросите этот набор для точного совпадения значений элементов..что даст мне A1.
Я планирую написать Хранимую процедуру и каким-то образом достичь поставленной цели.Но, мой вопрос, может ли это быть достигнуто с помощью одного запроса ... возможно, с помощью одного самостоятельного соединения.
Разъяснение:Набор (A,B, C) уникален для A1.И если ввести значение (A, B, C, D), запрос НЕ должен возвращать A1.
Решение
Ответы, приведенные до сих пор, предполагают, что поле Участника уникально для любого заданного groupId.В работе, которую я выполнял, это не так.А также, если в группе есть то, что вы ищете, плюс еще кое-что, вам нужно исключить эту группу.
SELECT
[Alpha].GroupID
FROM
[Alpha]
GROUP BY
[Alpha].GroupID
HAVING
SUM(CASE WHEN [alpha].Member IN ('A','B','C') THEN 1 ELSE 0 END) = 3
AND MIN(CASE WHEN [alpha].Member IN ('A','B','C') THEN 1 ELSE 0 END) = 1
Вы также можете заменить предложение IN на join on для таблицы, содержащей элементы, которые вы ищете...
SELECT
[Alpha].GroupID
FROM
[Alpha]
LEFT JOIN
[Search]
ON [Search].Member
GROUP BY
[Alpha].GroupID
HAVING
SUM(CASE WHEN [alpha].Member = [search].Member THEN 1 ELSE 0 END) = (SELECT COUNT(*) FROM [search])
AND MIN(CASE WHEN [alpha].Member = [search].Member THEN 1 ELSE 0 END) = 1
Другие советы
SELECT GroupID
FROM ALPHA
WHERE Member IN ('A', 'B', 'C')
GROUP BY GroupID
HAVING COUNT(*) = 3
Это зависит от того, что вы выписываете список участников в предложении IN и устанавливаете количество (различных) записей в списке участников в предложении HAVING.Если вы не можете сгенерировать SQL таким образом, то вам придется потрудиться еще усерднее.
Как отмечалось в раннем комментарии, это также зависит от интерпретации, согласно которой вам нужны группы, в которых все трое из A, B, C (и, возможно, некоторые другие) являются членами группы.Один из способов, не обязательно лучший способ, получить "где группа содержит ровно трех человек, а именно A, B, C", заключается в использовании:
SELECT GroupID
FROM ALPHA A1
WHERE Member IN ('A', 'B', 'C')
AND 3 = (SELECT COUNT(*) FROM ALPHA A2 WHERE A2.GroupID = A1.GroupID)
GROUP BY GroupID
HAVING COUNT(*) = 3
Это явно проверяет, что общее количество людей в группе равно 3 и что членами являются A, B и C (предполагая, что существует уникальное ограничение на Alpha (groupId, Member), так что участник не может быть дважды указан как принадлежащий к одной и той же группе).
SELECT DISTINCT aa.GroupId
FROM Alpha aa
JOIN Alpha ab ON (aa.GroupId = ab.GroupId)
JOIN Alpha ac ON (aa.GroupId = ac.GroupId)
LEFT OUTER JOIN Alpha ax ON (aa.GroupId = ax.GroupId AND ax.Member NOT IN ('A', 'B', 'C')
WHERE aa.Member = 'A' AND ab.Member = 'B' AND ac.Member = 'C'
AND ax.GroupId IS NULL;
Существуют также решения, включающие GROUP BY
но я нахожу, что JOIN
решение часто имеет более высокую производительность.Обычно я работаю в MySQL, и я понимаю, что MS SQL Server лучше группирует запросы.Поэтому попробуйте оба решения и посмотрите, что лучше всего подходит для той марки СУБД, которую вы используете.
попробуй это:
declare @YourTable table (GroupID char(2),Member char(1))
insert into @YourTable values ('A1','A')
insert into @YourTable values ('A1','B')
insert into @YourTable values ('A1','C')
insert into @YourTable values ('A2','A')
insert into @YourTable values ('A2','B')
insert into @YourTable values ('A3','A')
insert into @YourTable values ('A3','D')
insert into @YourTable values ('A3','E')
insert into @YourTable values ('A5','A')
insert into @YourTable values ('A5','B')
insert into @YourTable values ('A5','C')
insert into @YourTable values ('A5','D')
SELECT t1.GroupID
FROM @YourTable t1
LEFT OUTER JOIN @YourTable t2 ON t1.GroupID=t2.GroupID AND t2.Member NOT IN ('A', 'B', 'C')
WHERE t1.Member IN ('A', 'B', 'C')
AND t2.GroupID IS NULL
GROUP BY t1.GroupID
HAVING COUNT(*) = 3
ВЫХОДНОЙ СИГНАЛ:
GroupID
-------
A1
(1 row(s) affected)
Вот полное решение:
Прежде чем вы воспользуетесь моей функцией, вам нужно настроить "вспомогательную" таблицу, вам нужно сделать это только один раз для каждой базы данных:
CREATE TABLE Numbers
(Number int NOT NULL,
CONSTRAINT PK_Numbers PRIMARY KEY CLUSTERED (Number ASC)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
DECLARE @x int
SET @x=0
WHILE @x<8000
BEGIN
SET @x=@x+1
INSERT INTO Numbers VALUES (@x)
END
используйте эту функцию для разделения вашей строки, которая не зацикливается и выполняется очень быстро:
CREATE FUNCTION [dbo].[FN_ListToTable]
(
@SplitOn char(1) --REQUIRED, the character to split the @List string on
,@List varchar(8000) --REQUIRED, the list to split apart
)
RETURNS
@ParsedList table
(
ListValue varchar(500)
)
AS
BEGIN
/**
Takes the given @List string and splits it apart based on the given @SplitOn character.
A table is returned, one row per split item, with a column name "ListValue".
This function workes for fixed or variable lenght items.
Empty and null items will not be included in the results set.
Returns a table, one row per item in the list, with a column name "ListValue"
EXAMPLE:
----------
SELECT * FROM dbo.FN_ListToTable(',','1,12,123,1234,54321,6,A,*,|||,,,,B')
returns:
ListValue
-----------
1
12
123
1234
54321
6
A
*
|||
B
(10 row(s) affected)
**/
----------------
--SINGLE QUERY-- --this will not return empty rows
----------------
INSERT INTO @ParsedList
(ListValue)
SELECT
ListValue
FROM (SELECT
LTRIM(RTRIM(SUBSTRING(List2, number+1, CHARINDEX(@SplitOn, List2, number+1)-number - 1))) AS ListValue
FROM (
SELECT @SplitOn + @List + @SplitOn AS List2
) AS dt
INNER JOIN Numbers n ON n.Number < LEN(dt.List2)
WHERE SUBSTRING(List2, number, 1) = @SplitOn
) dt2
WHERE ListValue IS NOT NULL AND ListValue!=''
RETURN
END --Function FN_ListToTable
теперь вы можете использовать эту функцию следующим образом для запроса любого списка:
DECLARE @List varchar(100)
SET @List='A,B,C'
declare @YourTable table (GroupID char(2),Member char(1))
insert into @YourTable values ('A1','A')
insert into @YourTable values ('A1','B')
insert into @YourTable values ('A1','C')
insert into @YourTable values ('A2','A')
insert into @YourTable values ('A2','B')
insert into @YourTable values ('A3','A')
insert into @YourTable values ('A3','D')
insert into @YourTable values ('A3','E')
insert into @YourTable values ('A5','A')
insert into @YourTable values ('A5','B')
insert into @YourTable values ('A5','C')
insert into @YourTable values ('A5','D')
SELECT t1.GroupID
FROM @YourTable t1
LEFT OUTER JOIN @YourTable t2 ON t1.GroupID=t2.GroupID AND t2.Member NOT IN (SELECT ListValue FROM dbo.FN_ListToTable(',',@List))
WHERE t1.Member IN (SELECT ListValue FROM dbo.FN_ListToTable(',',@List))
AND t2.GroupID IS NULL
GROUP BY t1.GroupID
HAVING COUNT(*) = (SELECT COUNT(*) FROM dbo.FN_ListToTable(',',@List))
ВЫХОДНОЙ СИГНАЛ:
GroupID
-------
A1
выберите * из АЛЬФА где участник ( выберите участника из АЛЬФА сгруппируйте по участнику количество (*) = 3)
Попробуй вот это:
SELECT GroupId
FROM ALPHA
GROUP BY GroupId
HAVING SUM(CASE WHEN Member='A' THEN 1.0
WHEN Member='B' THEN 2.0
WHEN Member='C' THEN 4.0
ELSE 7.31415
END) = 7.0
Мое предложение состоит в том, чтобы разобрать эту строку с разделителями во временной таблице, а затем попробовать что-то вроде этого.
create table #temp(member varchar(10))
create table #groups
(
groupID varchar(2),
member char(1)
)
--#temp holds the members from your delimited string.
--#groups holds your relationships.
select distinct groupID
from #groups
where
(select count(*) from #groups i, #temp t
where i.member = t.member and i.groupID = #groups.groupID) =
(select count(*) from #temp)