Parametrizar una cláusula SQL IN

1041

¿Cómo parametrizo una consulta que contiene una INcláusula con un número variable de argumentos, como este?

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

En esta consulta, el número de argumentos puede ser de 1 a 5.

Preferiría no usar un procedimiento almacenado dedicado para esto (o XML), pero si hay alguna forma elegante específica para SQL Server 2008 , estoy abierto a eso.

Jeff Atwood
fuente

Respuestas:

315

Aquí hay una técnica rápida y sucia que he usado:

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

Así que aquí está el código 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);
}

Dos advertencias:

  • El rendimiento es terrible. LIKE "%...%"las consultas no están indexadas.
  • Asegúrese de no tener ninguna |etiqueta en blanco o nula o esto no funcionará

Hay otras formas de lograr esto que algunas personas pueden considerar más limpias, así que por favor sigan leyendo.

Joel Spolsky
fuente
119
Eso será muy lento
Matt Rogish
13
Sí, este es un escaneo de tabla. Ideal para 10 filas, pésimo para 100,000.
Will Hartung
17
Asegúrese de probar en las etiquetas que tienen tuberías en ellos.
Joel Coehoorn
17
Esto ni siquiera responde la pregunta. De acuerdo, es fácil ver dónde agregar los parámetros, pero ¿cómo puede aceptar esta solución si ni siquiera se molesta en parametrizar la consulta? Solo parece más simple que @Mark Brackett porque no está parametrizado.
tvanfosson
21
¿Qué pasa si su etiqueta es 'ruby | rails'? Coincidirá, lo que estará mal. Cuando implemente tales soluciones, debe asegurarse de que las etiquetas no contengan tuberías o de filtrarlas explícitamente: seleccione * de Etiquetas donde '| ruby ​​| rails | scruffy | rubyonrails |' como '% |' + Nombre + '|%' Y nombre no como '%!%'
AK
729

Puedes parametrizar cada valor, así que algo como:

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]);
    }
}

Lo que te 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, esto no está abierto a la inyección SQL . El único texto inyectado en CommandText no se basa en la entrada del usuario. Se basa únicamente en el prefijo "@tag" codificado y el índice de una matriz. El índice siempre será un número entero, no es generado por el usuario y es seguro.

Los valores ingresados ​​por el usuario todavía están incluidos en los parámetros, por lo que no hay vulnerabilidad allí.

Editar:

Dejando a un lado las preocupaciones de la inyección, tenga en cuenta que la construcción del texto del comando para acomodar un número variable de parámetros (como arriba) impide la capacidad del servidor SQL para aprovechar las consultas en caché. El resultado neto es que casi seguro pierde el valor de usar parámetros en primer lugar (en lugar de simplemente insertar las cadenas de predicados en el propio SQL).

No es que los planes de consulta en caché no sean valiosos, pero IMO esta consulta no es lo suficientemente complicada como para ver muchos beneficios. Si bien los costos de compilación pueden acercarse (o incluso exceder) los costos de ejecución, todavía está hablando milisegundos.

Si tiene suficiente RAM, esperaría que SQL Server probablemente también almacenara en caché un plan para los recuentos comunes de parámetros. Supongo que siempre puede agregar cinco parámetros y dejar que las etiquetas no especificadas sean NULL: el plan de consulta debería ser el mismo, pero me parece bastante feo y no estoy seguro de que valga la pena la microoptimización (aunque, en Desbordamiento de pila: puede valer la pena).

Además, SQL Server 7 y versiones posteriores parametrizarán automáticamente las consultas , por lo que el uso de parámetros no es realmente necesario desde el punto de vista del rendimiento; sin embargo, es crítico desde el punto de vista de la seguridad, especialmente con datos ingresados ​​por el usuario como este.

