2010-08-11 14 views
12

Ho due tabelle tabData e tabDataDetail. Voglio che tutti idData (PK) da Parent-Table (tabdata) che hanno solo righe in Child-Table (tabDataDetail, FK è fiData) con:SQL-Query: EXISTS in Sottostruttura

  • fiActionCode = 11 solo o
  • fiactionCode = 11 e fiActionCode = 34

Qualsiasi altra combinazione non è valida. Come ottenerli?

quello che ho provato senza successo (lento e mi dà anche righe che hanno solo fiActioncode 34):

alt text http://www.bilder-hochladen.net/files/4709-l0.jpg

Grazie per il vostro tempo.


EDIT: Grazie a tutti per le loro risposte. Purtroppo sfortunatamente non ho abbastanza tempo per controllare qual è il migliore o funziona affatto. Ho segnato il primo lavoro come risposta.

EDIT2: penso che la risposta marcata sia davvero la soluzione più efficiente e compatta.

EDIT3: La risposta di Codesleuth è interessante perché restituisce solo le righe che hanno solo un singolo fiActionCode = 11. Difficile da vedere, perché è vero solo per 20 tabDataDetail: righe di 41524189 righe totali che ne hanno due. Comunque non era al 100% quello che ho chiesto o piuttosto quello che stavo cercando.

+0

Non viene mai menzionato quali colonne sono necessarie nell'output. Sono solo le colonne tabData o sono necessari i dati di tabDataDetail? – Thomas

+0

È solo la chiave primaria (idData) che mi interessa e deve essere raggruppata (e ordinata) da (se necessario). Ma per verificare il risultato è meglio avere anche fiActionCode. –

risposta

5
Select ... 
From tabData As T1 
Where Exists (
       Select 1 
       From tabDataDetail As TDD1 
       Where TDD1.fiData = T1.idData 
        And TDD1.fiactionCode = 11 
       ) 
    And Not Exists (
         Select 1 
         From tabDataDetail As TDD1 
         Where TDD1.fiData = T1.idData 
          And TDD1.fiactionCode Not In(11,34) 
        ) 

Per espandere la mia logica, il primo controllo (una correzione) è garantire che una riga con fiActionCode = 11 esista. Il secondo controllo funziona definendo innanzitutto l'insieme di righe che non vogliamo. Non vogliamo nulla che sia diverso da fiActionCode = 11 o 34. Poiché questo è l'insieme di elementi che non vogliamo, cerchiamo qualsiasi cosa che non esiste in quel set.

+1

Grazie. Ma questo mi dà anche le righe che hanno solo 34 come fiActionCode in Childtable. Questi dovrebbero essere esclusi. –

+0

@Tim - Risolto. Serve solo un controllo aggiuntivo per garantire che fiActionCode = 11 esista. – Thomas

+0

PK è idData e tablename è tabData ma a parte questo sembra funzionare (non so perché). Ottengo 400k righe quindi è difficile da controllare. –

1

Modifica: Apols: capisco cosa intendi con righe secondarie. Questo non è particolarmente efficiente. Grazie anche a Lieven per i dati.

SELECT idData FROM 
tabData td 
WHERE EXISTS 
(
    SELECT 1 
     FROM tabDataDetail tdd 
     WHERE tdd.fiData = td.idData AND fiActionCode = 11 
) 
AND NOT EXISTS 
(
    SELECT 1 
     FROM tabDataDetail tdd 
     WHERE tdd.fiData = td.idData AND fiActionCode <> 11 
) 
UNION 
SELECT idData 
    FROM tabData td 
    WHERE EXISTS 
    (
     SELECT 1 
      FROM tabDataDetail tdd 
      WHERE tdd.fiData = td.idData AND fiActionCode = 11 
    ) 
    AND EXISTS 
    (
     SELECT 1 
      FROM tabDataDetail tdd 
      WHERE tdd.fiData = td.idData AND fiActionCode = 34 
    ) 
AND NOT EXISTS 
(
    SELECT 1 
     FROM tabDataDetail tdd 
     WHERE tdd.fiData = td.idData AND fiActionCode NOT IN (11, 34) 
) 
+0

Anche questo funziona. –

4

Ragionamento

  1. LEFT OUTER JOIN esclude tutti idData di che hanno un id diverso da 11 o 34
  2. HAVING esclude tutti idData di che solo hanno un record di 34
  3. rimanenti (dovrebbe) soddisfare tutti i vincoli

I dati dei test

DECLARE @tabData TABLE (idData INTEGER) 
DECLARE @tabDataDetail TABLE (fiData INTEGER, fiActionCode INTEGER) 

INSERT INTO @tabData VALUES (1) 
INSERT INTO @tabData VALUES (2) 
INSERT INTO @tabData VALUES (3) 
INSERT INTO @tabData VALUES (4) 
INSERT INTO @tabData VALUES (5) 

/* Only idData 1 & 2 should be returned */ 
INSERT INTO @tabDataDetail VALUES (1, 11) 
INSERT INTO @tabDataDetail VALUES (2, 11) 
INSERT INTO @tabDataDetail VALUES (2, 34) 
INSERT INTO @tabDataDetail VALUES (3, 99) 
INSERT INTO @tabDataDetail VALUES (4, 11) 
INSERT INTO @tabDataDetail VALUES (4, 99) 
INSERT INTO @tabDataDetail VALUES (5, 34) 

Query

