2011-09-20 6 views
12

Ho creato un semplice esempio (si spera molto più divertente dei miei dati effettivi) per esprimere meglio il mio domanda:Come strutturare una query per darmi solo le righe che corrispondono a TUTTI i valori in un elenco CSV di ID in T-SQL

CREATE TABLE SUPER_HERO 
( ID INT, 
    NAME VARCHAR(50) 
) 

INSERT INTO SUPER_HERO VALUES (1, 'Storm') 
INSERT INTO SUPER_HERO VALUES (2, 'Silver Surfer') 
INSERT INTO SUPER_HERO VALUES (3, 'Spider Man') 

CREATE TABLE SKILL 
( ID INT, 
    NAME VARCHAR(50) 
) 

INSERT INTO SKILL VALUES (1, 'Flight') 
INSERT INTO SKILL VALUES (2, 'Weather Control') 
INSERT INTO SKILL VALUES (3, 'Super Speed') 

CREATE TABLE SUPER_HERO_SKILL 
( SUPER_HERO_ID INT, 
    SKILL_ID INT 
) 

INSERT INTO SUPER_HERO_SKILL VALUES (1, 1) --Storm has Flight 
INSERT INTO SUPER_HERO_SKILL VALUES (1, 2) --Storm has Weather Control 
INSERT INTO SUPER_HERO_SKILL VALUES (2, 1) --Silver Surfer has Flight 
INSERT INTO SUPER_HERO_SKILL VALUES (2, 3) --Silver Surfer has Super Speed 
INSERT INTO SUPER_HERO_SKILL VALUES (3, 3) --Spider Man has Super Speed 

esempio di cattivo query (non mostrando i risultati desiderati):

DECLARE @DELIMITER CHAR = ',' 
DECLARE @CSV_STRING VARCHAR(20) = '1,3' 

SELECT 
    SUPER_HERO_NAME = SUPER_HERO.NAME, 
    SKILL_NAME  = SKILL.NAME 
FROM 
    SUPER_HERO 
    JOIN SUPER_HERO_SKILL ON SUPER_HERO_SKILL.SUPER_HERO_ID = SUPER_HERO.ID 
    JOIN SKILL ON SUPER_HERO_SKILL.SKILL_ID = SKILL.ID 
    JOIN dbo.Split(@CSV_STRING, @DELIMITER) SPLIT ON SPLIT.ITEMS = SKILL.ID 

Quello che mi piacerebbe vedere:
Quando DECLARE @CSV_STRING VARCHAR(20) = '1,3' dovrei vedere solo "Silver Surfer" si nce è l'unico con entrambe le abilità 1 e 3 correlate a Flight e Super Speed.

Quando DECLARE @CSV_STRING VARCHAR(20) = '1,2,3' Non dovrei vedere qualsiasi eroi nel mio universo poiché non ci sono nessuno definito per elencare tutte e tre le abilità.


Ci deve essere qualcosa di semplice che mi manca. Ho provato a strutturare la query in molti modi diversi. Ho presentato la forma più semplice di questo qui per non complicare la presentazione del problema.

Nota:. Uso una funzione che agisce come un Split basato su delimitatore passata

+1

Un tuffo sorprendentemente buona domanda. Interessato alla risposta a questo. –

+1

+1 per l'utilizzo di supereroi nel tuo esempio =) –

risposta

6

utilizzare la funzione di ripartitore di sotto del quale restituisce una colonna int. Quindi è facile controllare il conteggio nella clausola HAVING.

CREATE FUNCTION [dbo].[DelimitedParamParser](@DelimitedIds VARCHAR(MAX), @Delimiter CHAR(1)) 
RETURNS @IdsTable 
TABLE (Id INT) 
AS BEGIN 

DECLARE @Length INT, 
     @Index INT, 
     @NextIndex INT 

SET @Length = DATALENGTH(@DelimitedIds) 
SET @Index = 0 
SET @NextIndex = 0 