Mark Brackett
fuente
2
Básicamente lo mismo que mi respuesta a la pregunta "relacionada" y obviamente la mejor solución, ya que es constructiva y eficiente en lugar de interpretativa (mucho más difícil).
tvanfosson
49
Así es como LINQ to SQL lo hace, por cierto
Mark Cidade
3
@Pure: el objetivo de esto es evitar la inyección de SQL, a la que sería vulnerable si utilizara SQL dinámico.
Ray
44
@Dios de datos: sí, supongo que si necesita más de 2100 etiquetas, necesitará una solución diferente. Pero Basarb solo podría alcanzar 2100 si la longitud promedio de la etiqueta fuera <3 caracteres (ya que también necesita un delimitador). msdn.microsoft.com/en-us/library/ms143432.aspx
Mark Brackett
2
@bonCodigo: los valores seleccionados están en una matriz; simplemente recorre la matriz y agrega un parámetro (con el sufijo del índice) para cada uno.
Mark Brackett
249

Para SQL Server 2008, puede usar un parámetro con valores de tabla . Es un poco de trabajo, pero podría decirse que es más limpio que mi otro método .

Primero, debes crear un tipo

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

Entonces, su código ADO.NET se ve así:

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;
    });
}
Mark Brackett
fuente
41
probamos esto y los parámetros con valores de tabla son DOG lentos. Literalmente, es más rápido ejecutar 5 consultas que hacer un TVP.
Jeff Atwood
44
@JeffAtwood - ¿Has intentado reorganizar la consulta a algo así SELECT * FROM tags WHERE tags.name IN (SELECT name from @tvp);? En teoría, este realmente debería ser el enfoque más rápido. Puede usar índices relevantes (p. Ej., Un índice en el nombre de la etiqueta que INCLUDEcontar sería ideal), y SQL Server debería hacer algunos intentos para obtener todas las etiquetas y sus cuentas. ¿Cómo se ve el plan?
Nick Chammas
99
También probé esto y es RÁPIDO COMO LA ILUMINACIÓN (en comparación con la construcción de una cadena IN grande). Sin embargo, tuve algunos problemas para configurar el parámetro ya que constantemente recibía "No se pudo convertir el valor del parámetro de Int32 [] a IEnumerable`1". De todos modos, resolvió eso y aquí hay una muestra que hice pastebin.com/qHP05CXc
Fredrik Johansson
66
@FredrikJohansson - ¡De 130 votos a favor, puede ser la única carrera que realmente intentó ejecutar esto! Cometí un error al leer los documentos, y realmente necesitas un IEnumerable <SqlDataRecord>, no cualquier IEnumerable. El código ha sido actualizado.
Mark Brackett
3
@MarkBrackett ¡Genial con una actualización! Accidentalmente, este código realmente me salvó el día, ya que estoy consultando un índice de búsqueda de Lucene y, a veces, devuelve más de 50,000 hits que necesitan ser verificados dos veces en el servidor SQL. Así que creo una matriz de int [] (document / Claves SQL) y luego aparece el código anterior. Todo el OP ahora toma menos de 200 ms :)
Fredrik Johansson
188

La pregunta original era "¿Cómo parametrizo una consulta ..."

Permítanme decir aquí, que esta no es una respuesta a la pregunta original. Ya hay algunas demostraciones de eso en otras buenas respuestas.

Dicho esto, continúe y marque esta respuesta, vote hacia abajo, márquela como no una respuesta ... haga lo que crea que es correcto.

Vea la respuesta de Mark Brackett para la respuesta preferida que yo (y otras 231) voté. El enfoque dado en su respuesta permite 1) el uso efectivo de variables de enlace, y 2) para predicados que son sargable.

Respuesta seleccionada

Lo que quiero abordar aquí es el enfoque dado en la respuesta de Joel Spolsky, la respuesta "seleccionada" como la respuesta correcta.

El enfoque de Joel Spolsky es inteligente. Y funciona razonablemente, exhibirá un comportamiento predecible y un rendimiento predecible, dados los valores "normales", y con los casos límite normativos, como NULL y la cadena vacía. Y puede ser suficiente para una aplicación en particular.

Pero en términos de generalización de este enfoque, consideremos también los casos de esquina más oscuros, como cuando la Namecolumna contiene un carácter comodín (como lo reconoce el predicado LIKE). El carácter comodín que veo más comúnmente usado es %(un signo de porcentaje). Así que lidiemos con eso aquí ahora, y luego pasemos a otros casos.

