Parametrizzare una clausola SQL IN


1041

Come posso parametrizzare una query contenente una INclausola con un numero variabile di argomenti, come questo?

SELECT * FROM Tags 
WHERE Name IN ('ruby','rails','scruffy','rubyonrails')
ORDER BY Count DESC

In questa query, il numero di argomenti potrebbe essere compreso tra 1 e 5.

Preferirei non utilizzare una stored procedure dedicata per questo (o XML), ma se esiste un modo elegante specifico per SQL Server 2008 , sono aperto a questo.



Risposte:


315

Ecco una tecnica veloce e sporca che ho usato:

SELECT * FROM Tags
WHERE '|ruby|rails|scruffy|rubyonrails|'
LIKE '%|' + Name + '|%'

Quindi, ecco il codice C #:

string[] tags = new string[] { "ruby", "rails", "scruffy", "rubyonrails" };
const string cmdText = "select * from tags where '|' + @tags + '|' like '%|' + Name + '|%'";

using (SqlCommand cmd = new SqlCommand(cmdText)) {
   cmd.Parameters.AddWithValue("@tags", string.Join("|", tags);
}

Due avvertenze:

  • La performance è terribile. LIKE "%...%"le query non sono indicizzate.
  • Assicurati di non avere |tag vuoti o vuoti, altrimenti non funzionerà

Ci sono altri modi per ottenere questo risultato che alcune persone possono considerare più pulite, quindi continua a leggere.


119
Sarà molto lento
Matt Rogish,

13
Sì, questa è una scansione della tabella. Ottimo per 10 file, schifoso per 100.000.
Will Hartung,

17
Assicurati di provare su tag che contengono pipe.
Joel Coehoorn,

17
Questo non risponde nemmeno alla domanda. Certo, è facile vedere dove aggiungere i parametri, ma come si può accettare questa soluzione se non si preoccupa nemmeno di parametrizzare la query? Sembra solo più semplice di @Mark Brackett perché non è parametrizzato.
tvanfosson,

21
Cosa succede se il tag è "ruby | rails". Abbinerà, il che sarà sbagliato. Quando si implementano tali soluzioni, è necessario assicurarsi che i tag non contengano pipe o filtrarli esplicitamente: selezionare * da Tag in cui "| ruby ​​| rails | scruffy | rubyonrails |" come "% |" + Nome + '|%' E nome non come '%!%'
AK

729

Puoi parametrizzare ogni valore, quindi qualcosa del tipo:

string[] tags = new string[] { "ruby", "rails", "scruffy", "rubyonrails" };
string cmdText = "SELECT * FROM Tags WHERE Name IN ({0})";

string[] paramNames = tags.Select(
    (s, i) => "@tag" + i.ToString()
).ToArray();

string inClause = string.Join(", ", paramNames);
using (SqlCommand cmd = new SqlCommand(string.Format(cmdText, inClause))) {
    for(int i = 0; i < paramNames.Length; i++) {
       cmd.Parameters.AddWithValue(paramNames[i], tags[i]);
    }
}

Che ti darà:

cmd.CommandText = "SELECT * FROM Tags WHERE Name IN (@tag0, @tag1, @tag2, @tag3)"
cmd.Parameters["@tag0"] = "ruby"
cmd.Parameters["@tag1"] = "rails"
cmd.Parameters["@tag2"] = "scruffy"
cmd.Parameters["@tag3"] = "rubyonrails"

No, questo non è aperto all'iniezione SQL . L'unico testo iniettato in CommandText non si basa sull'input dell'utente. È basato esclusivamente sul prefisso "@tag" codificato e sull'indice di un array. L'indice sarà sempre un numero intero, non è generato dall'utente ed è sicuro.

I valori immessi dall'utente sono ancora inseriti nei parametri, quindi non vi è alcuna vulnerabilità.

Modificare:

A parte le preoccupazioni relative all'iniezione, fare attenzione a notare che la costruzione del testo del comando per accogliere un numero variabile di parametri (come sopra) impedisce alla capacità del server SQL di sfruttare le query memorizzate nella cache. Il risultato netto è che quasi sicuramente perdi il valore dell'uso dei parametri in primo luogo (invece di inserire semplicemente le stringhe di predicato nell'SQL stesso).

Non che i piani di query memorizzati nella cache non siano utili, ma IMO questa query non è abbastanza complicata da vederne molti vantaggi. Mentre i costi di compilazione possono avvicinarsi (o addirittura superare) ai costi di esecuzione, stai ancora parlando di millisecondi.

Se hai abbastanza RAM, mi aspetto che SQL Server probabilmente memorizzerà nella cache anche un piano per i conteggi comuni dei parametri. Suppongo che potresti sempre aggiungere cinque parametri e lasciare che i tag non specificati siano NULL: il piano di query dovrebbe essere lo stesso, ma a me sembra abbastanza brutto e non sono sicuro che valga la pena di micro-ottimizzazione (anche se, su Stack Overflow - potrebbe valerne la pena).

Inoltre, SQL Server 7 e versioni successive parametrizzeranno automaticamente le query , quindi l'utilizzo dei parametri non è realmente necessario dal punto di vista delle prestazioni - è, tuttavia, fondamentale dal punto di vista della sicurezza - soprattutto con dati immessi dall'utente come questo.


2
Fondamentalmente è la stessa della mia risposta alla domanda "correlata" e ovviamente la soluzione migliore poiché è costruttiva ed efficiente piuttosto che interpretativa (molto più difficile).
tvanfosson,

49
Questo è il modo in cui LINQ to SQL lo fa, BTW
Mark Cidade,

3
@Puro: il punto centrale di tutto ciò è evitare l'iniezione SQL, che sarebbe vulnerabile se si usasse SQL dinamico.
Ray

4
@Dio di dati - Sì, suppongo che se hai bisogno di più di 2100 tag avrai bisogno di una soluzione diversa. Ma Basarb poteva raggiungere i 2100 solo se la lunghezza media dei tag era <3 caratteri (poiché è necessario anche un delimitatore). msdn.microsoft.com/en-us/library/ms143432.aspx
Mark Brackett

2
@bonCodigo: i valori selezionati sono in un array; basta passare in rassegna l'array e aggiungere un parametro (suffisso con l'indice) per ognuno.
Mark Brackett,

249

Per SQL Server 2008, è possibile utilizzare un parametro con valori di tabella . È un po 'di lavoro, ma è probabilmente più pulito del mio altro metodo .

Innanzitutto, devi creare un tipo

CREATE TYPE dbo.TagNamesTableType AS TABLE ( Name nvarchar(50) )

Quindi, il tuo codice ADO.NET è simile al seguente:

string[] tags = new string[] { "ruby", "rails", "scruffy", "rubyonrails" };
cmd.CommandText = "SELECT Tags.* FROM Tags JOIN @tagNames as P ON Tags.Name = P.Name";

// value must be IEnumerable<SqlDataRecord>
cmd.Parameters.AddWithValue("@tagNames", tags.AsSqlDataRecord("Name")).SqlDbType = SqlDbType.Structured;
cmd.Parameters["@tagNames"].TypeName = "dbo.TagNamesTableType";

// Extension method for converting IEnumerable<string> to IEnumerable<SqlDataRecord>
public static IEnumerable<SqlDataRecord> AsSqlDataRecord(this IEnumerable<string> values, string columnName) {
    if (values == null || !values.Any()) return null; // Annoying, but SqlClient wants null instead of 0 rows
    var firstRecord = values.First();
    var metadata = SqlMetaData.InferFromValue(firstRecord, columnName);
    return values.Select(v => 
    {
       var r = new SqlDataRecord(metadata);
       r.SetValues(v);
       return r;
    });
}

41
abbiamo testato questo e i parametri con valori di tabella sono DOG lenti. È letteralmente più veloce eseguire 5 query di quanto non sia fare un TVP.
Jeff Atwood,

4
@JeffAtwood - Hai provato a rimescolare la query in qualcosa del genere SELECT * FROM tags WHERE tags.name IN (SELECT name from @tvp);? In teoria, questo dovrebbe essere l'approccio più veloce. È possibile utilizzare indici pertinenti (ad es. Un indice sul nome del tag che INCLUDEs conteggi sarebbe l'ideale) e SQL Server dovrebbe fare alcune ricerche per afferrare tutti i tag e i loro conteggi. Come appare il piano?
Nick Chammas,

