Procedura memorizzata T-SQL che accetta più valori ID


145

Esiste un modo grazioso per gestire il passaggio di un elenco di ID come parametro a una procedura memorizzata?

Ad esempio, desidero che i dipartimenti 1, 2, 5, 7, 20 vengano restituiti dalla mia procedura memorizzata. In passato, ho passato un elenco di ID delimitati da virgole, come il codice qui sotto, ma mi sento davvero sporco nel farlo.

Penso che SQL Server 2005 sia la mia unica limitazione applicabile.

create procedure getDepartments
  @DepartmentIds varchar(max)
as
  declare @Sql varchar(max)     
  select @Sql = 'select [Name] from Department where DepartmentId in (' + @DepartmentIds + ')'
  exec(@Sql)

Ecco una variante del metodo XML che ho appena trovato.
JasonS,

5
Se si utilizza SQL Server 2008, è possibile utilizzare un parametro con valori di tabella. http://www.sqlteam.com/article/sql-server-2008-table-valued-parameters
Ian Nelson

Risposte:


237

Erland Sommarskog ha mantenuto la risposta autorevole a questa domanda negli ultimi 16 anni: matrici ed elenchi in SQL Server .

Esistono almeno una dozzina di modi per passare un array o un elenco a una query; ognuno ha i suoi pro e contro unici.

  • Parametri con valori di tabella . Solo SQL Server 2008 e versioni successive, e probabilmente il più vicino all'approccio universale "migliore".
  • Il metodo iterativo . Passa una stringa delimitata e passaci attraverso.
  • Utilizzando il CLR . SQL Server 2005 e versioni successive solo dai linguaggi .NET.
  • XML . Ottimo per inserire molte righe; potrebbe essere eccessivo per SELECTs.
  • Tabella dei numeri . Prestazioni / complessità superiori rispetto al semplice metodo iterativo.
  • Elementi a lunghezza fissa . La lunghezza fissa migliora la velocità sulla stringa delimitata
  • Funzione dei numeri . Variazioni della tabella dei numeri e della lunghezza fissa in cui il numero viene generato in una funzione anziché preso da una tabella.
  • Espressione di tabella comune ricorsiva (CTE). SQL Server 2005 e versioni successive, prestazioni ancora non troppo complesse e superiori rispetto al metodo iterativo.
  • SQL dinamico . Può essere lento e ha implicazioni per la sicurezza.
  • Passando l'elenco come molti parametri . Noioso e soggetto a errori, ma semplice.
  • Metodi davvero lenti . Metodi che utilizzano charindex, patindex o LIKE.

Non posso davvero raccomandare abbastanza per leggere l'articolo per conoscere i compromessi tra tutte queste opzioni.


11

Sì, la tua soluzione attuale è soggetta ad attacchi di iniezione SQL.

La migliore soluzione che ho trovato è quella di utilizzare una funzione che divide il testo in parole (ce ne sono alcuni pubblicati qui, oppure puoi usarlo dal mio blog ) e poi unirlo al tuo tavolo. Qualcosa di simile a:

SELECT d.[Name]
FROM Department d
    JOIN dbo.SplitWords(@DepartmentIds) w ON w.Value = d.DepartmentId

14
Non sono sicuro che sia "soggetto ad attacchi di iniezione SQL" a meno che il proc memorizzato non sia richiamabile direttamente da client non attendibili, nel qual caso hai problemi più grandi. Il codice del livello di servizio dovrebbe generare la stringa @DepartmentIds da dati fortemente tipizzati (es. Int [] departmentIds), nel qual caso andrà bene.
Anthony,

Ottima soluzione, @Matt Hamilton. Non so se questo aiuterà qualcuno, ma ho ottenuto risultati più precisi su SQL Server 2008r mentre stavo cercando campi di testo usando "join dbo.SplitWords (@MyParameterArray) p ON CHARINDEX (p.value, d.MyFieldToSearch)> 0"
Darkloki,

3

