wake-up-neo.com

Übergeben eines Parameters mit einer gespeicherten Prozedur

Ich muss ein Array von "IDs" an eine gespeicherte Prozedur übergeben, um alle Zeilen außer der Zeile zu löschen, die mit der ID im Array übereinstimmen.

Wie mache ich das auf einfachste Weise?

73
markiz

Verwenden Sie eine gespeicherte Prozedur:

EDIT: Eine Ergänzung zur Serialisierungsliste (oder etwas anderes)

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

Das Ergebnis (bereit zur Verwendung mit XML-Parametern):

<?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>

ORIGINAL POST:

XML als Parameter übergeben:

<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

42
Zanoni

Wenn Sie SQL Server 2008 oder besser verwenden, können Sie einen so genannten Table-Value-Parameter (TVP) verwenden, anstatt Ihre Listendaten jedes Mal zu serialisieren und zu deserialisieren, wenn Sie sie an eine gespeicherte Prozedur übergeben möchten.

Beginnen wir mit der Erstellung eines einfachen Schemas, das als Spielplatz dient:

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

Mit unseren Schema- und Beispieldaten können wir jetzt unsere gespeicherte Prozedur für TVP erstellen:

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

Wenn sowohl unser Schema als auch unsere API vorhanden sind, können wir die gespeicherte Prozedur TVP aus unserem Programm wie folgt aufrufen: 

        // 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
   */

Es ist wahrscheinlich eine weniger schmerzhafte Methode, dies mit einer abstrakteren API wie Entity Framework zu tun. Ich habe jedoch zu diesem Zeitpunkt keine Zeit, mich selbst zu sehen.

56
rmiesen

dies ist die beste Quelle:

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

erstellen Sie eine Split-Funktion mithilfe des Links und verwenden Sie sie wie folgt:

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

Ich bevorzuge den Ansatz der Nummerntabelle

Dieser Code basiert auf dem obigen Link, der dies für Sie tun sollte ...

Bevor Sie meine Funktion verwenden, müssen Sie eine "Helper" -Tabelle einrichten. Sie müssen dies nur einmal pro Datenbank tun:

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

verwenden Sie diese Funktion, um Ihre Zeichenfolge zu teilen, die keine Schleife durchläuft und sehr schnell ist:

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

sie können diese Funktion als Tabelle in einem Join verwenden:

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

hier ist deine Löschung:

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

Sie könnten dies versuchen:



DECLARE @List VARCHAR(MAX)

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

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

12
Adriaan Stander

Sie können eine temporäre Tabelle verwenden, die von der gespeicherten Prozedur erwartet wird. Dies funktioniert mit älteren Versionen von SQL Server, die XML usw. nicht unterstützen.

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
3
Simon
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
2
Vitalivs

Ich würde in Betracht ziehen, Ihre IDs als XML-String zu übergeben, und dann könnten Sie das XML in eine temporäre Tabelle einbinden, mit der Sie sich verbinden möchten, oder Sie können auch direkt mit XML abfragen, indem Sie SP_XML_PREPAREDOCUMENT und OPENXML .

1
Scott Ivey

Wie wäre es mit dem XML-Datentyp, anstatt ein Array zu übergeben. Ich finde das eine bessere Lösung und funktioniert gut in SQL 2005

0
Cody C

Ich mag dieses, weil es geeignet ist, als XElement übergeben zu werden, was für SqlCommand geeignet ist 

(Sorry es ist VB.NET aber du bekommst die Idee)

<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

Dies ist die gespeicherte SQL-Prozedur, verkürzt, nicht vollständig!

CREATE PROCEDURE [dbo]. [Myproc] (@blah xml)
WIE ... WHERE SomeID IN (SELECT doc.t.value ('.', 'Int') von @ netwerkids.nodes (N '/ doc/d') als doc (t))

0
Egbert Nierop

In SQL Server 2016 können Sie das Array mit [] umschließen und als JSON übergeben. Siehe http://blogs.msdn.com/b/sqlserverstorageengine/archive/2015/09/08/passing-arrays-to-t- sql-Procedures-as-json.aspx

0
Jovan MSFT