SQL Server optimize code in Stored Procedure - sql

Comparing two codes below,both do the same,but with slighty differences:
ALTER procedure [dbo].[SP_USUARIOS_UPDATE]
#usu_ds varchar(100),
#usu_dt_lst_log datetime,
#usu_ds_senha varchar(255),
#usu_ds_email varchar(100)
as
begin
declare #usu_ID int;
create table #TempUser
(
UsuID int,
Senha varchar(255),
Email varchar(100)
)
select Usuarios.usu_ID as UsuID,Usuarios.usu_ds_senha as Senha,
Usuarios.usu_ds_email as Email into #TempUser from Usuarios where Usuarios.usu_ds = #usu_ds
if(#usu_ds_senha is null)
begin
set #usu_ds_senha = (select #TempUser.Senha from #TempUser);
end
if(#usu_ds_email is null)
begin
set #usu_ds_email = (select #TempUser.Email from #TempUser);
end
set #usu_ID = (select #TempUser.UsuID from #TempUser);
update Usuarios set usu_dt_lst_log =
#usu_dt_lst_log,usu_ds_senha = #usu_ds_senha,usu_ds_email = #usu_ds_email where usu_ID = #usu_ID
end
AND
ALTER procedure [dbo].[SP_USUARIOS_UPDATE]
#usu_ds varchar(100),
#usu_dt_lst_log datetime,
#usu_ds_senha varchar(255),
#usu_ds_email varchar(100)
as
begin
declare #usu_ID int;
if(#usu_ds_senha is null)
begin
set #usu_ds_senha = (select Usuarios.usu_ds_senha from Usuarios where Usuarios.usu_ds = #usu_ds);
end
if(#usu_ds_email is null)
begin
set #usu_ds_email = (select Usuarios.usu_ds_email from Usuarios where Usuarios.usu_ds = #usu_ds);
end
set #usu_ID = (select Usuarios.UsuID from Usuarios where Usuarios.usu_ds = #usu_ds);
update Usuarios set usu_dt_lst_log =
#usu_dt_lst_log,usu_ds_senha = #usu_ds_senha,usu_ds_email = #usu_ds_email where usu_ID = #usu_ID
end
Do you think the first is faster than second in performance,i mean,first code use temp table (#TempUser) to store 3 fields from the real table.Second code,select all fields from the real table one by one.
What code is best optimized?

First -- if it's possible for your passed parameters to be null, you need to set defaults. For example:
#usu_ds_email varchar(100) = null
...
Otherwise, your null checks further down will never come into play -- the procedure will just fail.
Second -- just run a direct update. It seems like you're pushing a lot of data back and forth unnecessarily. E.g., you don't need to create a temp table from the table you're going to update, and then turn right around and update your table from the temp table you just created.
ALTER procedure [dbo].[SP_USUARIOS_UPDATE]
#usu_ds varchar(100),
#usu_dt_lst_log datetime,
#usu_ds_senha varchar(255) = null,
#usu_ds_email varchar(100) = null
as
begin
update Usuarios
set usu_dt_lst_log = #usu_dt_lst_log,
usu_ds_senha = isnull(#usu_ds_senha, usu_ds_senha),
usu_ds_email = isnull(#usu_ds_email, usu_ds_email)
where usu_ID = #usu_ds
end

Third way...
ALTER procedure [dbo].[SP_USUARIOS_UPDATE]
#usu_ds varchar(100),
#usu_dt_lst_log datetime,
#usu_ds_senha varchar(255) = null,
#usu_ds_email varchar(100) = null
as
begin
update x
set x.usu_dt_lst_log = #usu_dt_lst_log,
x.usu_ds_senha = ISNULL(#usu_ds_senha, x.usu_ds_senha),
x.usu_ds_email = ISNULL(#usu_ds_email, x.usu_ds_email)
from Usuarios x where x.usu_ds = #usu_ds
end

Which one runs faster? Set a profiler, run both, and get some real data: then you'll know.
However, based on my own previous experiences, the Temp Table has always been faster for me.

Related

Stored procedure not inserting data into table

I'm trying to implement a stored procedure like this where it's going to compare UserID from another table. If that user id already exists in dbo.user info table, it will insert data into [dbo].[BulkUploadTagDetail] table.
Every time I execute this stored procedure running into an issue that the data doesn't get inserted into my database table.
CREATE PROCEDURE [dbo].[InsertBulkUploadTagDetail]
(#BulkTagID INT,
#PortalUserID UNIQUEIDENTIFIER,
#EmailAddress VARCHAR(256),
#BulkUploadFileName VARCHAR(256),
#CreatedOn DATETIME2(7),
#IsCompleted BIT = 0)
AS
BEGIN
SET NOCOUNT ON
IF EXISTS (SELECT UserID
FROM dbo.UserInfo
WHERE UserID = #PortalUserID)
BEGIN
UPDATE [unp]
SET [unp].[BulkTagID] = #BulkTagID,
[unp].[UserID] = #PortalUserID,
[unp].[EmailAddress] = #EmailAddress,
[unp].[BulkUploadFileName] = #BulkUploadFileName,
[unp].[CreatedOn] = GETUTCDATE(),
[unp].[IsCompleted] = #IsCompleted
FROM
[dbo].[InsertBulkUploadTagDetail] unp
INNER JOIN
[dbo].[UserInfo] uinfo ON [unp].[UserID] = [uinfo].[UserID]
WHERE
[unp].[UserID] = #PortalUserID
END
ELSE
BEGIN
INSERT INTO [dbo].[BulkUploadTagDetail]
([BulkTagID], [PortalUserID], [EmailAddress],
[BulkUploadFileName], [CreatedOn], [IsCompleted])
VALUES (#BulkTagID, #PortalUserID, #EmailAddress,
#BulkUploadFileName, #CreatedOn, #IsCompleted)
END
END

Update statement, update value is conditionally evaluated

I have the below pseudo code written that I want to implement in T-SQL. I need this code included in an existing stored procedure, I was trying to achieve the below with function call passing in a temp table as a parameter, is it possible to pass a temp table as a function parameter. Please let me know if there is a better approach to this.
Table: #Temp_Table has a column RefId which refers to #TempReadUpdateValue.Id. There are rules to identify if the #TempReadUpdateValue.Id can be applied to #Temp_Table.RefId.
Rule 1: the data qualifies in the DateRange
Rule 2: the #TempReadUpdateValue.Id is available if (Allowed - Used) > 0.
Allowed is fixed value and used will increment as its assigned.
I want to achieve the above with an UPDATE statement on Temp_Table, the challenge that I face is #Temp_Table.RefId = #TempReadUpdateValue.Id, need to increment
#TempReadUpdateValue.Used = #TempReadUpdateValue.Used + #Temp_Table.Units
every next row in #Temp_Table need to re-evaluate rules #1 and #2 for RefId assignment.
Update statement:
DECLARE #OLD INT = 0; -- THIS CAN ALSO BE SET TO 1, basically passed in as param to the stored procedure.
CREATE TABLE #TempReadUpdateValue
(
Id INT,
From_Date DateTime,
Thru_Date DateTime,
Allowed int,
Used int
)
CREATE TABLE #Temp_Table
(
Pk_ID INT,
DOS DateTime,
Units Int,
Ref_Id int
)
UPDATE #Temp_Table
SET Ref_Id = CASE
WHEN #OLD = 0 THEN 121
ELSE NewImplementation(DOS, Units, #TempReadUpdateValue)
END
CREATE OR ALTER FUNCTION NewImplementation
(#DOS DATETIME, #Units INT, #TempReadUpdateValue)
RETURNS INT
AS
BEGIN
DECLARE #Id INT
DECLARE #Allowed INT
DECLARE #Used INT
SELECT
#Id = Id,
#Allowed = Allowed,
#Used = Used
FROM
#TempReadUpdateValue
DECLARE #ReturnValue INT = 0
IF (#Id > 0) AND (#Allowed - #Used ) > 0
BEGIN
#ReturnValue = #Id;
UPDATE #TempReadUpdateValue
SET Used = (Used + #Units);
END
RETURN #ReturnValue
END

Sending SQL a boolean and update a table as 1 or -1 and how to "update" an empty row for the initial values

I have a Songs table with a likes column that holds the number of likes users sent . Each user sends a boolean (1 or 0) through a C# app which adds to the likes column.
About my procedure:
I want to know if there is more an efficient and short way of writing the part 1 of the function?
I had to manually insert '0' instead of the NULL for the first time for the function to work. It wasn't working because the initial value for Likes column is NULL. Is there a way to affect the row for the first time when it has NULL in it?
For part 2 of the function with [Users_Likes_Songs] table, I want to update if the user send a like (true = 1) or removed it (false = 0).
How can I update this table for the first time when the users 'like' must be valued as '1', when its rows are completely empty?
I thank you very much if you can help me.
The procedure:
CREATE PROCEDURE Songs_Likes
#User_ID INT,
#SongID INT,
#Song_like BIT
AS
BEGIN
--- part 1 of the function
IF (#Song_like = 1)
BEGIN
UPDATE [Songs]
SET [Likes] = [Likes] + #Song_like
WHERE [Song_ID] = #SongID
END
IF (#Song_like = 0)
BEGIN
UPDATE [Songs]
SET [Likes] = [Likes] - 1
WHERE [Song_ID] = #SongID
END
--- part 2 of the function with the second table
UPDATE [Users_Likes_Songs]
SET [LikeSong] = #Song_like
WHERE ([UserID] = #User_ID) AND ([SongID] = #SongID)
END
I think that the better method would be to change your design to calculate the likes and have a table that stores the likes for each user. In simple terms, something like:
USE Sandbox;
GO
CREATE SCHEMA music;
GO
CREATE TABLE music.song (SongID int IDENTITY(1,1),
Artist nvarchar(50) NOT NULL,
Title nvarchar(50) NOT NULL,
ReleaseDate date);
CREATE TABLE music.[User] (UserID int IDENTITY(1,1),
[Login] nvarchar(128));
CREATE TABLE music.SongLike (LikeID bigint IDENTITY(1,1),
SongID int,
UserID int,
Liked bit);
CREATE UNIQUE NONCLUSTERED INDEX UserLike ON music.SongLike(SongID, UserID); --Stops multiple likes
GO
--To add a LIKE you can then have a SP like:
CREATE PROC music.AddLike #SongID int, #UserID int, #Liked bit AS
BEGIN
IF EXISTS (SELECT 1 FROM music.SongLike WHERE UserID = #UserID AND SongID = #SongID) BEGIN
UPDATE music.SongLike
SET Liked = #Liked
WHERE UserID = #UserID
AND SongID = #SongID
END ELSE BEGIN
INSERT INTO music.SongLike (SongID,
UserID,
Liked)
VALUES (#SongID, #UserID, #Liked);
END
END
GO
--And, if you want the number of likes:
CREATE VIEW music.SongLikes AS
SELECT S.Artist,
S.Title,
S.ReleaseDate,
COUNT(CASE SL.Liked WHEN 1 THEN 1 END) AS Likes
FROM music.Song S
JOIN music.SongLike SL ON S.SongID = SL.SongID
GROUP BY S.Artist,
S.Title,
S.ReleaseDate;
GO
For 1) this is a bit clearer, shorter and a bit more efficient.
UPDATE [Songs]
SET [Likes] = COALESCE([Likes], 0) + CASE WHEN #Song_like = 1 THEN 1
WHEN #Song_like = 0 THEN -1
ELSE 0 END
WHERE [Song_ID] = #SongID;
For the second part you can do something like this:
IF NOT EXISTS (SELECT 1
FROM [Users_Likes_Songs]
WHERE [UserID] = #User_ID
AND [SongID] = #SongID)
INSERT INTO [Users_Likes_Songs] (User_ID, SongID, [LikeSong])
VALUES (#User_ID, #SongID, #Song_like)
ELSE
UPDATE [Users_Likes_Songs]
SET [LikeSong] = #Song_like WHERE ([UserID] = #User_ID) AND ([SongID] = #SongID)
You can try this query in your procedure
UPDATE [songs]
SET [likes] = Isnull ([likes], 0) + ( CASE WHEN #Song_like THEN 1 ELSE -1 END)
WHERE [song_id] = #SongID

Set parameters in a stored procedure that returns a resultset

I'm trying to consolidate some code but before I open this particular can of worms I wanted to find out from you guys. If I have several stored procedures...
sproc1 - "master proc" which sets #test
sproc2 - proc that executes if #test exists and returns both a resultset and (if possible) resets #serial
sproc3 - proc that executes if #test does not exist and returns both a resultset and (if possible) resets #serial
sproc1
#leftStack INT,
#leftTray INT,
#midStack INT,
#midTray INT,
#rightStack INT,
#rightTray INT
AS
DECLARE #soLineNumber varchar(50)
DECLARE #serial VARCHAR(50)
DECLARE #rack INT
DECLARE #tray INT
DECLARE #position INT
SELECT #test = oL.[SERIAL_NUMBER]
FROM [ROBOTICS_OPTICS_MECHUAT].[dbo].[AOF_ORDER_OPTICS] AS oL
WHERE NOT EXISTS
(
SELECT [SERIAL_NUMBER]
FROM [ROBOTICS_OPTICS_MECHUAT].[dbo].[AOF_OPTIC_RESULTS] AS rL
WHERE oL.[SERIAL_NUMBER] = rL.[SERIAL_NUMBER]
)
AND NOT EXISTS
(
SELECT [SERIAL_NUMBER]
FROM [ROBOTICS_OPTICS_MECHUAT].[dbo].[AOF_OPTIC_INSERTED] AS oI
WHERE oL.[SERIAL_NUMBER] = oI.[SERIAL_NUMBER]
)
-- AND oL.[SO_LINE_NUMBER] = #soLineNumber --pick regardless of SO line number, to reduce gaps between lines
AND ((oL.[RACK] = #leftStack AND oL.[TRAY] = #leftTray)
OR (oL.[RACK] = #midStack AND oL.[TRAY] = #midTray)
OR (oL.[RACK] = #rightStack AND oL.[TRAY] = #rightTray))
ORDER BY [SO_LINE_NUMBER] ASC
IF NULLIF(#test, '') IS NOT NULL
BEGIN
EXEC sproc2
END
IF NULLIF(#test, '') IS NULL
BEGIN
EXEC sproc3
END
UPDATE [ROBOTICS_OPTICS_MECHUAT].[dbo].[AOF_ORDER_OPTICS] SET [PICKED] = 'True' WHERE [SERIAL_NUMBER] = #serial
END
My questions:
1) how can I reset #serial from sproc2 and sproc3?
2) in an ADO recordset query, will the results from the executed stored procedures pull in, if so, how?
For this to work sproc2 and sproc3 should been defined like this:
CREATE PROC sproc2 #test VARCHAR(50), #serial VARCHAR(50) OUTPUT
What this does is, it sends the value of #test as a value param. The OUTPUT keyword on #serial enables you to keep track of any changes done on #serial.

T-SQL Stored Procedure NULL input values cause select statement to fail

Below is a stored procedure to check if there is a duplicate entry in the database based upon checking all the fields individually (don't ask why I should do this, it just has to be this way).
It sounds perfectly straightforward but the SP fails.
The problem is that some parameters passed into the SP may have a null value and therefore the sql should read "is null" rather than "= null".
I have tried isnull(),case statements,coalesce() and dynamic sql with exec() and sp_executesql and failed to implement any of these. Here is the code...
CREATE PROCEDURE sp_myDuplicateCheck
#userId int,
#noteType char(1),
#aCode char(3),
#bCode char(3),
#cCode char(3),
#outDuplicateFound int OUT
AS
BEGIN
SET #outDuplicateFound = (SELECT Top 1 id FROM codeTable
WHERE userId = #userId
AND noteType = #noteType
AND aCode = #aCode
AND bCode = #bCode
AND cCode = #cCode
)
-- Now set the duplicate output flag to a 1 or a 0
IF (#outDuplicateFound IS NULL) OR (#outDuplicateFound = '') OR (#outDuplicateFound = 0)
SET #outDuplicateFound = 0
ELSE
SET #outDuplicateFound = 1
END
I think you need something like this for each possibly-null parameter:
AND (aCode = #aCode OR (aCode IS NULL AND #aCode IS NULL))
If I understand your question correctly, then I encourage you to do a little research on:
SET ANSI_NULLS OFF
If you use this command in your stored procedure, then you can use = NULL in your comparison. Take a look at the following example code to see how this works.
Declare #Temp Table(Data Int)
Insert Into #Temp Values(1)
Insert Into #Temp Values(NULL)
-- No rows from the following query
select * From #Temp Where Data = NULL
SET ANSI_NULLS OFF
-- This returns the rows where data is null
select * From #Temp Where Data = NULL
SET ANSI_NULLS ON
Whenever you SET ANSI_NULLS Off, it's a good practice to set it back to ON as soon as possible because this may affect other queries that you run later. All of the SET commands only affect the current session, but depending on your application, this could span multiple queries, which is why I suggest you turn ansi nulls back on immediately after this query.
I think this should work with COALESCE function. Try this:
CREATE PROCEDURE sp_myDuplicateCheck
#userId int,
#noteType char(1),
#aCode char(3),
#bCode char(3),
#cCode char(3),
#outDuplicateFound int OUT
AS
BEGIN
SET #outDuplicateFound = (SELECT Top 1 id FROM codeTable
WHERE userId = #userId
AND noteType = #noteType
AND COALESCE(aCode,'NUL') = COALESCE(#aCode,'NUL')
AND COALESCE(bCode,'NUL') = COALESCE(#bCode,'NUL')
AND COALESCE(cCode,'NUL') = COALESCE(#cCode,'NUL')
)
-- Now set the duplicate output flag to a 1 or a 0
IF (#outDuplicateFound IS NULL) OR (#outDuplicateFound = '') OR (#outDuplicateFound = 0)
SET #outDuplicateFound = 0
ELSE
SET #outDuplicateFound = 1
END
Good Luck!
Jason
Try this :
CREATE PROCEDURE sp_myDuplicateCheck
#userId int = 0,
#noteType char(1) = "",
#aCode char(3) = "",
#bCode char(3) = "",
#cCode char(3) = "",
#outDuplicateFound int OUT
AS
BEGIN
SET #outDuplicateFound = (SELECT Top 1 id FROM codeTable
WHERE #userId in (userId ,0)
AND #noteType in (noteType,"")
AND #aCode in (aCode , "")
AND #bCode in (bCode , "")
AND #cCode in (cCode ,"")
)
-- Now set the duplicate output flag to a 1 or a 0
IF (#outDuplicateFound IS NULL) OR (#outDuplicateFound = '') OR (#outDuplicateFound = 0)
SET #outDuplicateFound = 0
ELSE
SET #outDuplicateFound = 1
END
What this basically does is to provide default values to the input parameters in case of null and then in the where condition checks only if the values are not equal to the default values.
I would first add a check to see if all of the parameters were null at run time, i.e.,
IF(COALESCE(#userId, #noteType, #aCode, #bCode, #cCode) IS NULL)
BEGIN
-- do something here, log, print, return, etc.
END
Then after you've validated that the user passed something in you can use something like this in your WHERE clause
WHERE userId = COALESCE(#userId, userId)
AND noteType = COALESCE(#noteType, noteType)
AND aCode = COALESCE(#aCode, aCode)
AND bCode = COALESCE(#bCode, bCode)
AND cCode = COALESCE(#cCode, cCode)
EDIT: I may have missed the intent that if the parameter was passed in as null that means you explicitly want to test the column for null. My above where clause assumed that the null parameter meant 'skip the test on this column.'
Alternatively, I believe you can use your original query and add the ANSI_NULLS set option at the stored procedure create time. For example,
SET ANSI_NULLS OFF
GO
CREATE PROC sp_myDuplicateCheck....
Effectively this should allow your code to then evaluate column=null as opposed to column is null. I think Kalen Delaney once coined the ANSI_NULLS and QUOTED_IDENTIFIER options as 'sticky options' because if they're set at procedure create time they stay with the procedure at run time, regardless of how the connection at that time is set.
SET ANSI_NULLS OFF/On
That way you can do colName = null