Пересечение Выберите "Выписки" в конкретных столбцах

У меня есть таблица SalesDetails, выглядящая так:

InvoiceID, LineID, Product

1,1,Apple
1,2,Banana
2,1,Apple
2,2,Mango
3,1,Apple
3,2,Banana
3,3,Mango

Мое требование состоит в том, чтобы возвращать строки, где в счете-фактуре были продажи обоих: Apple AND Banana, но если в таком счете есть другие продукты, я не хочу их.

Таким образом, результат должен быть:

1,1,Apple
1,2,Banana
3,1,Apple
3,2,Banana

Я попробовал следующее:

Select * from SalesDetails where Product = 'Apple'
Intersect
Select * from SalesDetails where Product = 'Banana'

Не работает, потому что кажется, что Intersect должен соответствовать всем столбцам.

Что я надеюсь сделать:

Select * from SalesDetails where Product = 'Apple'
Intersect ----On InvoiceID-----
Select * from SalesDetails where Product = 'Banana'

Есть ли способ сделать это?

Или мне нужно сначала пересечься по InvoiceIDs только с использованием моих критериев, а затем выбрать строки из этих InvoiceID, где критерии снова совпадают, I.e.:

Select * From SalesDetails
Where Product In ('Apple', 'Banana') And InvoiceID In 
  (
  Select InvoiceID from SalesDetails where Product = 'Apple'
  Intersect
  Select InvoiceID from SalesDetails where Product = 'Banana'
  )

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

Ответ 1

Хорошо, на этот раз мне удалось повторно использовать информацию Apple/Banana с помощью CTE.

with sd as (
Select * from SalesDetails 
where (Product in ('Apple', 'Banana'))
)
Select * from sd where invoiceid in (Select invoiceid from 
  sd group by invoiceid having Count(distinct product) = 2)

SQL Fiddle

Ответ 2

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

Select * From SalesDetails
Where Product ='Apple' And InvoiceID In
(
Select InvoiceID from SalesDetails where Product = 'Banana'
)
union all
select * from SalesDetails
Where Product ='Banana' And InvoiceID In
(
Select InvoiceID from SalesDetails where Product = 'Apple'
)

Ответ 3

Самосоединение решит проблему.

SELECT T1.*
FROM SalesDetails T1 
INNER JOIN SalesDetails T2 ON T1.InvoiceId = T2.InvoiceId
   AND (T1.Product = 'Apple' AND T2.Product = 'Banana'
     OR T1.Product = 'Banana' AND t2.Product = 'Apple')

Ответ 4

Сначала вы хотите COUNT количество строк на InvoiceID, которые соответствуют критериям Product = 'Apple' or 'Banana'. Затем выполните SELF-JOIN и отфильтруйте строки таким образом, чтобы COUNT должен быть >= 2 или число Product в вашем критерии.

SQL Fiddle

SELECT sd.*
FROM (
    SELECT InvoiceID, CC = COUNT(*)
    FROM SalesDetails
    WHERE Product IN('Apple', 'Banana')
    GROUP BY InvoiceID
)t
INNER JOIN SalesDetails sd
    ON sd.InvoiceID = t.InvoiceID
WHERE
    t.CC >= 2
    AND sd.Product IN('Apple', 'Banana')

Ответ 5

Сделайте это с условной агрегацией:

select * 
from SalesDetails
where product in ('apple', 'banana') and invoiceid in(
select invoiceid
from SalesDetails
group by invoiceid
having sum(case when product in('apple', 'banana') then 1 else 0 end) >= 2)

Ответ 6

Другим было сделать PIVOT следующим образом:

DECLARE @DataSource TABLE
(
    [InvoiceID] TINYINT
   ,[LineID] TINYINT
   ,[Product] VARCHAR(12)
);