WHILE (@Length > @Index) 
BEGIN 
    SET @NextIndex = CHARINDEX(@Delimiter, @DelimitedIds, @Index) 
    IF (@NextIndex = 0) SET @NextIndex = @Length + 2 
     INSERT @IdsTable SELECT SUBSTRING(@DelimitedIds, @Index, @NextIndex - @Index) 
    SET @index = @nextindex + 1 
END 
RETURN 
END 

Questo funziona, tieni presente di dare una virgola in più alla fine.

DECLARE @DELIMITER CHAR = ',' 
DECLARE @CSV_STRING VARCHAR(20) = '1,3,' 

SELECT Distinct SUPER_HERO.NAME, SKILL.NAME 
FROM 
    SUPER_HERO 
    INNER JOIN SUPER_HERO_SKILL ON SUPER_HERO_SKILL.SUPER_HERO_ID = SUPER_HERO.ID 
    INNER JOIN SKILL ON SUPER_HERO_SKILL.SKILL_ID = SKILL.ID 
    WHERE SUPER_HERO.ID IN 
    (
    SELECT SUPER_HERO_SKILL.SUPER_HERO_ID 
    FROM 
     SUPER_HERO 
     INNER JOIN SUPER_HERO_SKILL ON SUPER_HERO_SKILL.SUPER_HERO_ID = SUPER_HERO.ID 
     INNER JOIN SKILL ON SUPER_HERO_SKILL.SKILL_ID = SKILL.ID 
     INNER JOIN DelimitedParamParser(@CSV_STRING, @DELIMITER) SPLIT ON SPLIT.ID = SUPER_HERO_SKILL.SKILL_ID 
    GROUP BY SUPER_HERO_SKILL.SUPER_HERO_ID 
    HAVING COUNT(DISTINCT(SUPER_HERO_SKILL.SKILL_ID)) = (SELECT COUNT(DISTINCT(Id)) FROM DelimitedParamParser(@CSV_STRING, @DELIMITER)) 
    ) 
+0

Questo è stato uno dei miei altri tentativi ma, per qualche motivo, l'utilizzo del conteggio distinto degli elementi suddivisi rispetto al conteggio distinto degli ID super eroe ha restituito * no * righe. Questo * sembra * come il modo giusto per farlo, ma non riesco nemmeno a farlo funzionare. – Gibron

+0

Provato con diversi input e appena aggiornato la risposta di nuovo. Funziona bene ora. – CharithJ

+0

Sei rock! Sottoseleziona con gruppo per/avendo era! È incredibile come le cose che fissi per così tanto tempo finiscano con soluzioni così semplici. Grazie mille! :) – Gibron

1

Questo è diviso in due parti, il filtro e il resto della query quindi è semplice per estendere

DECLARE @DELIMITER CHAR = ',' 
DECLARE @CSV_STRING VARCHAR(20) = '1,3' 

SELECT @TOTREQ = COUNT(DISTINCT ITEMS) FROM dbo.Split(@CSV_STRING, @DELIMITER) 

SELECT 
    SUPER_HERO_NAME = SUPER_HERO.NAME 
FROM 
    SUPER_HERO INNER JOIN 
    (SELECT SUPER_HERO_SKILL.SUPER_HERO_ID 
    FROM SUPER_HERO_SKILL  
    LEFT JOIN dbo.Split(@CSV_STRING, @DELIMITER) SPLIT ON SUPER_HERO_SKILL.SKILL_ID = SPLIT.ITEMS 
    GROUP BY SUPER_HERO_SKILL.SUPER_HERO_ID 
    HAVING COUNT(SPLIT.ITEMS) = @TOTREQ  -- This ensure no mising super-powers 
     AND COUNT(*) = @TOTREQ    -- This ensure no extra super-powers (can be omited of course) 
    ) AS FILTER ON  SUPER_HERO.ID = FILTER.SUPER_HERO_ID 
+0

Mi piace anche questa soluzione. Grazie! :) – Gibron

Problemi correlati