Set parameters in a stored procedure that returns a resultset - sql

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.

Related

SQL Function is INCREDIBLY slow

I have a SQL function that is used to return a single value in another view, this function takes well over 30 seconds sometimes on larger databases, I think it might be running over and over?
Honestly I'm just losing my mind at this point and need the help. Does anyone know the best way to optimize this?
The T-SQL function looks like this:
IF OBJECT_ID('Base.fn_AssetPriority') IS NOT NULL
DROP FUNCTION Base.fn_AssetPriority
GO
CREATE FUNCTION Base.fn_AssetPriority
(#LID BIGINT)
RETURNS NVARCHAR(20)
WITH EXECUTE AS CALLER
AS
BEGIN
DECLARE #Priority NVARCHAR(20)
DECLARE #RGID BIGINT
DECLARE #CP TABLE
(
ConsequenceAssessmentID BIGINT,
[Sign] VARCHAR(2),
Score DECIMAL(18,2),
AssetPriority CHAR(1),
ConsNo INT
)
SET #Priority = 'Not Allocated'
INSERT INTO #CP
SELECT
ConsequenceAssessmentID, [Sign], Score, AssetPriority, ConsNo
FROM
Base.ConsequencePriority
ORDER BY
ConsNo DESC
SELECT #RGID = MAX(ID)
FROM ACA.ReviewGroup
WHILE EXISTS (SELECT * FROM #CP)
BEGIN
DECLARE #CAID BIGINT
DECLARE #ConsNo INT
DECLARE #Sign VARCHAR(2)
DECLARE #Score DECIMAL(18,2)
DECLARE #AP CHAR(1)
SELECT TOP 1
#CAID = ConsequenceAssessmentID,
#ConsNo = ConsNo,
#Sign = [Sign],
#Score = Score,
#AP = AssetPriority
FROM
#CP
ORDER BY
ConsNo DESC
IF #Sign = '='
BEGIN
IF EXISTS (SELECT * FROM ACA.ConsequenceAssessment
WHERE LID = #LID AND RGID = #RGID
AND BaseCAID = #CAID AND Score = #Score)
BEGIN
SET #Priority = #AP
BREAK
END
END
ELSE BEGIN
IF EXISTS (SELECT * FROM ACA.ConsequenceAssessment
WHERE LID = #LID AND RGID = #RGID
AND BaseCAID = #CAID AND Score >= #Score)
BEGIN
SET #Priority = #AP
BREAK
END
END
DELETE FROM #CP
WHERE ConsequenceAssessmentID = #CAID
AND ConsNo = #ConsNo
END
RETURN #Priority
END
There is another view that calls this as a field:
Base.fn_AssetPriority(BaseAS.ID) AS AssetPriority,
How on Earth do I optimize this? or get it to run a bit quicker?
It's possible the execution plan for your stored function is stale.
Try doing this and rerunning it.
EXEC sp_recompile N'Base.fn_AssetPriority';
If it gets faster you may want to run that recompile every so often. Maybe use a job to recompile it every day.
You probably don't want to put WITH RECOMPILE in the function's definition, because you use it a lot and the reason for recompilation is changing statistics in the tables it queries.

How to pass multiple values for a column in sql Stored Procedure?

i have two tables, such as
Queue
QueueID
LogParameters
QueueParameters
QueueParametersID
QueueID
LogParametersKey
LogParametersValue
I have to write SP inorder to make an entry in both the tables,
LogParameterKey and LogParameterValue may contains multiple values but the QueueID for each values should be same and QueueParameterID could be different.
**QueueID** **LogParameters**
1 AA
**QueueParametersID** **QueueID** **LogParametersKey** **LogParametersValue**
1 1 FirstName Mohammad
2 1 LastName Salman
3 1 Age 17
How do i pass multiple values for LogParameterKey and LogParameterValue?.. Someone suggested me to use Array for this... Is there's any other way?
CREATE Procedure AddQueue
#LogParameters NVARCHAR(255)
#AuditParameters AS AuditParameter READONLY,-- UserDefinedTable
AS
SET NOCOUNT ON;
BEGIN
DECLARE #QueueID BIGINT
EXECUTE dbo.procInsertQueue
#LogParameters = #LogParameters,
#QueueID = #QueueID OUTPUT
DECLARE #GetQueueID BIGINT = (SELECT QueuesID FROM Queues WHERE LogParameters= #LogParameters
DECLARE #AuditQueueParametersID BIGINT
DECLARE #TempTable TABLE(
ParameterKey NVARCHAR(255),
ParameterValue NVARCHAR(255),
AuditQueuesID BIGINT)
INSERT INTO #TempTable(ParameterKey,ParameterValue,QueuesID)
SELECT ParameterKey,ParameterValue,#GetQueueID FROM #AuditParameters
DECLARE #LogParameterKey NVARCHAR(255) = (SELECT ParameterKey FROM #TempTable WHERE QueuesID = #GetQueueID)
DECLARE #LogParameterValue NVARCHAR(255) = (SELECT ParameterValue FROM #TempTable WHERE QueuesID = #GetQueueID)
EXECUTE dbo.procAddQueueParameters
#AuditQueueID = #GetQueueID
#LogParametersKey = #LogParametersKey,
#LogParametersValue = #LogParametersValue,
#QueueParametersID = #QueueParametersID OUTPUT
END
END
1. Use User-Defined Table Types
https://technet.microsoft.com/en-us/library/bb522526(v=sql.105).aspx
2. Generate xml string and parse in SP
3.Use temp table
CREATE TABLE #Input
(
QueueParametersID ..
QueueID ..
LogParametersKey ..
LogParametersValue ..
)
--INSERT VALUES
EXEC dbo.YouSP
IN SP use table #Input

How can I call and get back the results of an SP from another Stored Procedure

I have this stored procedure:
CREATE PROCEDURE [dbo].[sp_get_correct_responses]
#QuestionUId UNIQUEIDENTIFIER
AS
BEGIN
...
-- This is the last part of the SP. I need to use the output
-- value of #AnswerGridCorrect in the calling SP
SELECT #AnswerGridCorrect = Correct
FROM Concatenated
WHERE RowNumber = ColumnCount
END
How can I call the stored procedure from another stored procedure, pass it the #QuestionUId parameter and put the returned variable #AnswerGridCorrect into a variable declared in the calling procedure?
Update: Here's the proposed answer:
CREATE PROCEDURE [dbo].[sp_get_correct_responses]
#QuestionUId UNIQUEIDENTIFIER,
#output VARCHAR(20) output
AS
BEGIN
select #QuestionUId
DECLARE #AnswerGridCorrect VARCHAR(20)
DECLARE #QuestionId int;
SELECT #QuestionId = QuestionId
FROM dbo.Question
Where QuestionUId = #QuestionUId;
Select #questionId;
WITH Partitioned AS (
SELECT ROW_NUMBER() OVER (PARTITION BY QuestionId ORDER BY AnswerId ASC) AS RowNumber,
COUNT(1) OVER (PARTITION BY QuestionId) AS ColumnCount,
CONVERT(VARCHAR(MAX), Correct) AS Correct
FROM dbo.Answer
WHERE [QuestionId] = #QuestionId
),
Concatenated AS (
SELECT RowNumber, ColumnCount, Correct FROM Partitioned WHERE RowNumber = 1
UNION ALL
SELECT P.RowNumber,
P.ColumnCount,
C.Correct + P.Correct AS Correct
FROM Partitioned P
INNER JOIN Concatenated C
ON P.RowNumber = C.RowNumber + 1
)
SET #output = (SELECT Correct
FROM Concatenated
WHERE RowNumber = ColumnCount)
RETURN
END
You could have a temp table in the other stored procedure and populate it with the results of this one:
INSERT INTO #table
Exec sp_get_correct_responses #QuestionUId
The other way would be to modify sp_get_correct_responses to produce an output as you are expecting only one value.
CREATE PROCEDURE [dbo].[sp_get_correct_responses]
#QuestionUId UNIQUEIDENTIFIER,
#output VARCHAR(20) output
AS
BEGIN
...
-- This is the last part of the SP. I need to use the output
-- value of #AnswerGridCorrect in the calling SP
SELECT #output = Correct
FROM Concatenated
WHERE RowNumber = ColumnCount
RETURN
END
And in your other SP:
DECLARE #output VARCHAR(20)
EXEC sp_get_correct_responses
#QuestionUId,
#output output
SELECT #output
You can make one table variable in parent SP and insert result of child SP in that like below :
DECLARE #TempTable TABLE(AnswerGridCorrect INT)
INSERT INTO #TempTable
EXEC [dbo].[sp_get_correct_responses] #QuestionUId

SQL Server optimize code in Stored Procedure

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.

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