Using UPDATE in stored procedure with optional parameters - sql-server

I have an SP like so (using SQL Server):
ALTER PROCEDURE [dbo].[sp_ClientNotes_update]
#id uniqueidentifier,
#ordering smallint = NULL,
#title nvarchar(20) = NULL,
#content text = NULL
AS
BEGIN
SET NOCOUNT ON;
UPDATE tbl_ClientNotes
SET ordering=#ordering, title=#title, content=#content
WHERE id=#id
END
I would like to only set the values if they are passed into the SP, i.e. not NULL. Can this be done?
This question seems to suggest the only way is using completely separate queries with conditionals, but for 3 optional parameters this would obviously be a nightmare!

Try this.
ALTER PROCEDURE [dbo].[sp_ClientNotes_update]
#id uniqueidentifier,
#ordering smallint = NULL,
#title nvarchar(20) = NULL,
#content text = NULL
AS
BEGIN
SET NOCOUNT ON;
UPDATE tbl_ClientNotes
SET ordering=ISNULL(#ordering,ordering),
title=ISNULL(#title,title),
content=ISNULL(#content, content)
WHERE id=#id
END
It might also be worth adding an extra part to the WHERE clause, if you use transactional replication then it will send another update to the subscriber if all are NULL, to prevent this.
WHERE id=#id AND (#ordering IS NOT NULL OR
#title IS NOT NULL OR
#content IS NOT NULL)

UPDATE tbl_ClientNotes
SET
ordering=ISNULL#ordering,ordering),
title=isnull(#title,title),
content=isnull(#content,content)
WHERE id=#id
I think I remember seeing before that if you are updating to the same value SQL Server will actually recognize this and won't do an unnecessary write.

One Idea:
UPDATE tbl_ClientNotes
SET ordering=ISNULL(#ordering, ordering),
title=ISNULL(#title, title),
content=ISNULL(#content, content)
WHERE id=#id

UPDATE tbl_ClientNotes
SET ordering=#ordering, title=#title, content=#content
WHERE id=#id
AND #ordering IS NOT NULL
AND #title IS NOT NULL
AND #content IS NOT NULL
Or if you meant you only want to update individual columns you would use the post above mine. I read it as do not update if any values are null

ALTER PROCEDURE LN
(
#Firstname nvarchar(200)
)
AS
BEGIN
UPDATE tbl_Students1
SET Firstname=#Firstname
WHERE Studentid=3
END
exec LN 'Thanvi'

Related

Correct way to use goto statement to transfer control to a label after go

For example. The following code fails because the 'ReturnControl' label is not recognized as it is after the go statement. But I need the configuration code for the query on the top as it gets modified repeatedly and it is easier to access instead of skimming through the whole query and finding the lines that need to be changed.
I am not limited to using goto. I am open to any other suggestions or methods that would allow keeping configuration logic on top of the query.
GOTO BatchSetup;
ReturnControl:
/*
Set Parameters here.
Configuration code that gets modified repeatedly
*/
Insert ##Parameters VALUES ('SomeParam', 'paramValue')
EXEC #CreateJob 'Job1'
EXEC #CreateJob 'Job2'
EXEC #ExecuteJob 'Job1'
EXEC #ExecuteJob 'Job2'
GOTO ENDBatch;
BatchSetup:
CREATE TABLE ##Jobs
(
ID INT IDENTITY,
JobName NVARCHAR(100),
);
CREATE TABLE ##Parameters
(
ParamName NVARCHAR(100),
ParamValue NVARCHAR(100),
);
GO
CREATE OR ALTER PROC #CreateJob
#JobName NVARCHAR(100)
AS
BEGIN
INSERT ##Jobs (JobName) VALUES (#JobName);
END
GO
CREATE OR ALTER PROC #ExecuteJob
#JobName NVARCHAR(100)
AS
BEGIN
DECLARE #Param NVARCHAR(100) = (SELECT ParamValue FROM ##Parameters WHERE ParamName = 'SomeParam');
/*
Code to execute Job
*/
END
GO
GOTO ReturnControl;
ENDBatch:
DROP TABLE IF EXISTS ##Jobs;
DROP TABLE IF EXISTS ##Parameters;
DROP PROC IF EXISTS #CreateJob;
DROP PROC IF EXISTS #ExecuteJob;
Edit:
I have restructured the query based on suggestions that somewhat did the job.
/*
Setup: Create temp stored procedures and temp tables. Access value using SESSION_CONTEXT
/*
Set Parameters here.
Configuration code that gets modified repeatedly
*/
EXEC sp_set_session_context #Key = 'key1', #Value = 'value1';
EXEC sp_set_session_context #Key = 'key2', #Value = 'value2;
EXEC Job1
EXEC Job2
EXEC Cleanup

Fire SQL Trigger When Column Value Changes

I am adding the date to a column in SQL when the 'workstatus' is 'completed', but my problem is, when I open and save the same job again in the software, it runs the trigger and changes the date again to a new value which I don't want.
I want the trigger to run only if the 'workstatus' value is something else than 'completed'.
GO
/****** Object: Trigger [dbo].[TRJCD_JOBREQUEST] Script Date: 06/25/2021 15:49:04 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER TRIGGER [dbo].[TRJCD_JOBREQUEST] ON [dbo].[TBL_JOBREQUEST]
AFTER UPDATE,INSERT
AS
if (Update (workstatus))
begin
DECLARE #Jobcompletiondate datetime
DECLARE #workstatus VARCHAR(15)
DECLARE #jobid int
select #workstatus = workstatus from inserted
select #jobid = jobid from inserted
select #Jobcompletiondate = GETDATE()
begin
if #workstatus='Completed'
update TBL_JOBREQUEST set JobCompDate=#Jobcompletiondate where jobid = #jobid
end
end
The following is how you should construct your trigger.
There is no need to assign any values to variables, triggers fire once per batch and always operate on the set of updated rows.
If you update a status to Completed you need to check it's not currently Completed, also if you want to retain the first JobCompDate even if the status is amended afterwards simply use a case expression to only update the column where it's currently NULL.
create or alter trigger [dbo].[TRJCD_JOBREQUEST] on [dbo].[TBL_JOBREQUEST]
after update,insert
as
if ##RowCount=0 return
set nocount on
if Update (workstatus)
begin
update t set
t.JobCompDate=case when t.JobCompDate is null then GetDate() else t.JobCompDate end
from inserted i join TBL_JOBREQUEST t on t.jobid=i.jobid
where i.workstatus='Completed'
and not exists (
select * from deleted d
where d.jobid=i.jobid and d.workstatus=i.workstatus
)
end
Please note that I do not have your data set, so I'm unable to test the trigger, however, based on what you provided in your question, I believe this is the answer you are seeking:
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER TRIGGER [dbo].[TRJCD_JOBREQUEST] ON [dbo].[TBL_JOBREQUEST]
AFTER UPDATE,INSERT
AS
if (Update (workstatus))
begin
DECLARE #Jobcompletiondate datetime
DECLARE #currentworkstatus VARCHAR(15)
DECLARE #oldworkstatus VARCHAR(15)
DECLARE #jobid int
select #oldworkstatus = workstatus from deleted
select #currentworkstatus = workstatus from inserted
select #jobid = jobid from inserted
select #Jobcompletiondate = GETDATE()
begin
if #currentworkstatus='Completed' and #oldworkstatus <> 'Completed'
update TBL_JOBREQUEST set JobCompDate=#Jobcompletiondate where jobid = #jobid
end
end
You needed to check if the deleted workstatus does not equal Completed and only then should the trigger fire.

Don't Understand SQL Procedure Error

I have a master table "Repairs" and a detail table "RepairDetails" I am trying to write a procedure to update both tables when I send the appropriate parameters from my application. Here is my SQL:
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE [dbo].[UpdateRepair]
#RepairID bigint,
#TypeID bigint = NULL,
#Directions nvarchar(3000) = NULL,
#NewDetails NewDetails READONLY
AS
BEGIN
SET NOCOUNT ON;
UPDATE Repairs
SET
TypeID = ISNULL(#TypeID, TypeID),
Directions = ISNULL(#Directions, Directions),
LastUpdate = SYSDATETIME()
WHERE RepairID = #RepairID;
IF #NewDetails IS NOT NULL UpdateRepairDetails;
END
where "NewDetails" is a User-defined table type and "UpdateRepairDetails" is a different stored procedure that takes #RepairID and #NewDetails as parameters.
I have an error and a question. The error message is:
Must declare the scalar variable "#NewDetails"
which I don't understand because it is defined.
And my question is: will the parameters "#RepairID" and "#NewDetails" get automatically passed to the "UpdateRepairDetails" procedure. If not, what is the proper approach to accomplish this?
You cannot assign NULL to a table variable. Hence you can't check whether a table variable is NULL.
Second: no. You should call as follows:
EXEC UpdateRepairDetails #RepairID, #NewDetails;

Need a stored procedure that inserts a row and returns the ID

I tried to write a stored procedure that first inserts a new record into table and then returned the id of this new record. I am not sure if it is the correct way and the best way to achieve this.
ALTER PROCEDURE dbo.spAddAsset
(
#Name VARCHAR(500),
#URL VARCHAR(2000)
)
AS
BEGIN
Set NOCOUNT on;
Insert Into Assets (Name, URL) Values (#Name, #URL)
Declare #new_identity int;
SELECT #new_identity = SCOPE_IDENTITY()
return #new_identity;
END
To return a single scalar value to the caller you should use an OUTPUT parameter, not RETURN. RETURN is for error/status codes. Also the prefix sp is redundant and unnecessary.
CREATE PROCEDURE dbo.AddAsset
#Name VARCHAR(500),
#URL VARCHAR(2000),
#new_identity INT = NULL OUTPUT
AS
BEGIN
SET NOCOUNT ON;
INSERT dbo.Assets(Name, URL) SELECT #Name, #URL;
SET #new_identity = SCOPE_IDENTITY();
END
GO
Then to call it:
DECLARE #new_identity INT;
EXEC dbo.AddAsset #Name = 'a', #URL = 'b', #new_identity = #new_identity OUTPUT;
PRINT #new_identity;
EDIT just adding a disclaimer that won't affect the asker in this specific scenario, but may help in other scenarios or for future readers. In SQL Server 2008 R2 and earlier, there is a potentially nasty bug with built-in functions such as SCOPE_IDENTITY when parallelism is used to derive the results to be inserted (think INSERT FROM othertable). This bug (here is the Connect item) is fixed in Cumulative Update #5 for SQL Server 2008 R2 SP1, but so far a fix has not appeared for 2008 R2 RTM, 2008 or 2005.
I can't see why it is necessary to place the returned row value into a new variable #new_identity. I would simply include SELECT SCOPE_IDENTITY(); at the end of the stored procedure like this:
CREATE PROCEDURE dbo.AddAsset
#Name VARCHAR(500),
#URL VARCHAR(2000)
AS
BEGIN
SET NOCOUNT ON;
INSERT dbo.Assets(Name, URL) SELECT #Name, #URL;
SELECT SCOPE_IDENTITY();
END
GO

Cursor inside Trigger not working

USE [ddb]
GO
SET ANSI_NULLS OFF
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TRIGGER [dbo].[requeststrigger]
ON [dbo].[requests]
AFTER INSERT,UPDATE
AS
BEGIN
DECLARE #email VARCHAR(400);
DECLARE #firstname VARCHAR(400);
DECLARE #requestno VARCHAR(400);
DECLARE #lastname VARCHAR(400);
DECLARE #statusid INT;
DECLARE thecursor CURSOR FOR SELECT inserted.requestno,contacts.firstname,contacts.lastname,contacts.email FROM request_contacts,contacts,inserted WHERE request_contacts.requestid=inserted.requestid AND contacts.contactid=request_contacts.contactid AND request_contacts.notification=1 AND contacts.notification=1;
SET #statusid = (SELECT statusid FROM inserted);
IF #statusid = 4 AND #statusid <> (SELECT statusid FROM deleted)
BEGIN
SET NOCOUNT ON
SET ARITHABORT ON
OPEN thecursor;
FETCH NEXT FROM thecursor
INTO #requestno,#firstname,#lastname,#email
WHILE ##FETCH_STATUS = 0
BEGIN
EXEC MAIL_SEND #email,#firstname,#requestno,#lastname;
FETCH NEXT FROM thecursor
INTO #requestno,#firstname,#lastname,#email
END
CLOSE thecursor;
DEALLOCATE thecursor
SET NOCOUNT OFF
END
END
This simply makes the whole UPDATE/INSERT not work. When I remove the cursor declaration, it works. The cursor is just selecting a field from a table that is existing in the same database called "contacts". What is wrong?
Are you prepared to consider amending your design? There appear to be a couple of issues with what you're attempting here.
A trigger isn't necessarily the best place to be doing this kind of row-by-row operation since it executes in-line with the changes to the source table, and will affect the performance of the system negatively.
Also, your existing code evaluates statusid only for a single row in the batch, although logically it could be set to more than one value in a single batch of updates.
A more robust approach might be to insert the rows which should generate a MAIL_SEND operation to a queuing table, from which a scheduled job can pick rows up and execute MAIL_SEND, setting a flag so that each operation is carried out only once.
This would simplify your trigger to an insert - no cursor would be required there (although you will still need a loop of some kind in the sechduled job).

Resources