Algunos problemas con% character

Considere un valor de Nombre de 'pe%ter'. (Para los ejemplos aquí, utilizo un valor de cadena literal en lugar del nombre de la columna.) Una consulta del formulario devolvería una fila con un valor de Nombre de 'pe% ter':

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

Pero esa misma fila no se devolverá si se invierte el orden de los términos de búsqueda:

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

El comportamiento que observamos es un poco extraño. Cambiar el orden de los términos de búsqueda en la lista cambia el conjunto de resultados.

Casi no hace falta decir que no queremos pe%terigualar la mantequilla de maní, no importa cuánto le guste.

Estuche de esquina oscuro

(Sí, estaré de acuerdo en que este es un caso oscuro. Probablemente uno que probablemente no sea probado. No esperaríamos un comodín en el valor de una columna. Podemos suponer que la aplicación evita que se almacene dicho valor. Pero En mi experiencia, rara vez he visto una restricción de la base de datos que específicamente no permitía caracteres o patrones que se considerarían comodines en el lado derecho de un LIKEoperador de comparación.

Parchar un agujero

Un enfoque para parchar este agujero es escapar del %personaje comodín. (Para cualquiera que no esté familiarizado con la cláusula de escape del operador, aquí hay un enlace a la documentación de SQL Server .

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

Ahora podemos hacer coincidir el% literal. Por supuesto, cuando tenemos un nombre de columna, vamos a necesitar escapar dinámicamente del comodín. Podemos usar la REPLACEfunción para encontrar ocurrencias del %personaje e insertar un carácter de barra diagonal inversa delante de cada uno, de esta manera:

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

Eso resuelve el problema con el comodín%. Casi.

Escapar del escape

Reconocemos que nuestra solución ha introducido otro problema. El personaje de escape. Vemos que también vamos a necesitar escapar de cualquier ocurrencia del personaje de escape en sí. Esta vez, usamos el! como el personaje de escape:

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

El guión bajo también

Ahora que estamos en un rollo, podemos agregar otro REPLACEidentificador al comodín de subrayado. Y solo por diversión, esta vez, usaremos $ como el personaje de escape.

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

Prefiero este enfoque a escapar porque funciona en Oracle y MySQL, así como en SQL Server. (Usualmente uso la barra invertida \ como el carácter de escape, ya que ese es el carácter que usamos en las expresiones regulares. ¡Pero por qué estar limitado por la convención!

Esos corchetes molestos

SQL Server también permite que los caracteres comodín se traten como literales encerrándolos entre paréntesis []. Así que todavía no hemos terminado de arreglar, al menos para SQL Server. Dado que los pares de paréntesis tienen un significado especial, también necesitaremos escapar de esos. Si logramos escapar adecuadamente de los corchetes, al menos no tendremos que molestarnos con el guión -y el quilate ^dentro de los corchetes. Y podemos dejar cualquier %y _dentro de los corchetes escapados, ya que básicamente habremos deshabilitado el significado especial de los corchetes.

Encontrar pares de paréntesis no debería ser tan difícil. Es un poco más difícil que manejar las ocurrencias de singleton% y _. (Tenga en cuenta que no es suficiente simplemente escapar de todas las apariciones de paréntesis, porque un paréntesis único se considera literal y no necesita escapar. La lógica se está volviendo un poco más borrosa de lo que puedo manejar sin ejecutar más casos de prueba .)

La expresión en línea se vuelve desordenada

Esa expresión en línea en el SQL se está volviendo más larga y fea. Probablemente podamos hacerlo funcionar, pero el cielo ayuda a la pobre alma que viene detrás y tiene que descifrarlo. Como soy un gran admirador de las expresiones en línea, me inclino a no usar una aquí, principalmente porque no quiero tener que dejar un comentario explicando la razón del desastre y disculpándome por ello.

Una función donde?

Bien, entonces, si no manejamos eso como una expresión en línea en el SQL, la alternativa más cercana que tenemos es una función definida por el usuario. Y sabemos que eso no acelerará las cosas (a menos que podamos definir un índice en él, como podríamos hacerlo con Oracle). Si tenemos que crear una función, podríamos hacerlo mejor en el código que llama al SQL declaración.

Y esa función puede tener algunas diferencias de comportamiento, dependiendo del DBMS y la versión. (Un agradecimiento a todos los desarrolladores de Java tan interesados ​​en poder utilizar cualquier motor de base de datos indistintamente).

Conocimiento del dominio

Es posible que tengamos un conocimiento especializado del dominio de la columna, (es decir, el conjunto de valores permitidos para la columna. Podemos saber a priori que los valores almacenados en la columna nunca contendrán un signo de porcentaje, un guión bajo o un corchete pares. En ese caso, solo incluimos un comentario rápido de que esos casos están cubiertos.

Los valores almacenados en la columna pueden permitir% o _ caracteres, pero una restricción puede requerir que esos valores se escapen, tal vez usando un carácter definido, de modo que los valores sean como la comparación "segura". Una vez más, un comentario rápido sobre el conjunto de valores permitidos, y en particular qué personaje se usa como personaje de escape, y sigue el enfoque de Joel Spolsky.

Pero, sin el conocimiento especializado y una garantía, es importante para nosotros al menos considerar manejar esos oscuros casos de esquina, y considerar si el comportamiento es razonable y "según la especificación".


Otros temas recapitulados

Creo que otros ya han señalado suficientemente algunas de las otras áreas de preocupación comúnmente consideradas:

  • Inyección de SQL (tomar lo que parece ser información suministrada por el usuario e incluirla en el texto de SQL en lugar de proporcionarla a través de variables de enlace. No es necesario usar variables de enlace, es solo un enfoque conveniente para frustrar con la inyección de SQL. Hay otros maneras de lidiar con eso:

  • plan optimizador que utiliza exploración de índice en lugar de búsquedas de índice, posible necesidad de una expresión o función para escapar de comodines (posible índice de expresión o función)

  • el uso de valores literales en lugar de variables de enlace afecta la escalabilidad


Conclusión

Me gusta el enfoque de Joel Spolsky. Es inteligente Y funciona.

Pero tan pronto como lo vi, inmediatamente vi un problema potencial con él, y no es mi naturaleza dejarlo pasar. No quiero criticar los esfuerzos de los demás. Sé que muchos desarrolladores toman su trabajo muy personalmente, porque invierten mucho en él y les importa mucho. Entonces, por favor, comprenda, esto no es un ataque personal. Lo que estoy identificando aquí es el tipo de problema que surge en la producción en lugar de las pruebas.

Sí, me he alejado mucho de la pregunta original. Pero, ¿dónde más dejar esta nota sobre lo que considero un problema importante con la respuesta "seleccionada" para una pregunta?

spencer7593
fuente
¿puede informarnos si utiliza o le gustan las consultas parametrizadas? en este caso particular, ¿es correcto saltar la regla de 'usar consultas parametrizadas' y desinfectar con el idioma original?
MUCHAS
2
@Luis: sí, prefiero usar variables de enlace en las sentencias SQL, y solo evitaré las variables de enlace cuando usarlas cause un problema de rendimiento. mi patrón normativo para el problema original sería crear dinámicamente la instrucción SQL con el número requerido de marcadores de posición en la lista IN, y luego vincular cada valor a uno de los marcadores de posición. Vea la respuesta de Mark Brackett, que es la respuesta que yo (y otros 231) voté.
spencer7593
133

Puedes pasar el parámetro como una cadena

Entonces tienes la cuerda

DECLARE @tags

SET @tags = ruby|rails|scruffy|rubyonrails

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

Entonces todo lo que tiene que hacer es pasar la cadena como 1 parámetro.

Aquí está la función de división que 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
David Basarab
fuente
2
También puede unirse a la función de tabla con este enfoque.
Michael Haren
Yo uso una solución similar a esto en Oracle. No tiene que volver a analizarse como algunas de las otras soluciones.
Leigh Riffel
99
Este es un enfoque de base de datos puro, el otro requiere trabajo en el código fuera de la base de datos.
David Basarab
¿Esto a una exploración de tabla o puede aprovechar los índices, etc.?
Pure.Krome
mejor sería usar CROSS APPLY contra la función de la tabla SQL (al menos en 2005 en adelante), que esencialmente se une contra la tabla que se devuelve
adolf ajo
66

Escuché a Jeff / Joel hablar sobre esto en el podcast de hoy ( episodio 34 , 2008-12-16 (MP3, 31 MB), 1 h 03 min 38 segundos - 1 h 06 min 45 segundos), y pensé que recordé Stack Overflow estaba usando LINQ to SQL , pero tal vez fue abandonado. Aquí está lo mismo en LINQ to SQL.

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

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

Eso es. Y sí, LINQ ya mira hacia atrás lo suficiente, pero la Containscláusula me parece extra hacia atrás. Cuando tuve que hacer una consulta similar para un proyecto en el trabajo, naturalmente intenté hacerlo de la manera incorrecta haciendo una unión entre la matriz local y la tabla de SQL Server, pensando que el traductor LINQ to SQL sería lo suficientemente inteligente como para manejar el traducción de alguna manera. No lo hizo, pero proporcionó un mensaje de error que fue descriptivo y me señaló hacia el uso de Contains .

De todos modos, si ejecuta esto en el LINQPad altamente recomendado y ejecuta esta consulta, puede ver el SQL real que generó el proveedor de SQL LINQ. Le mostrará cada uno de los valores que se parametrizan en una INcláusula.

Peter Meyer
fuente
50

Si llama desde .NET, puede usar 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});

Aquí Dapper piensa, así que no tienes que hacerlo. Algo similar es posible con LINQ to SQL , por supuesto:

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;
Marc Gravell
fuente
11
que resulta ser lo que usamos en esta página, para la pregunta real formulada (apuesto) i.stack.imgur.com/RBAjL.png
Sam Saffron
3
Tenga en cuenta que dapper ahora también es compatible con los parámetros de valor de tabla como ciudadanos de primera clase
Marc Gravell
Esto se cae si los nombres son largos
cs0815
29

Esta es posiblemente una forma medio desagradable de hacerlo, lo usé una vez, fue bastante efectivo.

Dependiendo de sus objetivos, puede ser útil.

  1. Crea una tabla temporal con una columna.
  2. INSERT cada valor de búsqueda en esa columna.
  3. En lugar de usar un IN, puede usar sus JOINreglas estándar . (Flexibilidad ++)

Esto tiene un poco de flexibilidad adicional en lo que puede hacer, pero es más adecuado para situaciones en las que tiene que consultar una tabla grande, con una buena indexación, y desea usar la lista parametrizada más de una vez. Ahorra tener que ejecutarlo dos veces y tener todo el saneamiento hecho manualmente.

Nunca llegué a perfilar exactamente qué tan rápido era, pero en mi situación era necesario.

Kent Fredric
fuente
¡Esto no es desagradable en absoluto! Aún más, es en mi humilde opinión una manera muy limpia. Y si observa el plan de ejecución, verá que es igual a la cláusula IN. En lugar de una tabla temporal, también puede crear una tabla fija con índices, donde almacena los parámetros junto con el SESSIONID.
SQL Police
27

En SQL Server 2016+podría usar la STRING_SPLITfunción:

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;

Demo en vivo

La respuesta aceptada , por supuesto, funcionará y es una de las formas de hacerlo, pero es antipatrón.

E. Encuentra filas por lista de valores

Este es el reemplazo de un antipatrón común, como la creación de una cadena SQL dinámica en la capa de aplicación o Transact-SQL, o mediante el uso del operador LIKE:

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

Anexo :

Para mejorar el STRING_SPLIT estimación de filas de la función de tabla, es una buena idea materializar los valores divididos como tabla / variable de tabla temporal:

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 en vivo

Relacionado: Cómo pasar una lista de valores a un procedimiento almacenado


La pregunta original tiene requerimiento SQL Server 2008. Debido a que esta pregunta a menudo se usa como duplicado, he agregado esta respuesta como referencia.

Lukasz Szozda
fuente
1
No he probado esto, pero siento que esta es la solución más limpia de 2016+. Todavía me gustaría poder pasar una serie de int, pero hasta entonces ...
Daniel
24

Tenemos una función que crea una variable de tabla a la que puede unirse:

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 

Entonces:

@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
David Robbins
fuente
20

Esto es asqueroso, pero si tiene garantizado tener al menos uno, puede hacer lo siguiente:

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

Tener SQL ('etiqueta1', 'etiqueta2', 'etiqueta1', 'etiqueta1', 'etiqueta1') será fácilmente optimizado por SQL Server. Además, obtienes búsquedas directas de índices

Matt Rogish
fuente
1
Los parámetros opcionales con comprobaciones nulas estropean el rendimiento, ya que el optimizador requiere la cantidad de parámetros utilizados para crear consultas eficientes. Una consulta para 5 parámetros puede necesitar un plan de consulta diferente que uno para 500 parámetros.
Erik Hart
18

En mi opinión, la mejor fuente para resolver este problema es lo que se ha publicado en este sitio:

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

Utilizar:

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

CRÉDITOS PARA: Dinakar Nethi

Paulo Henrique
fuente
Gran respuesta, limpio y modular, ejecución súper rápida, excepto el análisis inicial de CSV en una tabla (una vez, un pequeño número de elementos). ¿Aunque podría usar charindex () más simple / rápido en lugar de patindex ()? Charindex () también permite el argumento 'start_location' que puede evitar cortar la cadena de entrada cada iter? Para responder a la pregunta original, solo puede unirse con el resultado de la función.
crokusek
18

Pasaría un parámetro de tipo de tabla (ya que es SQL Server 2008 ) y haría una where existsunión interna. También puede usar XML, usando sp_xml_preparedocument, y luego incluso indexar esa tabla temporal.

eulerfx
fuente
La respuesta de Ph.E tiene un ejemplo de tabla temporal de construcción (de csv).
crokusek
12

La manera correcta en mi humilde opinión es almacenar la lista en una cadena de caracteres (limitada en longitud por lo que admite el DBMS); El único truco es que (para simplificar el procesamiento) tengo un separador (una coma en mi ejemplo) al principio y al final de la cadena. La idea es "normalizar sobre la marcha", convirtiendo la lista en una tabla de una columna que contenga una fila por valor. Esto te permite girar

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

en una

en (seleccionar ...)

o (la solución probablemente preferiría) una unión regular, si solo agrega un "distintivo" para evitar problemas con valores duplicados en la lista.

Desafortunadamente, las técnicas para cortar una cadena son bastante específicas del producto. Aquí está la versión de 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 versión de 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, ',', ''))));