9
Ho anche provato questo ed è FAST AS LIGHTNING (rispetto alla costruzione di una stringa IN di grandi dimensioni). Ho avuto alcuni problemi nell'impostazione del parametro, dato che stavo ottenendo costantemente "Impossibile convertire il valore del parametro da Int32 [] a IEnumerable`1.". Comunque, risolto questo ed ecco un esempio che ho realizzato pastebin.com/qHP05CXc
Fredrik Johansson

6
@FredrikJohansson - Su 130 voti positivi, potresti essere l'unica corsa che ha effettivamente provato a farlo! Ho fatto un errore durante la lettura dei documenti e in realtà hai bisogno di un IEnumerable <SqlDataRecord>, non solo di un IEnumerable. Il codice è stato aggiornato
Mark Brackett,

3
@MarkBrackett Ottimo con un aggiornamento! Alla fine questo codice mi ha davvero salvato la giornata poiché sto eseguendo una query su un indice di ricerca di Lucene e talvolta restituisce più di 50.000 o più hit che devono essere sottoposti a doppio controllo su SQL Server - Quindi creo una matrice di int [] (document / Chiavi SQL) e poi arriva il codice sopra. L'intero PO ora impiega meno di 200 ms :)
Fredrik Johansson

188

La domanda originale era "Come posso parametrizzare una query ..."

Consentitemi di affermare che questo è non una risposta alla domanda originale. Ne esistono già alcune dimostrazioni in altre buone risposte.

Detto questo, vai avanti e contrassegna questa risposta, ridimensionala, contrassegnala come non una risposta ... fai quello che ritieni giusto.

Vedi la risposta di Mark Brackett per la risposta preferita che io (e altri 231) abbiamo votato. L'approccio fornito nella sua risposta consente 1) per un uso efficace delle variabili di bind e 2) per predicati che sono rilevanti.

Risposta selezionata

Quello che voglio affrontare qui è l'approccio dato nella risposta di Joel Spolsky, la risposta "selezionata" come la risposta giusta.

L'approccio di Joel Spolsky è intelligente. E funziona in modo ragionevole, mostrerà comportamenti prevedibili e prestazioni prevedibili, dati valori "normali" e con i casi limite normativi, come NULL e la stringa vuota. E può essere sufficiente per una particolare applicazione.

Ma in termini di generalizzazione di questo approccio, consideriamo anche i casi d'angolo più oscuri, come quando la Namecolonna contiene un carattere jolly (come riconosciuto dal predicato LIKE). Il carattere jolly che vedo più comunemente usato è %(un segno di percentuale). Quindi affrontiamolo qui ora, e poi passiamo ad altri casi.

Alcuni problemi con il carattere%

Considera un valore Nome di 'pe%ter'. (Per gli esempi qui, utilizzo un valore di stringa letterale al posto del nome della colonna.) Una riga con un valore Nome di '' pe% ter 'verrebbe restituita da una query del modulo:

select ...
 where '|peanut|butter|' like '%|' + 'pe%ter' + '|%'

Ma quella stessa riga non verrà restituita se l'ordine dei termini di ricerca viene invertito:

select ...
 where '|butter|peanut|' like '%|' + 'pe%ter' + '|%'

Il comportamento che osserviamo è strano. La modifica dell'ordine dei termini di ricerca nell'elenco modifica il set di risultati.

È quasi ovvio che potremmo non voler pe%terabbinare il burro di arachidi, non importa quanto gli piaccia.

Custodia angolare oscura

