Оператор SQL для выбора группы, содержащей весь набор значений - PullRequest
6 голосов
/ 30 июня 2010

В SQL Server 2005 у меня есть таблица сведений о заказе с идентификатором заказа и идентификатором продукта. Я хочу написать заявление SQL, который находит все заказы, которые имеют все элементы в конкретном заказе. Итак, если у заказа 5 есть пункты 1, 2 и 3, я бы хотел, чтобы все другие ордера также имели 1, 2 и 3. Кроме того, если бы у заказа 5 было 2 дважды и 3, я бы хотел, чтобы все остальные заказы два 2 и 3.

Я предпочитаю, чтобы он возвращал ордера, которые точно совпадают, но ордера, которые являются надмножеством, приемлемы, если это намного проще / работает намного лучше.

Я попытался выполнить самостоятельное соединение, как показано ниже, но при этом были найдены заказы с любыми предметами вместо всех предметов.

SELECT * FROM Order O1
JOIN Order O2 ON (O1.ProductId = O2.ProductId)
WHERE O2.OrderId = 5

Это также дало мне дубликаты, если в заказе 5 содержался один и тот же предмет дважды.

Ответы [ 3 ]

3 голосов
/ 30 июня 2010

Если таблица OrderDetails содержит уникальные ограничения для OrderId и ProductId, то вы можете сделать что-то вроде этого:

Select ...
From Orders As O
Where Exists    (
                Select 1
                From OrderDetails As OD1
                Where OD1.ProductId In(1,2,3)
                    And OD1.OrderId = O.Id
                Group By OD1.OrderId
                Having Count(*) = 3
                )

Если возможно иметь один и тот же ProductId в одном и том же Order несколько раз, тоВы можете изменить предложение Имеющееся на Count(Distinct ProductId) = 3

Теперь, учитывая вышеизложенное, если вы хотите, чтобы ситуация, когда каждый заказ имел одинаковую подпись с дублирующимися записями о товаре, является более хитрой.Для этого вам понадобится подпись соответствующего заказа на рассматриваемые продукты, а затем запросить эту подпись:

With OrderSignatures As
    (
    Select O1.Id
        ,   (
            Select '|' + Cast(OD1.ProductId As varchar(10))
            From OrderDetails As OD1
            Where OD1.OrderId = O1.Id
            Order By OD1.ProductId
            For Xml Path('')
            ) As Signature
    From Orders As O1
    )
Select ...
From OrderSignatures As O
    Join OrderSignatures As O2
        On O2.Signature = O.Signature
            And O2.Id <> O.Id
Where O.Id = 5
1 голос
/ 30 июня 2010

Я думаю, что это должно работать.Я использую 108 в качестве примера OrderID, поэтому вам придется заменить его дважды ниже или использовать переменную.

WITH TempProducts(ProductID) AS
(
   SELECT DISTINCT ProductID FROM CompMarket
   WHERE OrderID = 108
)
SELECT OrderID  FROM CompMarket 
WHERE ProductID IN (SELECT ProductID FROM TempProducts) 
AND OrderID != 108
GROUP BY OrderID
HAVING COUNT(DISTINCT ProductID) >= (SELECT COUNT(ProductID) FROM TempProducts)

При этом используется CTE для получения списка продуктов Заказа, затем выбираются всеидентификаторы заказа, у которых есть продукты, которые находятся в этом списке.Чтобы убедиться, что в возвращенных Заказах есть все продукты, сравнивается количество CTE с количеством Продуктов возвращенного Заказа.

1 голос
/ 30 июня 2010

Такого рода вещи очень трудно сделать в SQL, поскольку SQL предназначен для генерации своего результирующего набора путем сравнения базового значения столбца в одной строке с другим значением на самом базовом уровне. Вы пытаетесь сравнить одно значение столбца (или набор значений столбца) на несколько строк с другим набором несколько строк .

Для этого вам нужно создать какую-то подпись заказа. Строго говоря, это невозможно сделать, используя только синтаксис запроса; вам придется использовать немного T-SQL.

declare @Orders table 
(
    idx int identity(1, 1), 
    OrderID int, 
    Signature varchar(MAX)
)
declare @Items table 
(
    idx int identity(1, 1), 
    ItemID int, 
    Quantity int
)

insert into @Orders (OrderID) select OrderID from [Order]

declare @i int
declare @cnt int

declare @j int
declare @cnt2 int

select @i = 0, @cnt = max(idx) from @Orders

while @i < @cnt
begin
    select @i = @i + 1

    declare @temp varchar(MAX)

    delete @Items

    insert into @Items (ItemID, Quantity)
    select 
        ItemID, 
        Count(ItemID) 

    from OrderItem oi    

    join @Orders o on o.idx = @i and o.OrderID = oi.OrderID

    group by oi.ItemID

    order by oi.ItemID

    select @j = min(idx) - 1, @cnt2 = max(idx) from @Items

    while @j < @cnt2
    begin
        select @j = @j + 1

        select @temp = isnull(@temp + ', ','') + 
            '(' + 
            convert(varchar,i.ItemID) + 
            ',' + 
            convert(varchar, i.Quantity) + 
            ')'
        from @Items i where idx = @j
    end

    update @Orders set Signature = @temp where idx = @i

    select @temp = null
end

select 
    o_other.OrderID 

from @Orders o

join @Orders o_other on 
        o_other.Signature = o.Signature
    and o_other.OrderID <> o.OrderID

where o.OrderID = @OrderID

Предполагается (в зависимости от формулировки вашего вопроса), что упорядочение нескольких одинаковых элементов в заказе приведет к появлению нескольких строк, а не к столбцу Quantity. В последнем случае просто удалите group by из запроса на заполнение @Items и замените Count(ItemID) на Quantity.

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