y la versión de 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, ',', ''));

(Por supuesto, "pivote" debe devolver tantas filas como la cantidad máxima de elementos que podemos encontrar en la lista)

Jeff Atwood
fuente
11

Si tiene SQL Server 2008 o posterior, usaría un parámetro de valor de tabla .

Si tiene la mala suerte de estar atascado en SQL Server 2005 , puede agregar una función CLR como esta,

[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());
}

Que podrías usar así,

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

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

Creo que este es un caso cuando una consulta estática no es el camino a seguir. Cree dinámicamente la lista para su cláusula in, escape de sus comillas simples y cree dinámicamente SQL. En este caso, probablemente no verá mucha diferencia con ningún método debido a la pequeña lista, pero el método más eficiente es enviar el SQL exactamente como está escrito en su publicación. Creo que es un buen hábito escribirlo de la manera más eficiente, en lugar de hacer lo que hace el código más bonito, o considerar que es una mala práctica construir dinámicamente SQL.

He visto que las funciones divididas tardan más en ejecutarse que la consulta en sí en muchos casos donde los parámetros se hacen más grandes. Un procedimiento almacenado con parámetros con valores de tabla en SQL 2008 es la única otra opción que consideraría, aunque probablemente sea más lento en su caso. TVP probablemente solo será más rápido para listas grandes si está buscando en la clave principal de TVP, porque SQL creará una tabla temporal para la lista de todos modos (si la lista es grande). No lo sabrá con seguridad a menos que lo pruebe.

