2009-07-01 22 views
72

Necesito pasar una matriz de "id" a un procedimiento almacenado, para eliminar todas las filas de la tabla EXCEPTO las filas que coinciden con los identificadores en la matriz.Pasar una matriz de parámetros a un procedimiento almacenado

¿Cómo puedo hacerlo de la manera más simple?

+2

¿no es esto un duplicado de http: // stackoverflow.com/questions/114504/is-it-possible-to-send-a-collection-of-ids-as-a-ado-net-sql-parameter? –

+3

@John Saunders, hay muchas preguntas del servidor sql "pasar matriz como parámetro". Sin embargo, esto tiene un giro adicional, eliminar todas las filas, excepto la parte de parámetros pasados ​​en la pregunta. Como resultado, no creo que sea un duplicado. –

+0

John Saunders, no sé, hice una búsqueda pero no encontré lo que estaba buscando. ¿Hay algún problema con eso? – markiz

Respuesta

42

utilizar un procedimiento almacenado:

EDIT: Un complemento para la Lista serialize (o cualquier otra cosa):

List<string> testList = new List<int>(); 

testList.Add(1); 
testList.Add(2); 
testList.Add(3); 

XmlSerializer xs = new XmlSerializer(typeof(List<int>)); 
MemoryStream ms = new MemoryStream(); 
xs.Serialize(ms, testList); 

string resultXML = UTF8Encoding.UTF8.GetString(ms.ToArray()); 

El resultado (listo para usar con parámetros XML):

<?xml version="1.0"?> 
<ArrayOfInt xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> 
    <int>1</int> 
    <int>2</int> 
    <int>3</int> 
</ArrayOfInt> 

Post original:

Pasando XML como parámetro:

<ids> 
    <id>1</id> 
    <id>2</id> 
</ids> 

CREATE PROCEDURE [dbo].[DeleteAllData] 
(
    @XMLDoc XML 
) 
AS 
BEGIN 

DECLARE @handle INT 

EXEC sp_xml_preparedocument @handle OUTPUT, @XMLDoc 

DELETE FROM 
    YOURTABLE 
WHERE 
    YOUR_ID_COLUMN NOT IN (
     SELECT * FROM OPENXML (@handle, '/ids/id') WITH (id INT '.') 
    ) 
EXEC sp_xml_removedocument @handle 

+4

+1 Esta es una gran solución SI sus datos ya están en una estructura XML, no tanto cuando agrega la sobrecarga del lado del cliente para construir el XML. – RolandTumble

+1

Un RolandTumble mencionado, esto requerirá convertir mi matriz de datos iput (Lista) en XML, así que supongo que no es la mejor solución en mi caso. – markiz

+0

Puede usar Serialize para hacer eso ... Es más confiable usar XML que las cadenas divididas ... – Zanoni

20

esta es la mejor fuente:

http://www.sommarskog.se/arrays-in-sql.html

crear una función de división utilizando el enlace, y usarlo como:

DELETE YourTable 
    FROM YourTable       d 
    LEFT OUTER JOIN dbo.splitFunction(@Parameter) s ON d.ID=s.Value 
    WHERE s.Value IS NULL 

I prefer the number table approach

Esto es código en función del enlace anterior que debería hacerlo por usted ...

Antes de utilizar mi función, es necesario establecer una mesa "ayudante", sólo tiene que hacer esto una vez por base de datos:

CREATE TABLE Numbers 
(Number int NOT NULL, 
    CONSTRAINT PK_Numbers PRIMARY KEY CLUSTERED (Number ASC)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] 
) ON [PRIMARY] 
DECLARE @x int 
SET @x=0 
WHILE @x<8000 
BEGIN 
    SET @[email protected]+1 
    INSERT INTO Numbers VALUES (@x) 
END 

uso de esta función para dividir la cadena, lo que no hace bucle y es muy rápido:

CREATE FUNCTION [dbo].[FN_ListToTable] 
(
    @SplitOn    char(1)    --REQUIRED, the character to split the @List string on 
    ,@List     varchar(8000)  --REQUIRED, the list to split apart 
) 
RETURNS 
@ParsedList table 
(
    ListValue varchar(500) 
) 
AS 
BEGIN 

/** 
Takes the given @List string and splits it apart based on the given @SplitOn character. 
A table is returned, one row per split item, with a column name "ListValue". 
This function workes for fixed or variable lenght items. 
Empty and null items will not be included in the results set. 


Returns a table, one row per item in the list, with a column name "ListValue" 

EXAMPLE: 
---------- 
SELECT * FROM dbo.FN_ListToTable(',','1,12,123,1234,54321,6,A,*,|||,,,,B') 

    returns: 
     ListValue 
     ----------- 
     1 
     12 
     123 
     1234 
     54321 
     6 
     A 
     * 
     ||| 
     B 

     (10 row(s) affected) 

**/ 



