2015-04-27 5 views
11

ich eine Tabelle von SalesDetails haben, wie folgt aussehen:Intersect Select-Anweisungen für bestimmte Spalten

InvoiceID, LineID, Product 

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

Meine Forderung ist Zeilen zurückgeben, wo eine Rechnung einen Umsatz von beiden enthalten: Apfel und Banane, aber wenn es andere Produkte auf einer solchen Rechnung, ich will diese nicht.

So sollte das Ergebnis sein:

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

Ich habe versucht, die folgenden:

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

nicht funktioniert, weil es scheint, Intersect alle Spalten übereinstimmen muss.

Was ich hoffe, zu tun ist:

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

Gibt es eine Möglichkeit, dies zu tun?

Oder muss ich zunächst auf InvoiceIDs Intersect nur meine Kriterien verwenden, wählen Sie dann die Reihen jener InvoiceIDs in denen die Kriterien wieder angepasst, das heißt:

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

die etwas verschwenderisch scheint, da sie die Kriterien der Prüfung ist zweimal.

+0

Welche ver sql-server benutzen sie? – Arion

+0

Machen Sie ein Self Join! – jarlh

+0

SQL Server 2014, Developer Edition – Xinneh

Antwort

3

Okay, dieses Mal habe ich es geschafft haben, die Wiederverwendung des Apple/Bananen-Informationen zu erhalten, indem ein CTE verwenden.

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

Ich mag diese Methode. Sie sollten jedoch 'in' anstelle von' or' verwenden, und ich bevorzuge 'exists' zu' in' (aber ich denke, letzteres ist eine Frage der Präferenz). –

+0

Ich stimme zu, dass ein "in" schöner ist. Ich habe nicht daran gedacht, bis ich meine Antwort gespeichert habe und dann deine gesehen und gedacht habe, hmm, ja, das ist schöner. Aber ich werde meine aktualisieren, um ein "in" anstelle eines oder zu verwenden. –

+1

@LeonBambrick Danke, dies liefert das Ergebnis, aber es ist immer noch das gleiche wie mein letzter Code, der funktioniert, aber untersucht die Kriterien zweimal. Nicht, dass das ein Problem wäre, ich habe mich nur gefragt, ob es einen besseren Weg dafür gibt. – Xinneh

2

Zuerst möchten Sie COUNT die Anzahl der Zeilen pro InvoiceID, die die Kriterien Product = 'Apple' or 'Banana' entsprechen. Dann machen Sie eine SELF-JOIN und filtern Sie die Zeilen so, dass die COUNT muss >= 2 oder die Anzahl der Product s in Ihrem critera sein.

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

Gibt es Rechnung 3 nicht zurück? Ich dachte es ist so. =) –

+0

nm. misread> = ad = aus irgendeinem Grund. – Taemyr

+0

Ich bin mir nicht sicher, ob das schneller ist als der Vorschlag von OP. – Taemyr

2

Tun Sie es mit bedingter Aggregation:

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

Andere war, ist PIVOT wie dies zu tun:

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; 

Es werden Ihnen die Ergebnisse in diesem Format , aber Sie können UNVPIVOT sie, wenn Sie wollen:

enter image description here

Oder können Sie window Funktion wie folgt verwendet werden:

;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

Hier ist ein Verfahren unter Verwendung von Fensterfunktionen:

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

denke ich, OP Vorschlag über die ist das Beste kann man machen. Der folgende könnte schneller sein, obwohl ich erwarte, dass der Unterschied gering ist und ich kein Benchmarking durchgeführt habe.

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

Funktioniert, um die erwarteten Ergebnisse zurückzugeben, aber dauert etwa 3 mal langsamer. Trotzdem, ich denke, das ist so optimiert wie es geht. – 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

Eine Selbstverknüpfung wird das Problem lösen.

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 *' sollte 'T1 wählen. *'. – Taemyr

+0

@qxg Das funktioniert, ist aber sehr langsam. – Xinneh

2

Wenn Sie nur die Bedingung einmal schreiben wollen und sind sicher, dass jedes Produkt nur einmal in jeder beliebigen Reihenfolge sein wird, können Sie diese verwenden:

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

Dies ist, was ich am Ende mit, inspiriert von @Leon Bambrick:

(Expanded ein wenig mehrere Produkte in den Kriterien unterstützen)

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 
Verwandte Themen