También he visto procedimientos almacenados que tenían 500 parámetros con valores predeterminados de nulo y que tenían WHERE Column1 IN (@ Param1, @ Param2, @ Param3, ..., @ Param500). Esto hizo que SQL construyera una tabla temporal, hiciera una clasificación / distinción y luego hiciera un escaneo de la tabla en lugar de una búsqueda de índice. Eso es esencialmente lo que estaría haciendo al parametrizar esa consulta, aunque en una escala lo suficientemente pequeña como para que no haga una diferencia notable. Recomiendo encarecidamente que no tenga NULL en sus listas IN, ya que si eso se cambia a NOT IN no actuará como se esperaba. Podría construir dinámicamente la lista de parámetros, pero lo único obvio que obtendría es que los objetos escaparían de las comillas simples por usted. Ese enfoque también es un poco más lento al final de la aplicación, ya que los objetos tienen que analizar la consulta para encontrar los parámetros.

La reutilización de planes de ejecución para procedimientos almacenados o consultas parametrizadas puede proporcionarle una ganancia de rendimiento, pero lo bloqueará en un plan de ejecución determinado por la primera consulta que se ejecute. Eso puede ser menos que ideal para consultas posteriores en muchos casos. En su caso, la reutilización de los planes de ejecución probablemente será una ventaja, pero podría no hacer ninguna diferencia, ya que el ejemplo es una consulta realmente simple.

