2015-04-27 12 views
11

Ho una tabella di SalesDetails, guardando come questo:Intersect select su colonne specifiche

InvoiceID, LineID, Product 

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

La mia esigenza è quella di restituire le righe in cui una fattura contenente le vendite di entrambi: mela e banana, ma se ci sono altri prodotti su tale fattura, non voglio quelli.

Così il risultato dovrebbe essere:

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

ho provato la seguente:

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

non ha funzionato, perché sembra Intersect deve corrispondere tutte le colonne.

Quello che sto sperando di fare è:

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

C'è un modo per fare questo?

O devo Intersezione prima a InvoiceIDs solo con i miei criteri, quindi selezionare le file di quei InvoiceIDs in cui i criteri si accompagna ancora una volta, 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' 
) 

Che sembra un po 'uno spreco come è esaminare i criteri due volte.

+0

Quale ver stai usando SQL Server? – Arion

+0

Partecipa! – jarlh

+0

SQL Server 2014, Developer Edition – Xinneh

risposta

3

Va bene questa volta sono riuscito a ottenere il riutilizzo delle informazioni Apple/Banana utilizzando un 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

+1

Mi piace questo metodo. Comunque, dovresti usare 'in' invece di' or', e preferisco 'exists' a' in' (ma penso che quest'ultimo sia una questione di preferenza). –

+0

Sono d'accordo che un 'in' è più bello. Non ci ho pensato fino a quando non ho salvato la mia risposta e poi ho visto i tuoi e ho pensato, hmm, sì è più bello. Ma aggiornerò il mio per usare un 'in' invece di un o. –

+1

@LeonBambrick Grazie, questo fornisce il risultato, ma è sempre lo stesso del mio ultimo codice, che funziona, ma esamina due volte il criterio. Non che quello sia un problema, mi stavo chiedendo se c'era un modo migliore per farlo. – Xinneh

2

In primo luogo, si desidera COUNT il numero di righe per InvoiceID che corrisponde ai criteri Product = 'Apple' or 'Banana'. Quindi fai un SELF-JOIN e filtra le righe in modo che lo COUNT debba essere >= 2 o il numero di Product s nella tua critica.

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

Non restituisce la fattura 3? Ho pensato che lo fosse =) –

+0

nm. fraintendimento> = ad = per qualche motivo. – Taemyr

+0

Anche se non sono sicuro se questo è più veloce del suggerimento di OP. – Taemyr

2

Farlo con l'aggregazione condizionale:

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

altro è stato è quello di fare PIVOT in questo modo:

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; 

Vi darà i risultati in questo formato , ma sei in grado di effettuare il UNVPIVOT se vuoi:

enter image description here

Oppure si può utilizzare window funzione come questa:

;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

Ecco un metodo che utilizza funzioni finestra:

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

Penso suggerimento di OP è di circa il meglio si può fare. Quanto segue potrebbe essere più veloce, anche se mi aspetto che la differenza sia lieve e non ho fatto alcun benchmarking.

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

Funziona per restituire i risultati previsti, ma richiede circa 3 volte più lentamente. Grazie comunque, immagino che questo sia ottimizzato come ottiene. – 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

Un self-join risolverà il problema.

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

'Selezionare *' dovrebbe essere 'selezionare T1. *'. – Taemyr

+0

@qxg Funziona, ma è MOLTO lento. – Xinneh

2

Se desideri solo scrivere la condizione una volta è certi che ogni prodotto sarà solo una volta in qualsiasi ordine, è possibile utilizzare questo:

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

Questo è quello che ho finito per usare, ispirato da @Leon Bambrick:

(ampliato un po 'di supportare più prodotti nei criteri)

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 
Problemi correlati