SELECT * 
FROM @tabData d 
     INNER JOIN @tabDataDetail dd ON dd.fiData = d.idData 
     INNER JOIN (
      SELECT idData 
      FROM @tabData d 
        INNER JOIN @tabDataDetail dd ON dd.fiData = d.idData 
        LEFT OUTER JOIN (
        SELECT fiData 
        FROM @tabDataDetail 
        WHERE fiActionCode NOT IN (11, 34) 
       ) exclude ON exclude.fiData = d.idData 
      WHERE exclude.fiData IS NULL     
      GROUP BY 
        idData 
      HAVING MIN(fiActionCode) = 11   
     ) include ON include.idData = d.idData 
+0

Grazie, ma ottengo diversi "Non è possibile associare l'identificatore di più parti" d.idData ".'e' Nome colonna ambiguo ' –

+0

Hai rimosso @? La query viene eseguita senza problemi sul mio sistema. –

+0

Anche lavori (un po 'lenti). Grazie a –

1

cura la mia risposta sulla base di chiarimento dato in commenti su altre risposte.

select td.idData 
from tabData td 
    left join tabDataDetail tdd 
    on td.idData = tdd.fiData 
    and tdd.fiActionCode = 11 
    left join tabDataDetail tdd2 
    on td.idData = tdd2.fiData 
    and tdd2.fiActionCode = 34 
    left join tabDataDetail tdd3 
    on td.idData = tdd3.fiData 
    and tdd3.fiActionCode not in (11,34) 
where (tdd.fiData is not null 
    or (tdd.fiData is not null and tdd2.fiData is not null)) 
    and tdd3.fiData is null 
group by td.idData 
+0

Anche risultato corretto. Questo metodo ("null-check") è nuovo per me. –

1

Grazie @Lieven per il codice dati per verificare questo:

DECLARE @tabData TABLE (idData INTEGER) 
DECLARE @tabDataDetail TABLE (idDataDetail int IDENTITY(1,1), 
    fiData INTEGER, fiActionCode INTEGER) 

INSERT INTO @tabData VALUES (1) 
INSERT INTO @tabData VALUES (2) 
INSERT INTO @tabData VALUES (3) 
INSERT INTO @tabData VALUES (4) 
INSERT INTO @tabData VALUES (5) 

/* Only idData 1 & 2 should be returned */ 
INSERT INTO @tabDataDetail (fiData,fiActionCode) VALUES (1, 11) 
INSERT INTO @tabDataDetail (fiData,fiActionCode) VALUES (2, 11) 
INSERT INTO @tabDataDetail (fiData,fiActionCode) VALUES (2, 34) 
INSERT INTO @tabDataDetail (fiData,fiActionCode) VALUES (3, 99) 
INSERT INTO @tabDataDetail (fiData,fiActionCode) VALUES (4, 11) 
INSERT INTO @tabDataDetail (fiData,fiActionCode) VALUES (4, 99) 
INSERT INTO @tabDataDetail (fiData,fiActionCode) VALUES (5, 34) 

Query:

SELECT td.idData 
FROM @tabData td 
     INNER JOIN @tabDataDetail tdd ON td.idData = tdd.fiData 
WHERE tdd.fiActionCode = 11 -- check 11 exists 
     AND NOT EXISTS (SELECT * FROM @tabDataDetail WHERE fiData = td.idData 
          AND idDataDetail <> tdd.idDataDetail) 
      -- ensures *only* 11 exists (0 results from subquery) 
UNION 
SELECT td.idData 
FROM @tabData td 
     INNER JOIN @tabDataDetail tdd1 ON td.idData = tdd1.fiData 
     INNER JOIN @tabDataDetail tdd2 ON td.idData = tdd2.fiData 
WHERE tdd1.fiActionCode = 11 -- check 11 exists 
     AND tdd2.fiActionCode = 34 -- check 34 exists 

Ritorni:

idData 
----------- 
1 
2 

(2 row(s) affected)

Con solo 1 subquery qui (e essendo un COUNT invece di un molto lenta NOT EXISTS questo crea un piano di esecuzione molto accurato che dovrebbe essere d'aiuto se hai problemi di velocità.

+0

@CodeSleuth, non dirlo. Il costo in base all'analizzatore di prestazioni è del 24% per la mia soluzione, il 32% per la tua. Mi interessa sapere in uno scenario del mondo reale se questo vale. –

+0

Non ho davvero guardato la domanda o questa risposta in alcun dettaglio, ma come punto generale 'NOT EXISTS' è più efficiente di' COUNT (*) 'in SQL Server come fa un anti semi join. Basta controllare che non esista una riga corrispondente. Non contare tutti quelli che corrispondono. –

+0

@Lieven: costo di cosa? Solo la sottoquery? Mi spiace di aver trovato "NOT IN" confuso nella mia testa con "NOT EXISTS" - Mi sbaglio, la performance è in realtà migliore del "COUNT" nella mia risposta. Lo modificherò. Saluti! – Codesleuth

1

Questo avviene con un passaggio attraverso i dati che penso.

Dipende dalla distribuzione dei dati indipendentemente dal fatto che sarebbe preferibile effettuare 2 ricerche separate.

WITH matches AS 
(
SELECT fiData 
FROM tabDataDetail 
GROUP BY fiData 
HAVING COUNT(CASE WHEN fiactionCode = 11 THEN 1 END) > 0 
AND COUNT(CASE WHEN fiactionCode NOT IN (11,34) THEN 1 END) = 0 
) 
SELECT ... 
FROM idData i 
JOIN matches m 
ON m.fiData = i.idData 
+0

Anche questo funziona (quando cambi' SELECT ... FRO M idData i 'a' SELECT idData FROM tabData i '). Grazie. –

Problemi correlati