INSERT INTO @DataSource ([InvoiceID], [LineID], [Product])
VALUES (1,1,'Apple')
      ,(1,2,'Banana')
      ,(2,1,'Apple')
      ,(2,2,'Mango')
      ,(3,1,'Apple')
      ,(3,2,'Banana')
      ,(3,3,'Mango');

SELECT *
FROM @DataSource
PIVOT
(
    MAX([LineID]) FOR [Product] IN ([Apple], [Banana])
) PVT
WHERE [Apple] IS NOT NULL 
    AND [Banana] IS NOT NULL;

Он даст вам результаты в этом формате, но вы можете UNVPIVOT их, если хотите:

enter image description here

Или вы можете использовать функцию window следующим образом:

;WITH DataSource AS
(
    SELECT  *
            ,SUM(1) OVER (PARTITION BY [InvoiceID]) AS [Match]
    FROM @DataSource
    WHERE [Product] = 'Apple' OR [Product] = 'Banana'
)
SELECT *
FROM DataSource
WHERE [Match] =2 

Ответ 7

Вот метод с использованием оконных функций:

select sd.*
from (select sd.*,
             max(case when product = 'Apple' then 1 else 0 end) over (partition by invoiceid) as HasApple,
             max(case when product = 'Banana' then 1 else 0 end) over (partition by invoiceid) as HasBanana
      from salesdetails sd
     ) sd
where (product = 'Apple' and HasBanana > 0) or
      (product = 'Banana' and HasApple > 0);

Ответ 8

declare @t table (Id int,val int,name varchar(10))
insert into @t (id,val,name)values
 (1,1,'Apple'),
(1,2,'Banana'),
(2,1,'Apple'),
(2,2,'Mango'),
(3,1,'Apple'),
(3,2,'Banana'),
(3,3,'Mango')
;with cte as (
select ID,val,name,ROW_NUMBER()OVER (PARTITION BY id ORDER BY val)RN from @t)
,cte2 AS(
select TOP 1 c.Id,c.val,c.name,C.RN from cte c
WHERE RN = 1
UNION ALL
select  c.Id,c.val,c.name,C.RN from cte c
WHERE c.Id <> c.val)
select Id,val,name from (
select Id,val,name,COUNT(RN)OVER (PARTITION BY Id )R from cte2 )R
WHERE R = 2

Ответ 9

WITH cte
AS
(
SELECT  *
FROM [dbo].[SalesDetails]
WHERE [Product]='banana')
,cte1
AS
(SELECT  *
FROM [dbo].[SalesDetails]
WHERE [Product]='apple')

SELECT *
FROM cte c INNER JOIN cte1 c1
ON  c.[InvoiceID]=c1.[InvoiceID]

enter image description here

Ответ 10

Если вы хотите только один раз написать условие и убедитесь, что каждый Продукт будет только один раз в любом порядке, вы можете использовать это:

SELECT * FROM (
  SELECT InvoiceID, Product
        ,COUNT(*) OVER (PARTITION BY InvoiceID) matchcount
  FROM SalesDetails
WHERE Product IN ('Apple','Banana') ) WHERE matchcount = 2;

Ответ 11

Это то, что я в конечном итоге использовал, вдохновленный @Leon Bambrick:

(немного расширен для поддержки нескольких продуктов в критериях)

WITH cteUnionBase AS
    (SELECT * FROM SalesDetails
        WHERE Product IN ('Apple Red','Apple Yellow','Apple Green','Banana Small','Banana Large')),
cteBanana AS
    (SELECT * FROM cteUnionBase
        WHERE Product IN ('Banana Small','Banana Large')),
cteApple AS
    (SELECT * FROM cteUnionBase
        WHERE Product IN ('Apple Red','Apple Yellow','Apple Green')),
cteIntersect AS
    (
    SELECT InvoiceID FROM cteApple
    Intersect
    SELECT InvoiceID FROM cteBanana
    )
SELECT cteUnionBase.* 
FROM cteUnionBase INNER JOIN cteIntersect
                        on cteUnionBase.InvoiceID = cteIntersect.InvoiceID