views:

1557

answers:

6

I need to pass an array of "id's" to SP, delete all rows from the table EXCEPT the rows that match id's in the array.

How can I do it in a most simple way?

+6  A: 

this is the best source:

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

create a split function using the link, and use it like:

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

This is code based on the above link that should do it for you...

Before you use my function, you need to set up a "helper" table, you only need to do this one time per database:

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 @x=@x+1
    INSERT INTO Numbers VALUES (@x)
END

use this function to split your string, which does not loop and is very fast:

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

you can use this function as a table in a join:

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

here is your delete:

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

What about using the XML data type instead of passing an array. I find that a better solution and works well in SQL 2005

Cody C
Can you please provide code + explanation?
markiz
reading through xml is slow compared to splitting strings!
thijs
You trust to use splitted strings? Trust first than speed...
Zanoni
@Zanoni, why not trust split strings?
KM
A: 

I'd consider passing your IDs as an XML string, and then you could shred the XML into a temp table to join against, or you could also query against the XML directly using SP_XML_PREPAREDOCUMENT and OPENXML.

Scott Ivey
+4  A: 

Use a stored procedure:

EDIT: A complement for serialize List (or anything else):

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

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

The result (ready to use with XML parameter):

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


ORIGINAL POST:

Passing XML as parameter:

<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', 2) WITH (id INT) 
    )
EXEC sp_xml_removedocument @handle


Zanoni
+1 This is a great solution IF your data is already in an XML structure--not so hot when you add the client-side overhead of building the XML.
RolandTumble
A RolandTumble mentioned, this will require to convert my iput data array (List) into XML, so i guess it not the best solution in my case.
markiz
You could use Serialize to do that... It's more reliable use XML than split strings...
Zanoni
Can you please explain how can I serialize List<strings> or arraylist? I don't want to create a wrapper object just for xml serialization.
markiz
Serialization also wrapes xml with it's standard header like:xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"is it a problem for sp_xml_preparedocument?
markiz
@markiz: I complement my post with the serializes...
Zanoni
If instead of serialization, I construct a concatenated string "look a like" xml parameter: string s ="<ids><id>1</id><id>2</id></ids>", will it be more efficient? (I don't need to pass a lot of values)
markiz
Just to add to the update here. you need to use xpath to get the list of ints out: SELECT * FROM OPENXML( @xmlHandle, '/ArrayOfInt/int') WITH (id INT '.')
cvista
+1  A: 

You could use a temp table which the stored procedure expects to exist. This will work on older versions of SQL Server, which do not support 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

Simon
+1  A: 

You could try this:



DECLARE @List VARCHAR(MAX)

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

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

astander