Reputation: 73
I want to delete rows according to the row number passed in from a WinForm. Of course, the output of the ROW_NUMBER()
calculation will always update when you delete a row, so row number will change too, therefore I want to do this in one shot rather than a loop.
I have this code:
CREATE PROCEDURE Delete_Record (@RowNum INT)
AS
;WITH REC_ROW AS
(
SELECT *, ROW_NUMBER() OVER (ORDER BY [Record Number]) AS RN
FROM User_Data
)
DELETE
FROM REC_ROW
WHERE RN IN (@RowNum)
And when I type:
exec Delete_Record @RowNum = '1,2'
It produces the error:
Error converting data type varchar to int.
If I change @RowNum INT
to @RowNum varchar(max)
, it will just produce the error message:
Error converting data type varchar to bigint.
When I hard-code the values:
;WITH REC_ROW AS
(
SELECT *, ROW_NUMBER() OVER (ORDER BY [Record Number]) AS RN
FROM User_Data
)
DELETE
FROM REC_ROW
WHERE RN IN (1,2)
It will successfully delete the row 1 and 2. The question is how can I integrate this in a stored procedure and input the '1,2'
to pass to the IN
clause?
Upvotes: 3
Views: 3167
Reputation: 280252
If you are going to be passing more complex strings than 1,2
you may want to consider using a table-valued parameter (TVP).
CREATE TYPE dbo.Integers AS TABLE
(
RowNumber INT PRIMARY KEY
);
Now you can create your stored procedure and pass in a DataTable or other collection from your application without ever bothering with constructing comma-separated strings or trying to break them apart.
CREATE PROCEDURE dbo.Delete_Record
@RowNums dbo.Integers READONLY
AS
BEGIN
SET NOCOUNT ON;
;WITH REC_ROW AS (...your CTE unchanged here...)
DELETE REC_ROW
FROM REC_ROW INNER JOIN @RowNums AS r
ON r.RowNumber = REC_ROW.RN;
END
GO
If you really want to use a split function:
CREATE FUNCTION dbo.SplitInts
(
@List VARCHAR(MAX),
@Delimiter VARCHAR(255)
)
RETURNS TABLE WITH SCHEMABINDING
AS
RETURN
(
SELECT Item = y.i.value('(./text())[1]', 'int')
FROM ( SELECT x = CONVERT(XML, '<i>'
+ REPLACE(@List, @Delimiter, '</i><i>')
+ '</i>').query('.')) AS a
CROSS APPLY x.nodes('i') AS y(i));
GO
Now your stored procedure:
CREATE PROCEDURE dbo.Delete_Record -- always use a schema prefix!
@RowNums VARCHAR(MAX)
AS
BEGIN
SET NOCOUNT ON;
;WITH REC_ROW AS
(
SELECT *, ROW_NUMBER() OVER (ORDER BY [Record Number]) AS RN
FROM dbo.User_Data
)
DELETE REC_ROW
FROM REC_ROW
INNER JOIN dbo.SplitInts(@RowNums, ',') AS r
ON r.Item = REC_ROW.RN;
END
GO
But I guarantee you the TVP will perform better.
Upvotes: 2
Reputation: 1269513
This expression does not do what you think it does:
WHERE RN IN (@RowNum)
It is looking for cases where @RowNum
is '1,2'
. And this causes the problem, because '1,2'
cannot be converted to an integer. You have declared the parameter an integer when you define the procedure, but then call it with a string.
You can do what you want with:
WHERE ','+@RowNum+',' like '%,'+RN+',%'
The extra commas ensure that "1" doesn't match "10".
EDIT:
There are essentially three approaches (that I can think of) for handling a string of comma delimited integers as real integers (which is better because the query will use integers).
One is to use the split()
function, which returns a table of the elements of a delimited string. You can google for various implementations of it.
The second is to use dynamic SQL. This is fine for queries that do not need to be instantaneous. It can incur additional overhead in compiling the query, which may be noticeable in transactional systems.
The third is to use recursive CTEs to parse the string. I would probably use this method for a stored procedure.
Upvotes: 0