Notas de los acantilados:

Para su caso, haga lo que haga, ya sea la parametrización con un número fijo de elementos en la lista (nulo si no se usa), construir dinámicamente la consulta con o sin parámetros, o usar procedimientos almacenados con parámetros con valores de tabla no hará mucha diferencia . Sin embargo, mis recomendaciones generales son las siguientes:

Su caso / consultas simples con pocos parámetros:

SQL dinámico, tal vez con parámetros si las pruebas muestran un mejor rendimiento.

Consultas con planes de ejecución reutilizables, llamadas múltiples veces simplemente cambiando los parámetros o si la consulta es complicada:

SQL con parámetros dinámicos.

Consultas con grandes listas:

Procedimiento almacenado con parámetros con valores de tabla. Si la lista puede variar en gran medida, use WITH RECOMPILE en el procedimiento almacenado, o simplemente use SQL dinámico sin parámetros para generar un nuevo plan de ejecución para cada consulta.

Scott
fuente
¿Qué quiere decir con "procedimiento almacenado" aquí? ¿Podría publicar un ejemplo?
struhtanov
9

Puede ser que podamos usar XML aquí:

    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)
MindLoggedOut
fuente
1
CTEy @xse puede eliminar / incluir en la subselección, si se hace con mucho cuidado, como se muestra en este artículo .
robert4
9

