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.
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
Related
I am using Entity Framework 6 and Microsoft SQL Server 2012.
Here is my stored procedure:
ALTER PROCEDURE SPDeleteRegion
#siteId int,
#regionId int
AS
BEGIN
SET NOCOUNT ON;
DECLARE #isDeleted BIT
IF EXISTS (SELECT 1 FROM SiteObjects WHERE SiteRegionId = #regionId)
BEGIN
SET #isDeleted = 0 ; --not deleted
RETURN #isDeleted;
END
ELSE
BEGIN
--do what needs to be done if not
DELETE FROM SiteRegions
WHERE Id = #regionId;
SET #isDeleted = 1; -- deleted
RETURN #isDeleted;
END
END
Here how I call the stored procedure in C#:
var t = _context.Database.SqlQuery<bool>("SPDeleteRegion #siteId, #regionId",
new SqlParameter("#siteId", siteId),
new SqlParameter("#regionId", regionId));
On the line of code above I get this exception:
The data reader has more than one field. Multiple fields are not valid for EDM primitive or enumeration types.
Any idea why I get the excewption and how to fix it?
Your procedure doesn't selecting anything. Change it like this:
ALTER PROCEDURE SPDeleteRegion
-- Add the parameters for the stored procedure here
#siteId int,
#regionId int
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
DECLARE
#isDeleted BIT
IF EXISTS (SELECT 1 FROM SiteObjects WHERE SiteRegionId = #regionId)
BEGIN
SET #isDeleted = 0 ; --not deleted
SELECT #isDeleted [IsDeleted]; --HERE
END
ELSE
BEGIN
--do what needs to be done if not
DELETE FROM SiteRegions WHERE Id = #regionId;
SET #isDeleted = 1;--deleted
SELECT #isDeleted [IsDeleted]; -- AND HERE
END
END
I need to run three Insert queries in SQL Server 2008 via stored procedure and expected three OUTPUT values to read in C#. In following Stored Procedure, my first query only runs and remain I am getting Null error
Cannot insert the value NULL into column
'AssessmentElectronicSignatureID'
USE [myDB]
GO
/****** Object: StoredProcedure [dbo]. [p_assessment_dfn_statementAnswer_insert] Script Date: 10/05/2015 13:24:22 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE [dbo].[p_assessment_dfn_statementAnswer_insert]
#StatementID AS bigint,
#StaffID AS int,
#AssessmentID As bigint,
#StatementText AS nvarchar(MAX),
#StatementDate AS Date,
#StatementAnswerID AS bigint OUTPUT,
--
#SignatureCheck AS bit,
#SignatureDate AS Date,
#ElectronicSignatureID AS bigint OUTPUT,
--
#AssessmentElectronicSignatureID AS bigint OUTPUT
AS
SET NOCOUNT ON
SET XACT_ABORT ON
-- local variables
DECLARE #l_object AS SYSNAME = OBJECT_NAME(##PROCID)
,#l_error_msg AS NVARCHAR(2000)
BEGIN TRY
BEGIN TRAN
INSERT INTO [adb_TestDb].[dbo].[Assessment_Statement_Answer]
([StatementID],[StaffID],[AssessmentID],[StatementText],[Date])
VALUES (#StatementID, #StaffID, #AssessmentID, #StatementText, #StatementDate)
SELECT #StatementAnswerID = SCOPE_IDENTITY();
IF(#StatementAnswerID>0)
BEGIN
INSERT INTO [adb_TestDb].[dbo].[Assessment_ElectronicSignature]([AssessmentID],[ElectronicSignatureCheck],[SignatureDateAndTime])
VALUES (#AssessmentID, #SignatureCheck,#SignatureDate)
SELECT #ElectronicSignatureID = SCOPE_IDENTITY();
END
IF(#ElectronicSignatureID>0)
BEGIN
INSERT INTO [adb_TestDb].[dbo].[AssessorSignature]([AssessmentElectronicSignatureID],[StatementAnswerID],[AssessorID])
VALUES(#ElectronicSignatureID, #StatementAnswerID, #AssessmentID)
SELECT #AssessmentElectronicSignatureID = SCOPE_IDENTITY();
END
COMMIT TRAN
--RETURN SCOPE_IDENTITY();
END TRY
BEGIN CATCH
-- rollback any open/uncomitted transactions
IF XACT_STATE() IN ( -1, 1) ROLLBACK TRANSACTION
-- return an error containing the object, error number and error description
SELECT #l_error_msg = 'Error number : ' + CAST(ERROR_NUMBER()AS VARCHAR) + '. ' + ERROR_MESSAGE()
RAISERROR (#l_error_msg,16,1)
END CATCH
There is a known bug with SCOPE_IDENTITY(); Occurs occasionally when triggers are used. Google for the occurances if you want to dig deep. Or...
Try changing things like
SELECT #StatementAnswerID = SCOPE_IDENTITY();
to
SELECT #StatementAnswerID = ##IDENTITY;
Or
SET #StatementAnswerID = ##IDENTITY;
and other similar assignments. I think the problem here is causing because of being the #StatementAnswerID as Null.
tblParent
pid(int) name deleted(bit)
1 abc 0
2 def 0
tblChild
cid(int) name pid(ForeignKey)
1 aaa 1
2 bbb 1
When a record from tblParent is being deleted, it should check for any child records. If yes, rollback & return 0. If no, then update the deleted column to '1' and return 1.
Basically, doing a soft delete
The SP works fine. All I need is to know the status as 0 or 1 based upon the action that took place. How should it be done. I would call this store procedure from c#, linq to entities to get the status.
something like:
public int somefuntion() //returning a string is also fine..
{
return MYDB.SoftDelete(parameters.....);
}
EDIT:
ALTER PROCEDURE SoftDelete
(
#TableName nvarchar(50), #ColName nvarchar(50),
#Id nvarchar(50)
)
AS
BEGIN
DECLARE #qry nvarchar(500)
SELECT #qry = 'begin transaction
delete '+#tablename+' where '+#colname+'='+#id+'
if(##Error <> 0)
Begin
--select 0
End
else
Begin
rollback transaction
update '+#tablename+' set deleted = 1 where '+#colname+' = '+#id+'
--select = 1
end'
EXECUTE sp_executesql #qry
END
Try this:
Declare #status nvarchar(50),#tablename nvarchar(50), #colname nvarchar(50),
#id nvarchar(50), #qry nvarchar(500)
set #tablename = 'person'
set #colname = 'id'
set #id = '15'
begin try
begin transaction
set #qry='delete '+#tablename+' where '+#colname+'=#id'
execute sp_executesql #qry,N'#id nvarchar(50)',#id=#id
rollback
--NO FK violation.So begin another transaction and soft delete
begin transaction
set #qry='update '+#tablename +' set deleted=1 where '+#colname+'=#id'
execute sp_executesql #qry,N'#id nvarchar(50)',#id=#id
commit
select 1
end try
begin catch
print(Error_Message())
--FK violation.Do nothing.Return 0
select 0
end catch
In your script the #status variable is not accessible inside your sql statement you build because "execute sp_executesql #qry" will be executing in different space. Instead use select n" in replace to "set #status = 0" and "set #status = 1"
Try this
Declare #tablename nvarchar(50), #colname nvarchar(50),
#id nvarchar(50), #qry nvarchar(500)
set #tablename = 'tblParent'
set #colname = 'pid'
set #id = '1'
select #qry = 'begin transaction
delete '+#tablename+' where '+#colname+'='+#id+'
if(##Error <> 0)
Begin
select 0
End
else
Begin
rollback transaction
update '+#tablename+' set deleted = 1 where '+#colname+' = '+#id+'
select 1
end
execute sp_executesql #qry
I am getting Dead Lock exceptions in my C# code when I call my stored procedure to update/insert into my Configuration table.
The SP_Update_Configuration stored procedure will either insert a new record or update an existing record.
The Triggers are setup to keep a history of previous records in a history table. If the Configuration table has an update or an insert, then it should add that record to the Configuration_History table.
I believe the triggers are causing the deadlock? I did not have any problems previous to adding triggers.... Any Ideas?
I am using SQL Server 2012 Express.
Here is an example of my SQL:
CREATE PROCEDURE SP_Update_Configuration
(
--Input variables
)
AS
BEGIN TRANSACTION
DECLARE #RetCode INT
DECLARE #RowCnt INT
--Standard Update Logic
SELECT #RowCnt = ##ROWCOUNT
IF ##ERROR <> 0
BEGIN
ROLLBACK TRAN
SET #RetCode = 5
RETURN #RetCode
END
IF #RowCnt = 0
BEGIN
--Standard Insert Logic
END
IF ##ERROR <> 0
BEGIN
ROLLBACK TRAN
SET #RetCode = 5
RETURN #RetCode
END
COMMIT TRANSACTION
GO
create trigger dbo.Configuration_Log_Insert
on dbo.Configuration
for insert
as
set nocount on
insert into Configuration_History
select *
from Configuration
go
exec sp_settriggerorder #triggername = 'Configuration_Log_Insert', #order = 'last', #stmttype = 'insert'
create trigger dbo.Configuration_Log_Update
on dbo.Configuration
for update
as
set nocount on
insert into Configuration_History
select *
from Configuration
go
exec sp_settriggerorder #triggername = 'Configuration_Log_Update', #order = 'last', #stmttype = 'update'
SELECT #RowCnt = ##ROWCOUNT
IF ##ERROR <> 0
here you have trouble, because ##ERROR is error code of
SELECT #RowCnt = ##ROWCOUNT
You can do this as:
SELECT #RowCnt = ##ROWCOUNT, #error = ##ERROR
IF #error <> 0
In triggers you have
insert into Configuration_History
select *
from Configuration
but is must be
insert into Configuration_History
select *
from inserted
Say we have stored procedure(s) performing simple operations like this:
CREATE PROCEDURE [dbo].[AddNewAuthorReturnID]
(
#Author_Name VARCHAR(MAX),
#Author_ID int OUTPUT
)
AS
SET NOCOUNT OFF;
BEGIN
INSERT INTO AUTHORS (#Author_Name)
VALUES (#Author_Name)
SET #Author_ID = SCOPE_IDENTITY()
SELECT #Author_ID
END
In the above procedure, the returned id is an indication of successful operation.
Consider the equivalent with DELETE.
CREATE PROCEDURE [dbo].[DeleteAuthor]
(
#Author_ID int
)
AS
SET NOCOUNT OFF;
BEGIN
DELETE FROM AUTHORS
WHERE
(Author_ID = #Author_ID)
END
How can we know the operation was
successful and the AUTHORS record was
succesfully removed if we use the above
procedure ?
With an update operation?
You could select ##rowcount
It will show you the rows affected.
e.g
CREATE PROCEDURE [dbo].[DeleteAuthor]
(
#Author_ID int
)
AS
SET NOCOUNT OFF;
BEGIN
DELETE FROM AUTHORS
WHERE
(Author_ID = #Author_ID)
SELECT ##ROWCOUNT
END
This can be applied to update too.
CREATE PROCEDURE [dbo].[UpdateAuthor]
(
#Author_ID int
)
AS
SET NOCOUNT OFF;
BEGIN
UPDATE AUTHORS
SET AuthorName = 'John'
WHERE
(Author_ID = #Author_ID)
SELECT ##ROWCOUNT
END
Alternatively you could use ##Error and raise an error id ##rowcount > 1 (if you only wanted to update one row).
e.g
CREATE PROCEDURE [dbo].[DeleteAuthor]
(
#Author_ID int
)
AS
SET NOCOUNT OFF;
BEGIN
DELETE FROM AUTHORS
WHERE
(Author_ID = #Author_ID)
IF ##ROWCOUNT <>1
BEGIN
RAISERROR ('An error occured',10,1)
RETURN -1
END
END
As Giorgi says this will be returned as a returncode.
You can to return ##ROWCOUNT to determine if your last statement affected any record.
You can return value from stored procedure using return statement. The ##ERROR variable is equal to zero if there was no error.
##ERROR
CREATE PROCEDURE [dbo].[DeleteAuthor]
(
#Author_ID int
)
AS
SET NOCOUNT OFF;
BEGIN
DELETE FROM AUTHORS
WHERE
(Author_ID = #Author_ID)
Return ##ERROR
END