I'm developing this Stored Procedure in SQL Server 2012:
CREATE PROCEDURE [dbo].[UploadCodes]
@param1 nvarchar(20),
@param2 nvarchar(20),
@param3 nvarchar(20),
@param4 nvarchar(20),
@param5 nvarchar(20),
@newCodes as dbo.CodeList READONLY
AS
declare @code nvarchar(20),
@codeLevel tinyint,
@headerId int,
@productId int;
set nocount on;
Begin transaction
-- Insert china header file.
Insert into CODES_HEADER
values (@param1, @param2, @param3, @param4, @param5);
-- If an error, end here.
If (@@ERROR != 0)
Begin
rollback transaction
return -1 -- Database error
End
-- Get header id from latest insert.
set @headerId = (select SCOPE_IDENTITY());
-- Get product's id.
set @productId = (select Id from PRODUCTS where PRODUCT_CODE = @param3)
-- If this product doesn't exist on database, insert it.
if (@productId is null)
begin
Insert into PRODUCTS values (@param3);
-- If an error, end here.
If (@@ERROR != 0)
Begin
rollback transaction
return -1 -- Database error
End
set @productId = (select SCOPE_IDENTITY());
end
set @codeLevel = (select CAST(@param1 as tinyint));
Create table #tempCodes (
Code nvarchar(20))
insert into #tempCodes (Code) select CODE from @newCodes;
set rowcount 1
select @code = Code from #tempCodes
-- Loop all child codes to check if they have a parent.
while @@rowcount <> 0
begin
set rowcount 0
select * from #tempCodes where Code = @code
delete #tempCodes where Code = @code
insert into EXTERNAL_CODES(CODE, CODE_LEVEL, CODES_HEADER_ID, PRODUCT_ID)
values (@code, @codeLevel, @headerId, @productId);
-- If an error, end here.
If (@@ERROR != 0)
Begin
rollback transaction
return -1 -- Database error
End
-- Get next code.
set rowcount 1
select @code = Code from #tempCodes
end
set rowcount 0
Commit transaction
return 0
This is the definition of dbo.CodeList type:
CREATE TYPE [dbo].[CodeList]
AS TABLE
(
CODE nvarchar(20)
);
My problem is with its loop, it gets too long when there are a lot of codes.
Is there another way to run it faster?
On C# I use SqlBulkCopy but I don't know if there is something similar on SQL. I have found Bulk Insert but it uses a file.
1 Answer 1
You should be able to rewrite the entire loop into a single insert statement.
This:
set rowcount 1
select @code = Code from #tempCodes
-- Loop all child codes to check if they have a parent.
while @@rowcount <> 0
begin
set rowcount 0
select * from #tempCodes where Code = @code
delete #tempCodes where Code = @code
insert into EXTERNAL_CODES(CODE, CODE_LEVEL, CODES_HEADER_ID, PRODUCT_ID)
values (@code, @codeLevel, @headerId, @productId);
-- If an error, end here.
If (@@ERROR != 0)
Begin
rollback transaction
return -1 -- Database error
End
-- Get next code.
set rowcount 1
select @code = Code from #tempCodes
end
set rowcount 0
Does this:
- Pick out a single code from #tempCodes into @code
- Delete that code from #tempCodes
- Insert one row into EXTERNAL_CODES, using that @code + other variables
- Grab the next and go back to step #2.
You can rewrite that entire loop into this query (basically everything I pasted above):
INSERT INTO EXTERNAL_CODES (CODE, CODE_LEVEL, CODES_HEADER_ID, PRODUCT_ID)
SELECT Code, @codeLevel, @headerId, @productID FROM #tempCodes
If you also need to clear out #tempCodes (which I doubt), you would also issue this statement:
DELETE #tempCodes
If you also need to rollback other changes if the above fails you would add that single if-statement with a rollback in here as well
5 Comments
#tempCodes and I can use newCodes instead because I don't need to delete any code from it. This should make it faster.Update instead of an Insert. I have asked this question, stackoverflow.com/questions/30773219/update-topn-from-select, because I need to update N rows, and this value is also in on a table parameter.
select * from #tempCodes where Code = @codefor each iteration, is this necessary?