2010-08-11 17 views
12

Tengo dos tablas tabData y tabDataDetail. Quiero que todos idData (PK) de Padres-Tabla (tabData) que tienen sólo filas de Niño-Tabla (tabDataDetail, FK es fiData) con:SQL-Query: EXISTS en la Subtabla

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

Cualquier otra combinación no es válida. ¿Cómo conseguirlos?

lo que he intentado sin éxito (lento y me da también filas que tienen única fiActioncode 34):

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

Gracias por su tiempo.


EDITAR: Gracias a todos por sus respuestas. Ahora, desafortunadamente, no tengo tiempo suficiente para verificar cuál es el mejor o funciona en absoluto. Marqué el primero como respuesta.

EDIT2: creo que la respuesta correcta es realmente la solución más eficiente y compacta.

EDIT3: La respuesta de Codesleuth es interesante porque devuelve solo filas que solo tienen un solo fiActionCode = 11. Difícil de ver, porque eso solo es válido para 20 tabDataDetail-rows ot de 41524189 total-rows que tienen dos. De todos modos, eso no era 100% lo que pedí o más bien lo que estaba buscando.

+0

Nunca se menciona qué columnas se necesitan en la salida. ¿Son solo columnas tabData o se requiere algún dato de tabDataDetail? – Thomas

+0

Es solo la clave principal (IdData) que me interesa y debe ser agrupada (y ordenada) por (si es necesario). Pero para verificar el resultado, es mejor tener también el CódigoFiAcción. –

Respuesta

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

Para ampliar mi lógica, la primera comprobación (una corrección) es garantizar que exista una fila con fiActionCode = 11. El segundo cheque funciona definiendo primero el conjunto de filas que no queremos. No queremos nada que sea diferente a fiActionCode = 11 o 34. Como ese es el conjunto de elementos que no queremos, buscamos cualquier cosa que no exista en ese conjunto.

+1

Gracias. Pero eso me da también las filas que tienen solo 34 como fiActionCode en Childtable. Estos deben ser excluidos. –

+0

@Tim - Reparado. Solo necesita una verificación adicional para asegurarse de que fiActionCode = 11 existe. – Thomas

+0

PK es idData y tablename es tabData, pero aparte de eso, parece funcionar (no sé por qué). Recibo 400k filas, así que es difícil de verificar. –

1

Editar: Apols - Veo lo que quiere decir con filas de niños. Esto no es particularmente eficiente. Gracias también a Lieven por los datos.

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

Eso también funciona. –

4

Razonamiento

  1. LEFT OUTER JOIN excluye todos de idData que tienen un ID diferente de 11 o 34
  2. HAVING excluye todos los idData de que solamente tienen un registros 34
  3. restantes (debería) satisfacer todas las restricciones

Los datos de prueba

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) 

consulta

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

Gracias, pero obtengo varios 'El identificador de varias partes "d.idData" no se pudo enlazar.'y' Columna ambigua ' –

+0

¿Has eliminado @? La consulta se ejecuta sin problemas en mi sistema. –

+0

Funciona también (algo lento). Gracias –

1

Editado mi respuesta sobre la base de aclaraciones dadas en los comentarios sobre otras respuestas.

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

También es correcto el resultado. Este método ("null-check") es nuevo para mí. –

1

Gracias @Lieven para el código de datos para probar esta:

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) 

consulta:

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 

devuelve:

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

(2 row(s) affected)

Con sólo 1 subconsulta aquí (y siendo un COUNT en lugar de un muy lento NOT EXISTS) esto crea un plan de ejecución muy ordenado que debería ayudar si tiene problemas con la velocidad.

+0

@CodeSleuth, no lo menciones. El costo según el analizador de rendimiento es 24% para mi solución, 32% para tu. Me interesa saber en un escenario del mundo real si eso es válido. –

+0

Realmente no he visto la pregunta o esta respuesta en detalle, pero como un punto general 'NOT EXISTS' es más eficiente que' COUNT (*) 'en SQL Server como lo hace con una semi semi unión. Solo tiene que verificar que no haya una fila coincidente. No cuenta todos los que coinciden. –

+0

@Lieven: costo de qué? ¿Solo la subconsulta? Lo siento, parece que 'NOT IN' se me ha metido en la cabeza con 'NOT EXISTS' - Estoy equivocado, el rendimiento es mejor que el' COUNT' en mi respuesta. Lo editaré en. ¡Saludos! – Codesleuth

1

Esto lo hace con una pasada a través de los datos, creo.

Depende de la distribución de los datos, sea o no preferible hacer 2 búsquedas separadas.

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

Esto también funciona (al cambiar 'SELECCIONAR ... FRO M idData i 'to' SELECCIONE idData FROM tabData i '). Gracias. –

Cuestiones relacionadas