---------------- 
--SINGLE QUERY-- --this will not return empty rows 
---------------- 
INSERT INTO @ParsedList 
     (ListValue) 
    SELECT 
     ListValue 
     FROM (SELECT 
        LTRIM(RTRIM(SUBSTRING(List2, number+1, CHARINDEX(@SplitOn, List2, number+1)-number - 1))) AS ListValue 
        FROM (
          SELECT @SplitOn + @List + @SplitOn AS List2 
         ) AS dt 
         INNER JOIN Numbers n ON n.Number < LEN(dt.List2) 
        WHERE SUBSTRING(List2, number, 1) = @SplitOn 
      ) dt2 
     WHERE ListValue IS NOT NULL AND ListValue!='' 



RETURN 

END --Function FN_ListToTable 

puede utilizar esta función como una tabla en una combinación:

SELECT 
    Col1, COl2, Col3... 
    FROM YourTable 
     INNER JOIN dbo.FN_ListToTable(',',@YourString) s ON YourTable.ID = s.ListValue 

aquí es su borrado:

DELETE YourTable 
    FROM YourTable        d 
    LEFT OUTER JOIN dbo.FN_ListToTable(',',@Parameter) s ON d.ID=s.ListValue 
    WHERE s.ListValue IS NULL 
+0

@KM: ¿Por qué el máximo de 8000 caracteres? ¿Podemos usar cadenas más largas que eso? – Pandincus

+0

puede hacer que el parámetro de entrada varchar (max), solo asegúrese de que su tabla de Números tenga suficientes filas. Solo uso 8000, porque nunca he dividido cadenas largas. Puedes ver esta otra respuesta mía: http://stackoverflow.com/questions/4227552/using-microsoft-query-and-odbc-to-sql-server-complicated-query/4227685#4227685 donde he actualizado la división función para usar una función de tabla y también mejoró la generación de la tabla Números. –

0

¿Qué pasa con el uso del tipo de datos XML en lugar de pasar una matriz. Encuentro que una mejor solución y funciona bien en SQL 2005

+1

¿Puede proporcionarnos el código + explicación? – markiz

+1

leyendo a través de xml es lento en comparación con las cadenas de división! – thijs

+1

¿Confía en utilizar cadenas divididas?Confíe primero que la velocidad ... – Zanoni

1

Consideraría pasar sus ID como una cadena XML, y luego podría triturar el XML en una tabla temporal para unir, o también podría consultar en contra de la XML directamente utilizando SP_XML_PREPAREDOCUMENT y OPENXML.

3

Usted podría utilizar una tabla temporal, que el procedimiento almacenado espera de existir. Esto funcionará en versiones anteriores de SQL Server, que no son compatibles con XML, etc.

CREATE TABLE #temp 
(INT myid) 
GO 
CREATE PROC myproc 
AS 
BEGIN 
    DELETE YourTable 
    FROM YourTable      
    LEFT OUTER JOIN #temp T ON T.myid=s.id 
    WHERE s.id IS NULL 
END 
12

Usted podría intentar esto:



DECLARE @List VARCHAR(MAX) 

SELECT @List = '1,2,3,4,5,6,7,8' 

EXEC(
'DELETE 
FROM TABLE 
WHERE ID NOT IN (' + @List + ')' 
) 

+5

... siempre que esté seguro de que el parámetro @List no se rellena desde la entrada del usuario – Joe

+0

La belleza es la simplicidad. – wwmbes

55

Si está utilizando SQL Server 2008 o mejor, se puede usar algo que se llama un parámetro con valores de tabla (TVP) en lugar de la serialización & deserializar los datos de la lista cada vez que quiera pasarlo a un procedimiento almacenado.

Vamos a empezar por la creación de un esquema sencillo para servir como nuestro patio de recreo:

CREATE DATABASE [TestbedDb] 
GO 


USE [TestbedDb] 
GO 

    /* First, setup the sample program's account & credentials*/ 
CREATE LOGIN [testbedUser] WITH PASSWORD=N'µ×? 
?S[°¿Q­¥½q?_Ĭ¼Ð)3õļ%dv', DEFAULT_DATABASE=[master], DEFAULT_LANGUAGE=[us_english], CHECK_EXPIRATION=OFF, CHECK_POLICY=ON 
GO 

CREATE USER [testbedUser] FOR LOGIN [testbedUser] WITH DEFAULT_SCHEMA=[dbo] 
GO 

EXEC sp_addrolemember N'db_owner', N'testbedUser' 
GO 


    /* Now setup the schema */ 
CREATE TABLE dbo.Table1 (t1Id INT NOT NULL PRIMARY KEY); 
GO 

INSERT INTO dbo.Table1 (t1Id) 
VALUES 
    (1), 
    (2), 
    (3), 
    (4), 
    (5), 
    (6), 
    (7), 
    (8), 
    (9), 
    (10); 
