2015-04-27 4 views
11

Я таблицу SalesDetails, глядя, как это:Intersect Выберите отчеты об отдельных столбцах

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 и банан, но если есть другие продукты на такой счет-фактуре, я не хочу их.

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

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' 

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

Или я должен первым Intersect на InvoiceIDs только используя мои критерии, а затем выберите строки тех InvoiceIDs где критерии совпавших снова, Ie:

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' 
) 

Который кажется несколько расточительно, как это рассматривает критерии дважды.

+0

Какие версии sion sql-server вы используете? – Arion

+0

Присоединяйтесь! – jarlh

+0

SQL Server 2014, Developer Edition – Xinneh

ответ

3

Хорошо на этот раз я сумел получить повторное использование информации Apple/Banana с помощью КТР.

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

+1

Мне нравится этот метод. Тем не менее, вы должны использовать 'in' вместо' or', и я предпочитаю 'exists'' in' (но я думаю, что последнее относится к предпочтению). –

+0

Я согласен, что «в» лучше. Я не думал об этом, пока не спас свой ответ, а потом увидел твое и подумал, да, да, это лучше. Но я буду обновлять мой, чтобы использовать «in» вместо a. –

+1

@LeonBambrick Спасибо, это дает результат, но он по-прежнему совпадает с моим последним кодом, который работает, но дважды проверяет критерии. Не то, что это проблема, мне просто интересно, есть ли лучший способ сделать это. – Xinneh

2

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

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') 
+0

Не возвращается ли счет-фактура 3? Я так и думал. =) –

+0

нм. misread> = ad = по какой-то причине. – Taemyr

+0

Хотя я не уверен, что это быстрее, чем предложение OP. – Taemyr

2

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

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) 
2

Другой был это сделать 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 
2

Вот метод, использующий оконные функции:

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); 
2
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 
3

Я думаю, что предложение 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' 
) 
+0

Работает, чтобы вернуть ожидаемые результаты, но занимает примерно 3 раза медленнее. Спасибо, так или иначе, я думаю, это так же оптимизировано, как и получается. – Xinneh

2
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

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') 
+0

'Select *' должен быть 'select T1. *'. – Taemyr

+0

@qxg Это работает, но ОЧЕНЬ медленно. – Xinneh

2

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

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

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

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

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 
Смежные вопросы