Me acercaría a esto de manera predeterminada al pasar una función con valores de tabla (que devuelve una tabla de una cadena) a la condición IN.

Aquí está el código para el UDF (lo obtuve de Stack Overflow en alguna parte, no puedo encontrar la fuente en este 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 vez que tenga esto, su código sería tan simple como esto:

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

A menos que tenga una cadena ridículamente larga, esto debería funcionar bien con el índice de la tabla.

Si es necesario, puede insertarlo en una tabla temporal, indexarlo y luego ejecutar una unión ...

Eli Ekstein
fuente
8

Otra posible solución es en lugar de pasar un número variable de argumentos a un procedimiento almacenado, pasar una sola cadena que contenga los nombres que busca, pero hacerlos únicos rodeándolos con '<>'. Luego use PATINDEX para encontrar los nombres:

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

Use el siguiente procedimiento almacenado. Utiliza una función de división personalizada, que se puede encontrar aquí .

 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
mangeshkt
fuente
8

Si tenemos cadenas almacenadas dentro de la cláusula IN con la coma (,) delimitada, podemos usar la función charindex para obtener los valores. Si usa .NET, puede mapear 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

Puede usar la declaración anterior en su código .NET y asignar el parámetro con SqlParameter.

Demo de Fiddler

EDITAR: cree la tabla llamada SelectedTags con el siguiente 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
Gowdhaman008
fuente
¿Puede mostrar un ejemplo de este trabajo donde no hay una lista codificada de valores posibles?
John Saunders el
@JohnSaunders, he editado el script sin usar ninguna lista codificada. Por favor verificar.
Gowdhaman008
3
Una limitación con esta opción. CharIndex devuelve 1 si se encuentra la cadena. IN devuelve una coincidencia para términos exactos. CharIndex para "Stack" devolverá 1 por un término "StackOverflow" IN no lo hará. Hay una pequeña modificación a esta respuesta usando PatIndex arriba que encierra los nombres con '<'% name% '>' que supera esta limitación. Sin embargo, la solución creativa a este problema.
Richard Vivian
7

Para un número variable de argumentos como este, la única forma que conozco es generar el SQL explícitamente o hacer algo que implique llenar una tabla temporal con los elementos que desea y unirlos contra la tabla temporal.

Preocupado por TunbridgeWells
fuente
7

En ColdFusion solo hacemos:

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

Aquí hay una técnica que recrea una tabla local para ser utilizada en una cadena de consulta. Hacerlo de esta manera elimina todos los problemas de análisis.

La cadena se puede construir en cualquier idioma. En este ejemplo, usé SQL ya que ese era el problema original que estaba tratando de resolver. Necesitaba una forma limpia de pasar los datos de la tabla sobre la marcha en una cadena para ejecutarlos más tarde.

Usar un tipo definido por el usuario es opcional. La creación del tipo solo se crea una vez y se puede hacer con anticipación. De lo contrario, simplemente agregue un tipo de tabla completa a la declaración en la cadena.

El patrón general es fácil de extender y puede usarse para pasar tablas más complejas.

-- 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)
Pez roca
fuente
7