GO 

Con nuestro esquema y los datos de la muestra en su lugar, ahora estamos listos para crear nuestro procedimiento TVP almacenado:

CREATE TYPE T1Ids AS Table (
     t1Id INT 
); 
GO 


CREATE PROCEDURE dbo.FindMatchingRowsInTable1(@Table1Ids AS T1Ids READONLY) 
AS 
BEGIN 
     SET NOCOUNT ON; 

     SELECT Table1.t1Id FROM dbo.Table1 AS Table1 
     JOIN @Table1Ids AS paramTable1Ids ON Table1.t1Id = paramTable1Ids.t1Id; 
END 
GO 
Con

tanto nuestro esquema y API en su lugar, podemos llamar al procedimiento almacenado TVP de nuestro programa de este modo:

 // Curry the TVP data 
     DataTable t1Ids = new DataTable(); 
     t1Ids.Columns.Add("t1Id", 
          typeof(int)); 

     int[] listOfIdsToFind = new[] {1, 5, 9}; 
     foreach (int id in listOfIdsToFind) 
     { 
      t1Ids.Rows.Add(id); 
     } 
     // Prepare the connection details 
     SqlConnection testbedConnection = 
       new SqlConnection(
         @"Data Source=.\SQLExpress;Initial Catalog=TestbedDb;Persist Security Info=True;User ID=testbedUser;Password=letmein12;Connect Timeout=5"); 

     try 
     { 
      testbedConnection.Open(); 

      // Prepare a call to the stored procedure 
      SqlCommand findMatchingRowsInTable1 = new SqlCommand("dbo.FindMatchingRowsInTable1", 
                    testbedConnection); 
      findMatchingRowsInTable1.CommandType = CommandType.StoredProcedure; 

      // Curry up the TVP parameter 
      SqlParameter sqlParameter = new SqlParameter("Table1Ids", 
                  t1Ids); 
      findMatchingRowsInTable1.Parameters.Add(sqlParameter); 

      // Execute the stored procedure 
      SqlDataReader sqlDataReader = findMatchingRowsInTable1.ExecuteReader(); 

      while (sqlDataReader.Read()) 
      { 
       Console.WriteLine("Matching t1ID: {0}", 
            sqlDataReader[ "t1Id" ]); 
      } 
     } 
     catch (Exception e) 
     { 
      Console.WriteLine(e.ToString()); 
     } 
    /* Output: 
    * Matching t1ID: 1 
    * Matching t1ID: 5 
    * Matching t1ID: 9 
    */ 

Probablemente haya una manera menos dolorosa de hacer esto usando una API más abstracta, como Entity Framework. Sin embargo, no tengo tiempo para ver por mí mismo en este momento.

+0

Creo que esta es la mejor manera de hacer esto. Estoy sorprendido de que no hay votos para esto. Así que te di uno. –

+1

Me gusta este enfoque. Gracias por compartir. – Justin

+0

Para la reutilización, creo que este es de lejos el mejor enfoque. Considere la siguiente aplicación común del mundo real: Tengo una búsqueda, que genera un conjunto de resultados que incluye una clave principal única. Luego deseo pasar ese resultado a otra operación (por ejemplo, recuperar más información para una hoja de cálculo de Excel). Con este método, puedo recuperar fácilmente una lista de ID del conjunto de resultados inicial y pasar la lista a mi segundo procedimiento almacenado de fase que acepta un solo parámetro de "@IDs T1IDs READONLY". Por lo tanto, evito la carga pesada de la búsqueda inicial. – mrmillsy

2
declare @ids nvarchar(1000) 

set @ids = '100,2,3,4,5' --Parameter passed 

set @ids = ',' + @ids + ',' 

select * 
from  TableName 
where charindex(',' + CAST(Id as nvarchar(50)) + ',', @ids) > 0 
+0

Bingo Vitalivs! El primer premio es el código mínimo y produce exactamente lo que se requiere en el menor tiempo posible. Los espacios en blanco incrustados pueden causar problemas en "@ids". – wwmbes

0

I como éste, ya que es adecuado para ser aprobado como un XElement, que es adecuado para SqlCommand

(Lo siento, es VB.NET, pero se entiende la idea)

<Extension()> 
Public Function ToXml(Of T)(array As IEnumerable(Of T)) As XElement 
    Return XElement.Parse(
      String.Format("<doc>{0}</doc>", String.Join("", array.Select(Function(s) String.Concat("<d>", s.ToString(), "</d>")))), LoadOptions.None) 
End Function 

Este es el proceso almacenado sql, abreviado, no completo.

CREAR PROCEDIMIENTO [dbo]. [Myproc] (XML @blah)
AS ... DONDE SomeID IN (SELECT doc.t.value (' '' int') de @ netwerkids.nodes (N '/ doc/d') como Doc (t))

Cuestiones relacionadas