Un metodo che potresti voler prendere in considerazione se lavorerai molto con i valori è quello di scriverli prima in una tabella temporanea. Quindi ti unisci ad esso come di consueto.

In questo modo, stai analizzando una sola volta.

È più facile usare uno degli UDF "Split", ma così tante persone ne hanno pubblicato esempi, ho pensato che avrei seguito una strada diversa;)

Questo esempio creerà una tabella temporanea per la tua partecipazione (#tmpDept) e la riempirà con l'id del dipartimento che hai passato. Suppongo che le stai separando con virgole, ma puoi - ovviamente - cambiare per quello che vuoi.

IF OBJECT_ID('tempdb..#tmpDept', 'U') IS NOT NULL
BEGIN
    DROP TABLE #tmpDept
END

SET @DepartmentIDs=REPLACE(@DepartmentIDs,' ','')

CREATE TABLE #tmpDept (DeptID INT)
DECLARE @DeptID INT
IF IsNumeric(@DepartmentIDs)=1
BEGIN
    SET @DeptID=@DepartmentIDs
    INSERT INTO #tmpDept (DeptID) SELECT @DeptID
END
ELSE
BEGIN
        WHILE CHARINDEX(',',@DepartmentIDs)>0
        BEGIN
            SET @DeptID=LEFT(@DepartmentIDs,CHARINDEX(',',@DepartmentIDs)-1)
            SET @DepartmentIDs=RIGHT(@DepartmentIDs,LEN(@DepartmentIDs)-CHARINDEX(',',@DepartmentIDs))
            INSERT INTO #tmpDept (DeptID) SELECT @DeptID
        END
END

Ciò ti consentirà di passare un ID reparto, più ID con virgole tra di loro o anche più ID con virgole e spazi tra di loro.

Quindi se hai fatto qualcosa del genere:

SELECT Dept.Name 
FROM Departments 
JOIN #tmpDept ON Departments.DepartmentID=#tmpDept.DeptID
ORDER BY Dept.Name

Vedresti i nomi di tutti gli ID dipartimento che hai passato ...

Ancora una volta, questo può essere semplificato usando una funzione per popolare la tabella temporanea ... L'ho fatto principalmente senza uno solo per uccidere un po 'di noia :-P

- Kevin Fairchild


3

È possibile utilizzare XML.

Per esempio

declare @xmlstring as  varchar(100) 
set @xmlstring = '<args><arg value="42" /><arg2>-1</arg2></args>' 

declare @docid int 

exec sp_xml_preparedocument @docid output, @xmlstring

select  [id],parentid,nodetype,localname,[text]
from    openxml(@docid, '/args', 1) 

Il comando sp_xml_preparedocument è integrato.

Ciò produrrebbe l'output:

id  parentid    nodetype    localname   text
0   NULL        1           args        NULL
2   0           1           arg         NULL
3   2           2           value       NULL
5   3           3           #text       42
4   0           1           arg2        NULL
6   4           3           #text       -1

che ha tutto (di più?) di ciò di cui hai bisogno.


2

Un metodo XML superveloce, se si desidera utilizzare una procedura memorizzata e passare l'elenco separato da virgole degli ID reparto:

Declare @XMLList xml
SET @XMLList=cast('<i>'+replace(@DepartmentIDs,',','</i><i>')+'</i>' as xml)
SELECT x.i.value('.','varchar(5)') from @XMLList.nodes('i') x(i))

Tutto il merito va al blog di Guru Brad Schulz


-3

Prova questo:

@list_of_params varchar(20) -- value 1, 2, 5, 7, 20 

SELECT d.[Name]
FROM Department d
where @list_of_params like ('%'+ CONVERT(VARCHAR(10),d.Id)  +'%')

molto semplice.


1
molto semplice - e molto sbagliato. Ma anche se risolvessi il problema nel tuo codice sarebbe molto lento. Per i dettagli, consultare il link "Metodi realmente lenti" nella risposta accettata.
Sebastian Meine,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.