En SQL Server 2016+, otra posibilidad es usar la OPENJSONfunción.

Este enfoque se bloguea en OPENJSON, una de las mejores formas de seleccionar filas por lista de identificadores .

Un ejemplo completo a continuación

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 
Martin Smith
fuente
7

Aquí hay otra alternativa. Simplemente pase una lista delimitada por comas como un parámetro de cadena al procedimiento almacenado y:

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

Y la función:

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
Metáfora
fuente
6

Tengo una respuesta que no requiere un UDF, XML porque IN acepta una instrucción select, por ejemplo, SELECT * FROM Prueba donde Data IN (SELECT Value FROM TABLE)

Realmente solo necesita una forma de convertir la cadena en una tabla.

Esto se puede hacer con un CTE recursivo, o una consulta con una tabla de números (o Master..spt_value)

Aquí está la versión 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);
Runonthespot
fuente
6

Utilizo una versión más concisa de la respuesta más votada :

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

Hace un bucle a través de los parámetros de la etiqueta dos veces; pero eso no importa la mayor parte del tiempo (no será su cuello de botella; si es así, desenrolle el bucle).

Si está realmente interesado en el rendimiento y no quiere repetir el ciclo dos veces, aquí hay una versión menos hermosa:

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);
George Stocker
fuente
5

Aquí hay otra respuesta a este problema.

(nueva versión publicada el 4/6/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;
    }

Salud.

Darek
fuente
4

El único movimiento ganador es no jugar.

No hay variabilidad infinita para ti. Solo variabilidad finita.

En el SQL tienes una cláusula como esta:

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

En el código C # haces algo como esto:

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

Básicamente, si el recuento es 0, entonces no hay filtro y todo pasa. Si el recuento es superior a 0, entonces el valor debe estar en la lista, pero la lista se ha completado a cinco con valores imposibles (para que el SQL todavía tenga sentido)

A veces, la solución poco convincente es la única que realmente funciona.

Jason Henriksen
fuente