(Sì, concordo sul fatto che questo è un caso oscuro. Probabilmente uno che non è probabile che venga testato. Non ci aspetteremmo un carattere jolly in un valore di colonna. Potremmo presumere che l'applicazione impedisca la memorizzazione di tale valore. Ma nella mia esperienza, ho visto raramente un vincolo di database che non consentiva specificamente caratteri o modelli che sarebbero considerati caratteri jolly sul lato destro di un LIKEoperatore di confronto.

Rattoppare un buco

Un approccio per correggere questo buco è quello di sfuggire al %carattere jolly. (Per chiunque non abbia familiarità con la clausola di escape sull'operatore, ecco un link alla documentazione di SQL Server .

select ...
 where '|peanut|butter|'
  like '%|' + 'pe\%ter' + '|%' escape '\'

Ora possiamo abbinare la% letterale. Naturalmente, quando abbiamo un nome di colonna, dovremo sfuggire dinamicamente al jolly. Possiamo usare la REPLACEfunzione per trovare le occorrenze del %personaggio e inserire una barra rovesciata davanti a ciascuna, in questo modo:

select ...
 where '|pe%ter|'
  like '%|' + REPLACE( 'pe%ter' ,'%','\%') + '|%' escape '\'

Questo risolve il problema con il carattere jolly%. Quasi.

Sfuggire alla fuga

Riconosciamo che la nostra soluzione ha introdotto un altro problema. Il personaggio di fuga. Vediamo che avremo anche bisogno di sfuggire a qualsiasi occorrenza del personaggio di fuga stesso. Questa volta, usiamo il! come personaggio di fuga:

select ...
 where '|pe%t!r|'
  like '%|' + REPLACE(REPLACE( 'pe%t!r' ,'!','!!'),'%','!%') + '|%' escape '!'

Anche il trattino basso

Ora che siamo su un rotolo, possiamo aggiungere un altro REPLACEhandle al carattere jolly di sottolineatura. E solo per divertimento, questa volta useremo $ come personaggio di escape.

select ...
 where '|p_%t!r|'
  like '%|' + REPLACE(REPLACE(REPLACE( 'p_%t!r' ,'$','$$'),'%','$%'),'_','$_') + '|%' escape '$'

Preferisco questo approccio alla fuga perché funziona in Oracle, MySQL e SQL Server. (Di solito uso la barra rovesciata come carattere di escape, poiché quello è il personaggio che usiamo nelle espressioni regolari. Ma perché essere vincolato dalla convenzione!

Quelle fastidiose parentesi

SQL Server consente inoltre di trattare i caratteri jolly come letterali racchiudendoli tra parentesi []. Quindi non abbiamo ancora risolto, almeno per SQL Server. Poiché le coppie di parentesi hanno un significato speciale, dovremo sfuggire anche a quelle. Se riusciamo a sfuggire correttamente alle parentesi, almeno non dovremo preoccuparci del trattino -e del carato ^all'interno delle parentesi. E possiamo lasciare qualsiasi %e_ caratteri all'interno delle parentesi fuggiti, dal momento che avremo praticamente disattivato il significato speciale delle staffe.

Trovare coppie di parentesi corrispondenti non dovrebbe essere così difficile. È un po 'più difficile che gestire le occorrenze di singleton% e _. (Nota che non è sufficiente sfuggire a tutte le occorrenze delle parentesi, perché una parentesi singleton è considerata letterale e non ha bisogno di essere sfuggita. La logica sta diventando un po 'più confusa di quanto io possa gestire senza eseguire più casi di test .)

L'espressione incorporata diventa confusa

Quell'espressione incorporata nell'SQL sta diventando più lunga e brutta. Probabilmente possiamo farlo funzionare, ma il paradiso aiuta la povera anima che viene dietro e deve decifrarla. Per quanto io sia un fan delle espressioni in linea, sono propenso a non usarne uno qui, principalmente perché non voglio lasciare un commento che spieghi il motivo del disordine e mi scusi per questo.

Una funzione dove?

Ok, quindi, se non lo gestiamo come espressione incorporata nell'SQL, l'alternativa più vicina che abbiamo è una funzione definita dall'utente. E sappiamo che non accelererà le cose (a meno che non possiamo definire un indice su di esso, come avremmo potuto fare con Oracle). Se dobbiamo creare una funzione, potremmo farlo nel codice che chiama SQL dichiarazione.

E quella funzione può avere alcune differenze nel comportamento, a seconda del DBMS e della versione. (Un grido a tutti voi sviluppatori Java così entusiasti di poter usare qualsiasi motore di database in modo intercambiabile.)

Conoscenza del dominio

Potremmo avere una conoscenza specializzata del dominio per la colonna, (ovvero l'insieme dei valori consentiti applicati per la colonna. Potremmo sapere a priori che i valori memorizzati nella colonna non conterranno mai un segno di percentuale, un carattere di sottolineatura o una parentesi coppie. In tal caso, includiamo solo un breve commento sul fatto che tali casi sono coperti.

I valori memorizzati nella colonna possono consentire% o _ caratteri, ma un vincolo può richiedere la fuga di tali valori, magari utilizzando un carattere definito, in modo che i valori siano COME un confronto "sicuro". Ancora una volta, un breve commento sul set di valori consentito, e in particolare su quale personaggio viene utilizzato come personaggio di escape, e segui l'approccio di Joel Spolsky.

Ma, in assenza di conoscenze specialistiche e di una garanzia, è importante per noi almeno prendere in considerazione la gestione di quegli oscuri casi angolari e valutare se il comportamento sia ragionevole e "secondo le specifiche".


Altre questioni ricapitolate

Credo che altri abbiano già sufficientemente sottolineato alcune delle altre aree di interesse comunemente considerate:

  • Iniezione di SQL (prendendo quelle che sembrano essere le informazioni fornite dall'utente e includendole nel testo SQL anziché fornirle tramite variabili di bind. L'uso delle variabili di bind non è necessario, è solo un comodo approccio per contrastare l'iniezione di SQL. Ci sono altri modi per affrontarlo:

  • piano di ottimizzazione che utilizza la scansione dell'indice anziché la ricerca dell'indice, possibile necessità di un'espressione o di una funzione per evitare caratteri jolly (possibile indice sull'espressione o sulla funzione)

  • l'utilizzo di valori letterali al posto delle variabili di bind influisce sulla scalabilità


Conclusione

Mi piace l'approccio di Joel Spolsky. È intelligente. E funziona

Ma non appena l'ho visto, ho subito visto un potenziale problema con esso, e non è mia natura lasciarlo scivolare. Non intendo essere critico nei confronti degli sforzi degli altri. So che molti sviluppatori prendono il loro lavoro molto sul personale, perché investono così tanto in esso e si preoccupano così tanto. Quindi, per favore, capisci, questo non è un attacco personale. Quello che sto identificando qui è il tipo di problema che cresce nella produzione piuttosto che testare.

Sì, sono andato molto lontano dalla domanda originale. Ma dove altro lasciare questa nota riguardo a ciò che considero un problema importante con la risposta "selezionata" per una domanda?


puoi per favore facci sapere se usi o ti piacciono le query con parametri? in questo caso particolare è corretto saltare la regola di "utilizzare query con parametri" e disinfettare con la lingua originale? GRAZIE molto
Luis Siquot,

2
@Luis: sì, preferisco utilizzare le variabili di bind nelle istruzioni SQL e eviterò le variabili di bind solo quando si usano causando un problema di prestazioni. il mio modello normativo per il problema originale sarebbe quello di creare dinamicamente l'istruzione SQL con il numero richiesto di segnaposto nell'elenco IN, quindi associare ciascun valore a uno dei segnaposto. Vedi la risposta di Mark Brackett, che è la risposta che io (e altri 231) abbiamo votato.
spencer7593,

133

È possibile passare il parametro come stringa

Quindi hai la stringa

DECLARE @tags

SET @tags = ruby|rails|scruffy|rubyonrails

select * from Tags 
where Name in (SELECT item from fnSplit(@tags, ‘|’))
order by Count desc

Quindi tutto ciò che devi fare è passare la stringa come 1 parametro.

Ecco la funzione split che uso.

CREATE FUNCTION [dbo].[fnSplit](
    @sInputList VARCHAR(8000) -- List of delimited items
  , @sDelimiter VARCHAR(8000) = ',' -- delimiter that separates items
) RETURNS @List TABLE (item VARCHAR(8000))

BEGIN
DECLARE @sItem VARCHAR(8000)
WHILE CHARINDEX(@sDelimiter,@sInputList,0) <> 0
 BEGIN
 SELECT
  @sItem=RTRIM(LTRIM(SUBSTRING(@sInputList,1,CHARINDEX(@sDelimiter,@sInputList,0)-1))),
  @sInputList=RTRIM(LTRIM(SUBSTRING(@sInputList,CHARINDEX(@sDelimiter,@sInputList,0)+LEN(@sDelimiter),LEN(@sInputList))))

 IF LEN(@sItem) > 0
  INSERT INTO @List SELECT @sItem
 END

IF LEN(@sInputList) > 0
 INSERT INTO @List SELECT @sInputList -- Put the last item in
RETURN
END

2
Con questo approccio puoi anche unirti alla funzione tabella.
Michael Haren,

Uso una soluzione simile a questa in Oracle. Non deve essere analizzato nuovamente come fanno alcune delle altre soluzioni.
Leigh Riffel,

9
Questo è un approccio al database puro, l'altro richiede un lavoro nel codice esterno al database.
David Basarab,

Fa una scansione della tabella o può trarre vantaggio da indici, ecc.?
Pure.Krome

meglio sarebbe usare CROSS APPLY contro la funzione di tabella SQL (almeno dal 2005 in poi), che si unisce essenzialmente alla tabella che viene restituita
adolf garlic

66

Ho sentito Jeff / Joel parlare di questo sul podcast oggi ( episodio 34 , 2008-12-16 (MP3, 31 MB), 1 h 03 min 38 secondi - 1 h 06 min 45 secondi), e ho pensato di ricordare Stack Overflow stava usando LINQ to SQL , ma forse era stato abbandonato. Ecco la stessa cosa in LINQ to SQL.

var inValues = new [] { "ruby","rails","scruffy","rubyonrails" };

var results = from tag in Tags
              where inValues.Contains(tag.Name)
              select tag;

Questo è tutto. E, sì, LINQ guarda già abbastanza indietro, ma la Containsclausola mi sembra molto più indietro. Quando ho dovuto fare una query simile per un progetto al lavoro, ho naturalmente cercato di farlo nel modo sbagliato facendo un join tra l'array locale e la tabella di SQL Server, immaginando che il traduttore LINQ to SQL sarebbe abbastanza intelligente da gestire il traduzione in qualche modo. Non ha funzionato, ma ha fornito un messaggio di errore descrittivo e mi ha indicato l'uso di Contains .

Ad ogni modo, se si esegue questo nel LINQPad altamente raccomandato ed si esegue questa query, è possibile visualizzare l'SQL effettivo generato dal provider SQL LINQ. Ti mostrerà ciascuno dei valori che vengono parametrizzati in una INclausola.


50

Se stai chiamando da .NET, puoi usare Dapper dot net :

string[] names = new string[] {"ruby","rails","scruffy","rubyonrails"};
var tags = dataContext.Query<Tags>(@"
select * from Tags 
where Name in @names
order by Count desc", new {names});

Qui Dapper pensa, quindi non è necessario. Qualcosa di simile è possibile con LINQ to SQL , ovviamente:

string[] names = new string[] {"ruby","rails","scruffy","rubyonrails"};
var tags = from tag in dataContext.Tags
           where names.Contains(tag.Name)
           orderby tag.Count descending
           select tag;

11
che sembra essere quello che usiamo in questa pagina, per la vera domanda (dapper) i.stack.imgur.com/RBAjL.png
Sam Saffron,

3
Nota che dapper ora supporta
Marc Gravell

Questo cade se i nomi sono lunghi
cs0815

29

Questo è forse un mezzo modo per farlo, l'ho usato una volta, era piuttosto efficace.

A seconda dei tuoi obiettivi potrebbe essere utile.

  1. Creare una tabella temporanea con una colonna.
  2. INSERT ogni valore di ricerca in quella colonna.
  3. Invece di usare un IN, puoi semplicemente usare le tue JOINregole standard . (Flessibilità ++)

Questo ha un po 'di flessibilità in più in ciò che puoi fare, ma è più adatto a situazioni in cui hai una grande tabella da interrogare, con una buona indicizzazione e vuoi usare l'elenco parametrizzato più di una volta. Risparmia di doverlo eseguire due volte e di eseguire tutti i servizi igienico-sanitari manualmente.

Non ho mai avuto modo di profilare esattamente quanto fosse veloce , ma nella mia situazione era necessario.


Questo non è affatto brutto! Ancora di più, è IMHO un modo molto pulito. E se guardi nel piano di esecuzione, vedi che è lo stesso della clausola IN. Invece di una tabella temporanea, è anche possibile creare una tabella fissa con indici, in cui memorizzare i parametri insieme a SESSIONID.
SQL Police,

27

In SQL Server 2016+potresti usare la STRING_SPLITfunzione:

DECLARE @names NVARCHAR(MAX) = 'ruby,rails,scruffy,rubyonrails';

SELECT * 
FROM Tags
WHERE Name IN (SELECT [value] FROM STRING_SPLIT(@names, ','))
ORDER BY [Count] DESC;

o:

DECLARE @names NVARCHAR(MAX) = 'ruby,rails,scruffy,rubyonrails';

SELECT t.*
FROM Tags t
JOIN STRING_SPLIT(@names,',')
  ON t.Name = [value]
ORDER BY [Count] DESC;

Dimostrazione dal vivo

La risposta accettata funzionerà ovviamente ed è una delle strade da percorrere, ma è anti-schema.

E. Trova righe per elenco di valori

Questo è un sostituto per l'anti-pattern comune come la creazione di una stringa SQL dinamica nel livello applicazione o Transact-SQL o usando l'operatore LIKE:

SELECT ProductId, Name, Tags
FROM Product
WHERE ',1,2,3,' LIKE '%,' + CAST(ProductId AS VARCHAR(20)) + ',%';

Addendum :

Per migliorare la STRING_SPLITstima della riga della funzione della tabella, è consigliabile materializzare i valori divisi come variabile temporanea tabella / tabella:

DECLARE @names NVARCHAR(MAX) = 'ruby,rails,scruffy,rubyonrails,sql';

CREATE TABLE #t(val NVARCHAR(120));
INSERT INTO #t(val) SELECT s.[value] FROM STRING_SPLIT(@names, ',') s;

SELECT *
FROM Tags tg
JOIN #t t
  ON t.val = tg.TagName
ORDER BY [Count] DESC;

SEDE - Demo live

Correlati: Come passare un elenco di valori in una procedura memorizzata


La domanda originale ha dei requisiti SQL Server 2008. Poiché questa domanda viene spesso utilizzata come duplicata, ho aggiunto questa risposta come riferimento.


1
Non ho provato questo perf, ma penso che questa sia la soluzione 2016+ più pulita. Mi piacerebbe ancora poter passare una serie di int, ma fino ad allora ...
Daniel,

24

Abbiamo una funzione che crea una variabile di tabella a cui puoi unirti:

ALTER FUNCTION [dbo].[Fn_sqllist_to_table](@list  AS VARCHAR(8000),
                                           @delim AS VARCHAR(10))
RETURNS @listTable TABLE(
  Position INT,
  Value    VARCHAR(8000))
AS
  BEGIN
      DECLARE @myPos INT

      SET @myPos = 1

      WHILE Charindex(@delim, @list) > 0
        BEGIN
            INSERT INTO @listTable
                        (Position,Value)
            VALUES     (@myPos,LEFT(@list, Charindex(@delim, @list) - 1))

            SET @myPos = @myPos + 1

            IF Charindex(@delim, @list) = Len(@list)
              INSERT INTO @listTable
                          (Position,Value)
              VALUES     (@myPos,'')

            SET @list = RIGHT(@list, Len(@list) - Charindex(@delim, @list))
        END

      IF Len(@list) > 0
        INSERT INTO @listTable
                    (Position,Value)
        VALUES     (@myPos,@list)

      RETURN
  END 

Così:

@Name varchar(8000) = null // parameter for search values    

select * from Tags 
where Name in (SELECT value From fn_sqllist_to_table(@Name,',')))
order by Count desc

20

Questo è disgustoso, ma se hai la garanzia di averne almeno uno, potresti fare:

SELECT ...
       ...
 WHERE tag IN( @tag1, ISNULL( @tag2, @tag1 ), ISNULL( @tag3, @tag1 ), etc. )

Avere IN ('tag1', 'tag2', 'tag1', 'tag1', 'tag1') sarà facilmente ottimizzato da SQL Server. Inoltre, ottieni ricerche sull'indice diretto


1
I parametri opzionali con null controllano le prestazioni, poiché l'ottimizzatore richiede il numero di parametri utilizzati per creare query efficienti. Una query per 5 parametri potrebbe richiedere un piano di query diverso da uno per 500 parametri.
Erik Hart,

18

Secondo me, la migliore fonte per risolvere questo problema, è ciò che è stato pubblicato su questo sito:

Syscomments. Dinakar Nethi

CREATE FUNCTION dbo.fnParseArray (@Array VARCHAR(1000),@separator CHAR(1))
RETURNS @T Table (col1 varchar(50))
AS 
BEGIN
 --DECLARE @T Table (col1 varchar(50))  
 -- @Array is the array we wish to parse
 -- @Separator is the separator charactor such as a comma
 DECLARE @separator_position INT -- This is used to locate each separator character
 DECLARE @array_value VARCHAR(1000) -- this holds each array value as it is returned
 -- For my loop to work I need an extra separator at the end. I always look to the
 -- left of the separator character for each array value

 SET @array = @array + @separator

 -- Loop through the string searching for separtor characters
 WHILE PATINDEX('%' + @separator + '%', @array) <> 0 
 BEGIN
    -- patindex matches the a pattern against a string
    SELECT @separator_position = PATINDEX('%' + @separator + '%',@array)
    SELECT @array_value = LEFT(@array, @separator_position - 1)
    -- This is where you process the values passed.
    INSERT into @T VALUES (@array_value)    
    -- Replace this select statement with your processing
    -- @array_value holds the value of this element of the array
    -- This replaces what we just processed with and empty string
    SELECT @array = STUFF(@array, 1, @separator_position, '')
 END
 RETURN 
END

Uso:

SELECT * FROM dbo.fnParseArray('a,b,c,d,e,f', ',')

CREDITI PER: Dinakar Nethi


Ottima risposta, pulita e modulare, esecuzione super veloce ad eccezione dell'analisi CSV iniziale in una tabella (una volta, numero ridotto di elementi). Anche se potrebbe usare charindex () più semplice / veloce invece di patindex ()? Charindex () consente anche l'argomento 'start_location' che potrebbe essere in grado di evitare di tagliare la stringa di input ogni iter? Per rispondere alla domanda originale puoi semplicemente unirti al risultato della funzione.
crokusek,

18

Passerei un parametro del tipo di tabella (dal momento che è SQL Server 2008 ) e farei un where existsjoin interno. Puoi anche usare XML, usando sp_xml_preparedocumente quindi anche indicizzare quella tabella temporanea.


La risposta di Ph.E ha un esempio di costruzione di una tabella temporanea (da CSV).
crokusek,

12

Il modo corretto di IMHO è di memorizzare l'elenco in una stringa di caratteri (limitata in lunghezza da ciò che supporta il DBMS); l'unico trucco è che (per semplificare l'elaborazione) ho un separatore (una virgola nel mio esempio) all'inizio e alla fine della stringa. L'idea è di "normalizzare al volo", trasformando l'elenco in una tabella a una colonna che contiene una riga per valore. Questo ti permette di girare

in (ct1, ct2, ct3 ... ctn)

in un

in (seleziona ...)

o (la soluzione che preferirei probabilmente) un join regolare, se aggiungi semplicemente un "distinto" per evitare problemi con valori duplicati nell'elenco.

Sfortunatamente, le tecniche per tagliare una stringa sono abbastanza specifiche del prodotto. Ecco la versione di SQL Server:

 with qry(n, names) as
       (select len(list.names) - len(replace(list.names, ',', '')) - 1 as n,
               substring(list.names, 2, len(list.names)) as names
        from (select ',Doc,Grumpy,Happy,Sneezy,Bashful,Sleepy,Dopey,' names) as list
        union all
        select (n - 1) as n,
               substring(names, 1 + charindex(',', names), len(names)) as names
        from qry
        where n > 1)
 select n, substring(names, 1, charindex(',', names) - 1) dwarf
 from qry;

La versione Oracle:

 select n, substr(name, 1, instr(name, ',') - 1) dwarf
 from (select n,
             substr(val, 1 + instr(val, ',', 1, n)) name
      from (select rownum as n,
                   list.val
            from  (select ',Doc,Grumpy,Happy,Sneezy,Bashful,Sleepy,Dopey,' val
                   from dual) list
            connect by level < length(list.val) -
                               length(replace(list.val, ',', ''))));

e la versione di MySQL:

select pivot.n,
      substring_index(substring_index(list.val, ',', 1 + pivot.n), ',', -1) from (select 1 as n
     union all
     select 2 as n
     union all
     select 3 as n
     union all
     select 4 as n
     union all
     select 5 as n
     union all
     select 6 as n
     union all
     select 7 as n
     union all
     select 8 as n
     union all
     select 9 as n
     union all
     select 10 as n) pivot,    (select ',Doc,Grumpy,Happy,Sneezy,Bashful,Sleepy,Dopey,' val) as list where pivot.n <  length(list.val) -
                   length(replace(list.val, ',', ''));

(Naturalmente, "pivot" deve restituire tutte le righe del numero massimo di elementi che possiamo trovare nell'elenco)


11

Se hai SQL Server 2008 o versione successiva, utilizzerei un parametro con valori di tabella .

Se sei abbastanza sfortunato da rimanere bloccato su SQL Server 2005, puoi aggiungere una funzione CLR come questa,

[SqlFunction(
    DataAccessKind.None,
    IsDeterministic = true,
    SystemDataAccess = SystemDataAccessKind.None,
    IsPrecise = true,
    FillRowMethodName = "SplitFillRow",
    TableDefinintion = "s NVARCHAR(MAX)"]
public static IEnumerable Split(SqlChars seperator, SqlString s)
{
    if (s.IsNull)
        return new string[0];

    return s.ToString().Split(seperator.Buffer);
}

public static void SplitFillRow(object row, out SqlString s)
{
    s = new SqlString(row.ToString());
}

Che potresti usare in questo modo,

declare @desiredTags nvarchar(MAX);
set @desiredTags = 'ruby,rails,scruffy,rubyonrails';

select * from Tags
where Name in [dbo].[Split] (',', @desiredTags)
order by Count desc

10

Penso che questo sia un caso in cui una query statica non è la strada da percorrere. Crea dinamicamente l'elenco per la tua clausola in, scappa dalle virgolette singole e crea dinamicamente SQL. In questo caso probabilmente non vedrai molte differenze con qualsiasi metodo a causa del piccolo elenco, ma il metodo più efficiente è davvero quello di inviare l'SQL esattamente come è scritto nel tuo post. Penso che sia una buona abitudine scriverlo nel modo più efficiente, piuttosto che fare ciò che rende il codice più carino, o considerare una cattiva pratica costruire dinamicamente SQL.

Ho visto che le funzioni divise richiedono più tempo per l'esecuzione rispetto alla query stessa in molti casi in cui i parametri diventano grandi. Una procedura memorizzata con parametri con valori di tabella in SQL 2008 è l'unica altra opzione che prenderei in considerazione, anche se probabilmente sarà più lenta nel tuo caso. Probabilmente TVP sarà più veloce per elenchi di grandi dimensioni se stai cercando sulla chiave primaria di TVP, perché SQL creerà comunque una tabella temporanea per l'elenco (se l'elenco è grande). Non lo saprai per certo se non lo testerai.

Ho anche visto stored procedure che avevano 500 parametri con valori predefiniti di null e con WHERE Column1 IN (@ Param1, @ Param2, @ Param3, ..., @ Param500). Ciò ha comportato la creazione di una tabella temporanea da parte di SQL, l'esecuzione di un ordinamento / distinto, quindi una scansione della tabella anziché una ricerca dell'indice. Questo è essenzialmente ciò che faresti parametrizzando quella query, anche se su una scala abbastanza piccola da non fare una differenza evidente. Consiglio vivamente di non avere NULL negli elenchi IN, poiché se viene modificato in NOT IN non funzionerà come previsto. Potresti creare dinamicamente l'elenco dei parametri, ma l'unica cosa ovvia che potresti guadagnare è che gli oggetti sfuggiranno alle virgolette singole per te. Tale approccio è anche leggermente più lento sul lato dell'applicazione poiché gli oggetti devono analizzare la query per trovare i parametri.

Il riutilizzo dei piani di esecuzione per le procedure memorizzate o le query con parametri può fornire un guadagno in termini di prestazioni, ma ti bloccherà a un piano di esecuzione determinato dalla prima query che viene eseguita. In molti casi potrebbe non essere l'ideale per domande successive. Nel tuo caso, il riutilizzo dei piani di esecuzione sarà probabilmente un vantaggio, ma potrebbe non fare alcuna differenza poiché l'esempio è una query davvero semplice.

Note sulle scogliere:

Nel tuo caso, qualsiasi cosa tu faccia, che si tratti di parametrizzazione con un numero fisso di elementi nell'elenco (null se non utilizzato), la creazione dinamica della query con o senza parametri o l'utilizzo di stored procedure con parametri con valori di tabella non farà molta differenza . Tuttavia, le mie raccomandazioni generali sono le seguenti:

Il tuo caso / domande semplici con pochi parametri:

SQL dinamico, forse con parametri se il test mostra prestazioni migliori.

Query con piani di esecuzione riutilizzabili, chiamate più volte semplicemente modificando i parametri o se la query è complicata:

SQL con parametri dinamici.

Query con elenchi di grandi dimensioni:

Procedura memorizzata con parametri con valori di tabella. Se l'elenco può variare in larga misura, utilizzare WITH RECOMPILE sulla procedura memorizzata oppure utilizzare semplicemente SQL dinamico senza parametri per generare un nuovo piano di esecuzione per ogni query.


Cosa intendi con "procedura memorizzata" qui? Potresti pubblicare un esempio?
Struhtanov,

9

Forse possiamo usare XML qui:

    declare @x xml
    set @x='<items>
    <item myvalue="29790" />
    <item myvalue="31250" />
    </items>
    ';
    With CTE AS (
         SELECT 
            x.item.value('@myvalue[1]', 'decimal') AS myvalue
        FROM @x.nodes('//items/item') AS x(item) )

    select * from YourTable where tableColumnName in (select myvalue from cte)

1
CTEe @xpuò essere eliminato / integrato nella sottoselezione, se eseguito con molta attenzione, come mostrato in questo articolo .
robert4,

9

Mi avvicinerei a questo per impostazione predefinita con il passaggio di una funzione con valori di tabella (che restituisce una tabella da una stringa) alla condizione IN.

Ecco il codice per l'UDF (l'ho preso da Stack Overflow da qualche parte, non riesco a trovare l'origine in questo momento)

CREATE FUNCTION [dbo].[Split] (@sep char(1), @s varchar(8000))
RETURNS table
AS
RETURN (
    WITH Pieces(pn, start, stop) AS (
      SELECT 1, 1, CHARINDEX(@sep, @s)
      UNION ALL
      SELECT pn + 1, stop + 1, CHARINDEX(@sep, @s, stop + 1)
      FROM Pieces
      WHERE stop > 0
    )
    SELECT 
      SUBSTRING(@s, start, CASE WHEN stop > 0 THEN stop-start ELSE 512 END) AS s
    FROM Pieces
  )

Una volta ottenuto questo, il tuo codice sarebbe semplice come questo:

select * from Tags 
where Name in (select s from dbo.split(';','ruby;rails;scruffy;rubyonrails'))
order by Count desc

A meno che tu non abbia una stringa ridicolmente lunga, questo dovrebbe funzionare bene con l'indice della tabella.

Se necessario, puoi inserirlo in una tabella temporanea, indicizzarlo, quindi eseguire un join ...


8

Un'altra possibile soluzione è invece di passare un numero variabile di argomenti a una procedura memorizzata, passare una singola stringa contenente i nomi che stai cercando, ma renderli unici circondandoli con '<>'. Quindi utilizzare PATINDEX per trovare i nomi:

SELECT * 
FROM Tags 
WHERE PATINDEX('%<' + Name + '>%','<jo>,<john>,<scruffy>,<rubyonrails>') > 0

8

Utilizzare la seguente procedura memorizzata. Utilizza una funzione di divisione personalizzata, che può essere trovata qui .

 create stored procedure GetSearchMachingTagNames 
    @PipeDelimitedTagNames varchar(max), 
    @delimiter char(1) 
    as  
    begin
         select * from Tags 
         where Name in (select data from [dbo].[Split](@PipeDelimitedTagNames,@delimiter) 
    end

8

Se abbiamo stringhe memorizzate nella clausola IN con la virgola (,) delimitata, possiamo usare la funzione charindex per ottenere i valori. Se usi .NET, puoi mappare con SqlParameters.

Script DDL:

CREATE TABLE Tags
    ([ID] int, [Name] varchar(20))
;

INSERT INTO Tags
    ([ID], [Name])
VALUES
    (1, 'ruby'),
    (2, 'rails'),
    (3, 'scruffy'),
    (4, 'rubyonrails')
;

T-SQL:

DECLARE @Param nvarchar(max)

SET @Param = 'ruby,rails,scruffy,rubyonrails'

SELECT * FROM Tags
WHERE CharIndex(Name,@Param)>0

È possibile utilizzare l'istruzione precedente nel codice .NET e mappare il parametro con SqlParameter.

Demo di Fiddler

EDIT: crea la tabella chiamata SelectedTags usando il seguente script.

Script DDL:

Create table SelectedTags
(Name nvarchar(20));

INSERT INTO SelectedTags values ('ruby'),('rails')

T-SQL:

DECLARE @list nvarchar(max)
SELECT @list=coalesce(@list+',','')+st.Name FROM SelectedTags st

SELECT * FROM Tags
WHERE CharIndex(Name,@Param)>0

Puoi mostrare un esempio di questo lavoro in cui non esiste un elenco hardcoded di valori possibili?
John Saunders,

@JohnSaunders, ho modificato lo script senza usare alcun elenco hardcoded. Si prega di verificare.
Gowdhaman008,

3
Una limitazione con questa opzione. CharIndex restituisce 1 se viene trovata la stringa. IN restituisce una corrispondenza per termini esatti. CharIndex per "Stack" restituirà 1 per un termine "StackOverflow" IN no. C'è una piccola modifica a questa risposta usando PatIndex sopra che racchiude i nomi con '<'% name% '>' che supera questa limitazione. Soluzione creativa a questo problema però.
Richard Vivian,

7

Per un numero variabile di argomenti come questo l'unico modo di cui sono a conoscenza è generare l'SQL in modo esplicito o fare qualcosa che implichi il popolamento di una tabella temporanea con gli elementi desiderati e l'unione con la tabella temporanea.


7

In ColdFusion facciamo solo:

<cfset myvalues = "ruby|rails|scruffy|rubyonrails">
    <cfquery name="q">
        select * from sometable where values in <cfqueryparam value="#myvalues#" list="true">
    </cfquery>

7

Ecco una tecnica che ricrea una tabella locale da utilizzare in una stringa di query. Farlo in questo modo elimina tutti i problemi di analisi.

La stringa può essere costruita in qualsiasi lingua. In questo esempio ho usato SQL poiché quello era il problema originale che stavo cercando di risolvere. Avevo bisogno di un modo pulito per passare al volo i dati della tabella in una stringa da eseguire in seguito.

L'uso di un tipo definito dall'utente è facoltativo. La creazione del tipo viene creata una sola volta e può essere eseguita in anticipo. Altrimenti aggiungi semplicemente un tipo di tabella completa alla dichiarazione nella stringa.

Il modello generale è facile da estendere e può essere utilizzato per passare tabelle più complesse.

-- Create a user defined type for the list.
CREATE TYPE [dbo].[StringList] AS TABLE(
    [StringValue] [nvarchar](max) NOT NULL
)

-- Create a sample list using the list table type.
DECLARE @list [dbo].[StringList]; 
INSERT INTO @list VALUES ('one'), ('two'), ('three'), ('four')

-- Build a string in which we recreate the list so we can pass it to exec
-- This can be done in any language since we're just building a string.
DECLARE @str nvarchar(max);
SET @str = 'DECLARE @list [dbo].[StringList]; INSERT INTO @list VALUES '

-- Add all the values we want to the string. This would be a loop in C++.
SELECT @str = @str + '(''' + StringValue + '''),' FROM @list

-- Remove the trailing comma so the query is valid sql.
SET @str = substring(@str, 1, len(@str)-1)

-- Add a select to test the string.
SET @str = @str + '; SELECT * FROM @list;'

-- Execute the string and see we've pass the table correctly.
EXEC(@str)

7

In SQL Server 2016+ un'altra possibilità è utilizzare la OPENJSONfunzione.

Questo approccio è scritto nel blog di OPENJSON, uno dei modi migliori per selezionare le righe in base all'elenco di ID .

Un esempio completo di seguito

CREATE TABLE dbo.Tags
  (
     Name  VARCHAR(50),
     Count INT
  )

INSERT INTO dbo.Tags
VALUES      ('VB',982), ('ruby',1306), ('rails',1478), ('scruffy',1), ('C#',1784)

GO

CREATE PROC dbo.SomeProc
@Tags VARCHAR(MAX)
AS
SELECT T.*
FROM   dbo.Tags T
WHERE  T.Name IN (SELECT J.Value COLLATE Latin1_General_CI_AS
                  FROM   OPENJSON(CONCAT('[', @Tags, ']')) J)
ORDER  BY T.Count DESC

GO

EXEC dbo.SomeProc @Tags = '"ruby","rails","scruffy","rubyonrails"'

DROP TABLE dbo.Tags 

7

Ecco un'altra alternativa. Basta passare un elenco delimitato da virgole come parametro stringa alla procedura memorizzata e:

CREATE PROCEDURE [dbo].[sp_myproc]
    @UnitList varchar(MAX) = '1,2,3'
AS
select column from table
where ph.UnitID in (select * from CsvToInt(@UnitList))

E la funzione:

CREATE Function [dbo].[CsvToInt] ( @Array varchar(MAX))
returns @IntTable table
(IntValue int)
AS
begin
    declare @separator char(1)
    set @separator = ','
    declare @separator_position int
    declare @array_value varchar(MAX)

    set @array = @array + ','

    while patindex('%,%' , @array) <> 0
    begin

        select @separator_position = patindex('%,%' , @array)
        select @array_value = left(@array, @separator_position - 1)

        Insert @IntTable
        Values (Cast(@array_value as int))
        select @array = stuff(@array, 1, @separator_position, '')
    end
    return
end

6

Ho una risposta che non richiede un UDF, XML perché IN accetta un'istruzione select ad es. SELECT * FROM Test in cui Data IN (SELECT Value FROM TABLE)

Hai davvero solo bisogno di un modo per convertire la stringa in una tabella.

Questo può essere fatto con un CTE ricorsivo o una query con una tabella numerica (o Master..spt_value)

Ecco la versione CTE.

DECLARE @InputString varchar(8000) = 'ruby,rails,scruffy,rubyonrails'

SELECT @InputString = @InputString + ','

;WITH RecursiveCSV(x,y) 
AS 
(
    SELECT 
        x = SUBSTRING(@InputString,0,CHARINDEX(',',@InputString,0)),
        y = SUBSTRING(@InputString,CHARINDEX(',',@InputString,0)+1,LEN(@InputString))
    UNION ALL
    SELECT 
        x = SUBSTRING(y,0,CHARINDEX(',',y,0)),
        y = SUBSTRING(y,CHARINDEX(',',y,0)+1,LEN(y))
    FROM 
        RecursiveCSV 
    WHERE
        SUBSTRING(y,CHARINDEX(',',y,0)+1,LEN(y)) <> '' OR 
        SUBSTRING(y,0,CHARINDEX(',',y,0)) <> ''
)
SELECT
    * 
FROM 
    Tags
WHERE 
    Name IN (select x FROM RecursiveCSV)
OPTION (MAXRECURSION 32767);

6

Uso una versione più concisa della risposta più votata :

List<SqlParameter> parameters = tags.Select((s, i) => new SqlParameter("@tag" + i.ToString(), SqlDbType.NVarChar(50)) { Value = s}).ToList();

var whereCondition = string.Format("tags in ({0})", String.Join(",",parameters.Select(s => s.ParameterName)));

Fa scorrere due volte i parametri del tag; ma non importa la maggior parte delle volte (non sarà il collo di bottiglia; in caso affermativo, srotolare il ciclo).

Se sei davvero interessato alle prestazioni e non vuoi scorrere due volte il ciclo, ecco una versione meno bella:

var parameters = new List<SqlParameter>();
var paramNames = new List<string>();
for (var i = 0; i < tags.Length; i++)  
{
    var paramName = "@tag" + i;

    //Include size and set value explicitly (not AddWithValue)
    //Because SQL Server may use an implicit conversion if it doesn't know
    //the actual size.
    var p = new SqlParameter(paramName, SqlDbType.NVarChar(50) { Value = tags[i]; } 
    paramNames.Add(paramName);
    parameters.Add(p);
}

var inClause = string.Join(",", paramNames);

5

Ecco un'altra risposta a questo problema.

(nuova versione pubblicata il 04/06/13).

    private static DataSet GetDataSet(SqlConnectionStringBuilder scsb, string strSql, params object[] pars)
    {
        var ds = new DataSet();
        using (var sqlConn = new SqlConnection(scsb.ConnectionString))
        {
            var sqlParameters = new List<SqlParameter>();
            var replacementStrings = new Dictionary<string, string>();
            if (pars != null)
            {
                for (int i = 0; i < pars.Length; i++)
                {
                    if (pars[i] is IEnumerable<object>)
                    {
                        List<object> enumerable = (pars[i] as IEnumerable<object>).ToList();
                        replacementStrings.Add("@" + i, String.Join(",", enumerable.Select((value, pos) => String.Format("@_{0}_{1}", i, pos))));
                        sqlParameters.AddRange(enumerable.Select((value, pos) => new SqlParameter(String.Format("@_{0}_{1}", i, pos), value ?? DBNull.Value)).ToArray());
                    }
                    else
                    {
                        sqlParameters.Add(new SqlParameter(String.Format("@{0}", i), pars[i] ?? DBNull.Value));
                    }
                }
            }
            strSql = replacementStrings.Aggregate(strSql, (current, replacementString) => current.Replace(replacementString.Key, replacementString.Value));
            using (var sqlCommand = new SqlCommand(strSql, sqlConn))
            {
                if (pars != null)
                {
                    sqlCommand.Parameters.AddRange(sqlParameters.ToArray());
                }
                else
                {
                    //Fail-safe, just in case a user intends to pass a single null parameter
                    sqlCommand.Parameters.Add(new SqlParameter("@0", DBNull.Value));
                }
                using (var sqlDataAdapter = new SqlDataAdapter(sqlCommand))
                {
                    sqlDataAdapter.Fill(ds);
                }
            }
        }
        return ds;
    }

Saluti.


4

La sola mossa vincente è non giocare.

Nessuna variabilità infinita per te. Solo variabilità finita.

Nell'SQL hai una clausola come questa:

and ( {1}==0 or b.CompanyId in ({2},{3},{4},{5},{6}) )

Nel codice C # fai qualcosa del genere:

  int origCount = idList.Count;
  if (origCount > 5) {
    throw new Exception("You may only specify up to five originators to filter on.");
  }
  while (idList.Count < 5) { idList.Add(-1); }  // -1 is an impossible value
  return ExecuteQuery<PublishDate>(getValuesInListSQL, 
               origCount,   
               idList[0], idList[1], idList[2], idList[3], idList[4]);

Quindi fondamentalmente se il conteggio è 0, allora non c'è filtro e tutto passa. Se il conteggio è maggiore di 0, allora il valore deve essere nell'elenco, ma l'elenco è stato compilato su cinque con valori impossibili (in modo che l'SQL abbia ancora senso)

A volte la soluzione zoppa è l'unica che funziona davvero.

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.