T-SQL: поиск группы по ее членам - PullRequest
0 голосов
/ 29 октября 2009

Учитывая следующие две таблицы в SQL Server 2005:

IF EXISTS (SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = 'GroupItems')
    DROP TABLE GroupItems;
CREATE TABLE GroupItems (
    RowID INT IDENTITY(1,1) PRIMARY KEY
    , GroupID CHAR(1)
    , ItemID INT
);

IF EXISTS (SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = 'ItemList')
    DROP TABLE ItemList;
CREATE TABLE ItemList (
    ItemID INT PRIMARY KEY
)


INSERT GroupItems ( GroupID, ItemID )
SELECT 'A', 1
UNION SELECT 'A', 2
UNION SELECT 'A', 3
UNION SELECT 'A', 4
UNION SELECT 'B', 1
UNION SELECT 'B', 2
UNION SELECT 'B', 4
UNION SELECT 'C', 1
UNION SELECT 'C', 2
UNION SELECT 'D', 1
UNION SELECT 'D', 4
UNION SELECT 'D', 5


INSERT ItemList ( ItemID )
SELECT 1
UNION SELECT 2
UNION SELECT 4

Я пытаюсь найти GroupID (ы) из таблицы GroupItems, где ItemIDs точно соответствуют содержимому таблицы ItemList.

В данных выборки результатом должна быть группа 'B'.

Группа A отклонена, поскольку она содержит элемент, которого нет в таблице ItemList.

Группа C отклонена, поскольку она не содержит все элементы в таблице ItemList.

Группа D отклонена по обеим причинам.


В настоящее время я делаю что-то вроде

DECLARE @ListCount INT;
SELECT @ListCount = COUNT(*) FROM ItemList;

SELECT GI.GroupID FROM GroupItems AS GI
INNER JOIN ItemList AS IL ON IL.ItemID = GI.ItemID
INNER JOIN ( SELECT GroupID FROM GroupItems 
             GROUP BY GroupID
             HAVING COUNT(*) = @ListCOunt ) AS GS ON GS.GroupID = GI.GroupID  
GROUP BY GI.GroupID 
HAVING COUNT(*) = @ListCount;

Эта функция дает правильный результат, который я ищу, однако в моей производственной среде таблица GroupItems содержит сотни тысяч строк и тысячи уникальных GroupID. Таблица ItemList обычно содержит около десятка строк. Эта функция вызывается довольно регулярно. Я ищу более эффективный способ получить те же результаты.

Ответы [ 3 ]

0 голосов
/ 29 октября 2009

Предполагая:

  • Значение ItemID может быть только> 0
SELECT t.GroupID
FROM (
  SELECT GroupItems.GroupID
        ,count(1) as groupItemsCount 
        ,min(IsNull(ItemList.ItemID, -1)) as minVal
  FROM GroupItems
      LEFT JOIN ItemList
              ON (GroupItems.ItemID = ItemList.ItemID)
  GROUP BY GroupID
) t
WHERE t.groupItemsCount = (SELECT COUNT(1) FROM ItemList)
  AND (t.minVal > 0)
0 голосов
/ 29 октября 2009

Рассматривали ли вы создание индексированного представления для агрегирования подсчетов по групповым элементам?

CREATE VIEW GroupCounts (groupId, GroupCount) with SCHEMABINDING
AS
SELECT groupId, COUNT_BIG(1) /* I use 1 instead of asterisk by convention */
FROM GroupItems
GROUP BY groupId

CREATE CLUSTERED INDEX IX_GroupCounts on GroupCounts(groupId)

При этом вы можете использовать запрос, аналогичный тому, который у вас есть, но он должен иметь гораздо лучшую производительность.

SELECT GS.groupId FROM GroupItems AS GI
INNER JOIN ItemList AS IL ON IL.ItemID = GI.ItemID
INNER JOIN GroupCounts AS GS ON GS.GroupID = GI.GroupID  
GROUP BY GS.GroupID 
HAVING COUNT(1) = groupCount;
0 голосов
/ 29 октября 2009

Предполагая, что:

  • отсутствует соответствующая информация
  • ItemID - это PK, и, следовательно, уникальные
  • Вам не нужны групповые идентификаторы, в которых вы повторяли комбинации групп / элементов

Это должно работать:

select GroupID
from GroupItems
inner join ItemMaster
    on GroupItems.ItemID = ItemMaster.ItemID
inner join GroupMaster
    on GroupItems.GroupID = GroupMaster.GroupID
group by GroupID
having count(*) = (select count(*) from ItemList)

Если бы в GroupItems была гарантия уникальных комбинаций групп / элементов, объединение было бы ненужным.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...