Why this code would fail or not fail? - sql

What is the best way to write it ? Since I have written it but I suspect it's not a good way.
Scenario: I have written a store procedure which simply INSERTS a record into TABLE and then picks last inserted Primary key and return it to front end application i.e.
#parameter1 int,
#Parameter2 smallint,
#Error varchar(MAX) output,
#OutPutParamter int Output
Begin
Begin Try
insert into table1
values (#parameter1, #parameter2)
Set #OutPutParamter= Scope_Identity()
End Try
Begin Catch
set #Error= Select Error_Message()
set #OutPutParameter= 0
End Catch
End
Now question is that how #OutPutParameter would be treated ? What hazards in returning a value could be ?

The query you write is correct.
Scope_Identity() will get the id which the record insert into the table
#OutPutParamter is a output type variable, after run the SP, you can find the id from #OutPutParamter.
You also can use a local variable to get the id and use select to return the value

Related

'INSERT' stored procedure with validation

I'm trying to create a stored procedure where I'm inserting a new office into the OFFICE table I have in my database.
I want to first check whether the office I'm trying to create already exists or not.
Here is some code from where I've gotten so far, but I'm not able to quite get it right. I would greatly appreciate some input.
CREATE PROCEDURE stored_proc_new_office
AS
BEGIN
DECLARE #office_id int
SELECT #office_id = (SELECT office_id FROM inserted)
IF NOT EXISTS (SELECT 1 FROM OFFICE WHERE office_id = #office_id)
BEGIN
ROLLBACK TRANSACTION
PRINT 'Office already exists.'
END
END
Here is a bare bones example of how you can use a stored procedure to insert a new record with a check to ensure it doesn't already exist.
create procedure dbo.AddNewOffice
(
#Name nvarchar(128)
-- ... add parameters for other office details
, #NewId int out
)
as
begin
set nocount on;
insert into dbo.Office([Name]) -- ... add additional columns
select #Name -- ... add additional parameters to match the columns above
where not exists (select 1 from dbo.Office where [Name] = #Name); -- ... add any additional conditions for testing for uniqueness
-- If nothing inserted return an error code for the calling app to use to display something meaningful to the user
if ##rowcount = 0 return 99;
-- return the new id to the calling app.
set #NewId = scope_identity();
return 0;
end

T-SQL TRIGGER return 1 more "Rows Affected" Than expected when INSERT data

I have a table with a secondary key. Before inserting, I want to check if this secondary key exists in another table as primary key.
To do this I've create a trigger in T-SQL on this table. Here it is:
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER TRIGGER [dbo].[trg_tb_repair_order_before]
ON [dbo].[tb_repair_order]
INSTEAD OF INSERT
AS
BEGIN
DECLARE #or_code NVARCHAR(50)
DECLARE #or_designation NVARCHAR(300)
DECLARE #or_comment NVARCHAR(3000)
DECLARE #code_customer NVARCHAR(50)
DECLARE cur_tb_repair_order_before CURSOR FOR
SELECT or_code, or_designation, or_comment, code_customer
FROM inserted
OPEN cur_tb_repair_order_before
FETCH NEXT FROM cur_tb_repair_order_before INTO #or_code, #or_designation, #or_comment, #code_customer
WHILE ##FETCH_STATUS = 0
BEGIN
IF ((SELECT Count(*) FROM tb_customer WHERE code = #code_customer) > 0)
BEGIN
IF ((SELECT COUNT(*) FROM inserted) > 0)
BEGIN
INSERT INTO tb_repair_order (or_code, or_designation, or_comment, code_customer)
VALUES (#or_code, #or_designation, #or_comment, #code_customer)
END
END
FETCH NEXT FROM cur_tb_repair_order_before INTO #or_code, #or_designation, #or_comment, #code_customer
END
CLOSE cur_tb_repair_order_before
DEALLOCATE cur_tb_repair_order_before
END
Everything is good, this trigger works perfectly.
The problem is the number of rows inserted
Here is result for wrong secondary key
As we can see, SQL returns "1 rows affected" but nothing is inserted into the table ...
Here is result for good secondary key
And here SQL returns "2 rows affected".
If someone can explain how I can return the correct result because in my C# code the ExecuteNonQuery function returns not the expected result and it's bad practice to continue with this.
Thanks in advance

Why would a stored procedure return a 0 and the selected id?

I have a stored procedure like this:
CREATE PROCEDURE [dbo].[create_myNewId]
(#parentId BIGINT)
AS
BEGIN
SET NOCOUNT ON;
INSERT INTO [Mapping] (ParentId)
VALUES (#parentId)
SELECT SCOPE_IDENTITY();
END
This, when run on its own, returns the new id that has been assigned to the new row that's inserted with the parent id. However, when I do something like this:
DECLARE #NewId int
EXEC #NewId = create_myNewId #parentId = 33333
SELECT #NewId
When running this, the output window shows the result of the stored procedure, which returns an Id but #NewId always is 0. I fixed this by changing the stored procedure to use RETURN SCOPE_IDENTITY() but I was wondering why SELECT didn't work in this case?
I have my suspicions that it's something around the 0 being the success status being returned first from the stored procedure rather than the result, but was curious why this doesn't then happen when called directly from the client.
No! Write the procedure the right way:
CREATE PROCEDURE [dbo].[create_myNewId] (
#parentId bigint,
#outId bigint OUTPUT
) AS
BEGIN
SET NOCOUNT ON;
DECLARE #ids TABLE (id bigint);
INSERT INTO [Mapping](ParentId)
OUTPUT id INTO #ids
VALUES (#parentId);
SELECT #outId = id
FROM #ids;
END;
Then call this as:
DECLARE #NewId int;
EXEC create_myNewId #parentId = 33333, #NewId OUTPUT;
SELECT #NewId;
The OUTPUT clause is the recommend way to get results from a data-modification clause. The older methods using the *_IDENTITY() functions should be obsoleted.
Stored procedures do return values. These are integers that are designed to return status information. Other information should be returned via OUTPUT parameters.
Microsoft's design intent for stored procedures is that they always return an int to describe how successful the process undertaken by the procedure was. It's not intended to return a result data, and you're free to define the bits you want to return to describe succes, partial success etc. You could abuse it to return an integer result data (count query for example) if you wanted, but it's not the design intention
Executing a select query within a stored procedure creates a result set you can read on your client if the sproc is the kind that is intended to return data
My suggestion is to use an OUTPUT parameter. Not only will it be 'easier' to use when calling the stored procedure, it will also be clearer to the person calling the stored procedure.
CREATE PROCEDURE [dbo].[create_myNewId]
(#parentId BIGINT,
#myNewId BIGINT OUTPUT)
AS
BEGIN
SET NOCOUNT ON;
INSERT INTO [Mapping] ([ParentId])
VALUES (#parentId);
SET #myNewId = SCOPE_IDENTITY();
END;
GO
You would then call your stored procedure like this:
DECLARE #myNewId BIGINT;
EXECUTE [dbo].[create_myNewId] #parentId = 0, -- bigint
#myNewId = #myNewId OUTPUT; -- bigint
SELECT [This was just inserted] = #myNewId;
For anyone who has 0 as return value from a stored procedure, check if the stored procedure executes from the right database and only one procedure exists within the given context. Output parameters wouldn't be of any use if you ever plan to access the DB with ORM and the procedure returns an object's property.

Multiple rows are getting inserted into a table (which is not desired) as part of a stored procedure

Update: This still remain a mystery. Checked the calling code and we did not find anything that would make the SP run in a loop.
For now we have split the SP into two which seems to have arrested the issue although not able to reason how that has helped out.
Database: MS SQL Server.
I have a SP which performs few operations - i.e inserts a row into 3 tables based on certain status as part of that SP being called.
It is getting called from our web application based on a user action.
We have cases, few times a day where the same row gets inserted multiple times (sometime more than 50+) with the same values in each row except that if you look at the datetime when the row was inserted there is a difference of few milliseconds. So it is unlikely that the user is initiating that action.
This SP is not running in a Transaction or with any locks however it is getting called probably concurrently multiple times as we have many concurrent users on the web application invoking this action.
My question is what is causing the same row to insert so many times? If concurrent execution of SP was an issue where we are updating same row then it is understood one may overwrite the other. However in this case each user calls in the SP with different parameters.
I have put the said operation in a Transaction to monitor the behavior however was looking to find out what exactly causes these kind of multiple inserts with same value just a few milliseconds apart?
USE [ABC]
GO
/****** Object: StoredProcedure [dbo].[AddProcessAdmittedDocUploadScrutinyWithLog] ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE [dbo].[AddProcessAdmittedDocUploadScrutinyWithLog]
(
--Insert using bulk
#stdfrm_id int,
#course_id int,
#stdfrm_scrt_apprvby int,
#stdfrm_scrt_apprvcomment varchar(max),
#sRemainingDocs varchar(max),
#DTProcessAdmittedDocUploadScrutiny AS dbo.MyDTProcessAdmittedDocUploadScrutiny READONLY
)
AS
BEGIN
DECLARE #result char
SET #result='N'
--New
declare #AuditCount int=0;
select #AuditCount=count(scrtaudit_id) from tbl_ProcessAdmittedScrutinyAuditLog
where stdfrm_id=#stdfrm_id and stdfrm_scrt_apprvby=#stdfrm_scrt_apprvby
and stdfrm_scrt_apprvcomment=#stdfrm_scrt_apprvcomment and convert(date,stdfrm_scrt_apprvon,103)=convert(date,getdate(),103)
--Checked extra conditon to avoid repeatation
if(#AuditCount=0)
BEGIN
--Call Insert
BEGIN TRY
/*Remaining Documents----------*/
DECLARE #sdtdoc_id Table (n int primary key identity(1,1), id int)
if(#sRemainingDocs is not null)
begin
--INSERT INTO #sdtdoc_id (id) SELECT Name from splitstring(#sRemainingDocs)
INSERT INTO #sdtdoc_id (id) SELECT [Value] from dbo.FN_ListToTable(#sRemainingDocs,',')
end
Declare #isRemaining int=0;
SELECT #isRemaining=Count(*) FROM #sdtdoc_id
/*Calculate stdfrm_scrt_apprvstatus*/
Declare #stdfrm_scrt_apprvstatus char(1)='A';--Approved
Declare #TotalDescripancies int;
select #TotalDescripancies=count(doc_id) from #DTProcessAdmittedDocUploadScrutiny where doc_id_scrtyn='Y'
if(#isRemaining>0)
begin
set #stdfrm_scrt_apprvstatus='H';--Discrepancies Found
end
else if exists (select count(doc_id) from #DTProcessAdmittedDocUploadScrutiny where doc_id_scrtyn='Y')
begin
if(#TotalDescripancies>0)
begin
set #stdfrm_scrt_apprvstatus='H';--Discrepancies Found
end
end
/* Check if Discrepancies Found first time then assign to Checker o.w assign to direct college like grievance*/
if(#stdfrm_scrt_apprvstatus='H')
begin
declare #countAuditLog int=0;
select #countAuditLog=count(stdfrm_id) from tbl_ProcessAdmittedScrutinyAuditLog where stdfrm_id =#stdfrm_id
if (#countAuditLog=0)
begin
set #stdfrm_scrt_apprvstatus='G'--'E';--Discrepancies Found set Edit request assign to Checker
end
--else if (#countAuditLog=1)
-- begin
--set #stdfrm_scrt_apprvstatus='G';--Discrepancies Found set Grievance assign to college
-- end
end
/*----------------------*/
/*Update status in original table-----*/
Update tbl_ProcessAdmitted set stdfrm_scrt_apprvstatus=#stdfrm_scrt_apprvstatus
,stdfrm_scrt_apprvon=getdate(),stdfrm_scrt_apprvby=#stdfrm_scrt_apprvby
,stdfrm_scrt_apprvcomment=#stdfrm_scrt_apprvcomment
where stdfrm_id =#stdfrm_id
/*Add in Main Student Log-----------*/
/********* The row here gets inserted multiple times *******************/
INSERT into tbl_ProcessAdmittedScrutinyAuditLog
(stdfrm_id, stdfrm_scrt_apprvstatus, stdfrm_scrt_apprvon, stdfrm_scrt_apprvby, stdfrm_scrt_apprvcomment )
values
(#stdfrm_id, #stdfrm_scrt_apprvstatus, getdate(), #stdfrm_scrt_apprvby, #stdfrm_scrt_apprvcomment)
DECLARE #scrtaudit_id int =##identity
/*Completed -------------------------*/
DELETE FROM tbl_ProcessAdmittedDocUploadScrutiny WHERE stdfrm_id =#stdfrm_id
SET NOCOUNT ON;
/********* The row here gets inserted multiple times *******************/
INSERT tbl_ProcessAdmittedDocUploadScrutiny
(stdfrm_id, course_id, doc_id, doc_id_scrtyn, doc_id_scrtrmrk, doc_id_comment)
SELECT #stdfrm_id, #course_id, doc_id, doc_id_scrtyn, doc_id_scrtrmrk, doc_id_comment
FROM #DTProcessAdmittedDocUploadScrutiny;
/*Scrutiny Document Log -------------------------*/
/********* The row here gets inserted multiple times *******************/
INSERT tbl_ProcessAdmittedDocUploadScrutinyAuditLog
(scrtaudit_id,stdfrm_id, course_id, doc_id, doc_id_scrtyn, doc_id_scrtrmrk, doc_id_comment)
SELECT #scrtaudit_id,#stdfrm_id, #course_id, doc_id, doc_id_scrtyn, doc_id_scrtrmrk, doc_id_comment
FROM #DTProcessAdmittedDocUploadScrutiny;
/*Remaining Documents Insert into table*/
DELETE FROM tbl_ProcessAdmittedDocUploadScrutinyRemiaing WHERE stdfrm_id =#stdfrm_id
DECLARE #Id int,#doc_id int
WHILE (SELECT Count(*) FROM #sdtdoc_id) > 0
BEGIN
Select Top 1 #Id = n,#doc_id=id From #sdtdoc_id
--Do some processing here
insert into tbl_ProcessAdmittedDocUploadScrutinyRemiaing(stdfrm_id, doc_id )
values (#stdfrm_id,#doc_id)
insert into tbl_ProcessAdmittedDocUploadScrutinyRemiaingAuditLog
(scrtaudit_id, stdfrm_id, doc_id )
values (#scrtaudit_id,#stdfrm_id,#doc_id)
DELETE FROM #sdtdoc_id WHERE n = #Id
END --Begin end While
/*End Remaining Documents-----------*/
SET #result=#stdfrm_scrt_apprvstatus
END TRY
BEGIN CATCH
SET #result='N'
insert into tbl_ErrorSql( ErrorMessage, stdfrm_id)
values(coalesce(Error_Message(),ERROR_LINE()),#stdfrm_id)
END CATCH;
--End of Call Insert
END
SELECT #result
END

problem with raiseerror()

what I want to do is to create a stored procedure that executes insert statement.There is a possibility the execution to fail because of a check constraint for the table Employee.In that case I want to handle a user-defined error.Obviously the following procedure is not working properly because it always raises my error,but not only when insertion fails.
EXEC sp_addmessage 50001, 16, N'Title must be one of the following - Captain,Engineer,Flight-attendant,Purser,First-officer';
CREATE PROCEDURE InsertIntoEmployee
#firstName nvarchar(30),
#familyName nvarchar(30),
#title nvarchar(50),
#address nvarchar(50),
#chiefID int ,
#salary money ,
#FK_IDCrew int,
#FK_DepartmentID int
AS
BEGIN
declare #err_num int;
declare #err_sev int;
declare #err_msg int;
begin try
insert into Employee(firstName, familyName, title, address, chiefID, salary, FK_IDCrew,
FK_DepartmentID)
values(#firstName, #familyName, #title, #address, #chiefID, #salary, #FK_IDCrew,
#FK_DepartmentID);
raiserror(50001,16,1);
END try
begin catch
set #err_num=ERROR_NUMBER();
set #err_sev=ERROR_SEVERITY();
set #err_msg=ERROR_STATE();
raiserror(#err_num,#err_sev,#err_msg);
end catch
end
GO
In this case:
Title should be a lookup to another table and a foreign key
In the CATCH block you can trap the FK constraint violation separately if you want...
...but you'd only allow rows from the new table in your client so I wouldn't personally
No need for a sys.messages entry
Your code will also always hit the RAISERROR too which doesn't add any value,
I hope that the dimensions mentioned in the parameter list is sycn with table columns length.
Before insertion, You should check take care of following points.
Check the existence of #FK_IDCrew value in it's table.
Check the existence of #FK_DepartmentID value in it's table.
It should be like below.
If Not Exists(Select IDCrewColumnName From Table Where columnName = #FK_IDCrew)
Begin
return here from the stored procedure.
End
In case any of them fails to meet the conditions, you should show some user friendly message to user that
(a) Crew ID, you are going to insert, either deleted or does not exists in the database.
(b) DepartmentID, you are going to insert, either deleted or does not exists in the database.
In this way the probability of error will also come to an end.