Be easy on me... still a newbie T-SQL programmer :)
I have a Stored Procedure that is intended to take three input parameters, evaluate them and send back one parameter (#eligible). If I just execute a T-SQL script with variables hard coded in, my ##ROWCOUNT sets #eligible to 1. When I EXECUTE a call to it as a stored procedure, it does not return #eligible correctly. Here is the procedure:
ALTER PROCEDURE [dbo].[proc_Eligible]
(#control AS nvarchar(10),
#checkno AS nvarchar(10),
#event_type AS nvarchar(7),
#eligible AS bit OUTPUT)
AS
BEGIN
SET #eligible = 0
SELECT #control AS Control, #checkno AS CheckNum
-- Is the check drawn on an eligible bank?
SELECT
H.CONTROL,
H.NAME,
H.RECV_DATE,
H.CHECK_NUM,
H.BANK,
SUM(D.RECV_AMOUNT)
FROM
[ZZZ].[dbo].[MRRECVH] H INNER JOIN
[ZZZ].[dbo].[MRRECVD] D ON H.control = D.CONTROL
WHERE
BANK IN (SELECT
RIMAS_Code
FROM
[Custom].[dbo].[Bank_Account])
AND H.CONTROL = #control
AND H.CHECK_NUM = #checkno
GROUP BY
H.CONTROL,
H.BANK,
H.NAME,
H.CHECK_NUM,
H.RECV_DATE
HAVING
SUM(D.RECV_AMOUNT) > 0
IF ##ROWCOUNT > 0
SELECT #eligible = 1
END
(On the next to last line, I have tried 'SET #eligible = 1', but that didn't make any difference).
To call the procedure:
DECLARE
#eligible AS bit
EXECUTE proc_Eligible
#Control = '3034'
,#Checkno = '5011'
,#event_type = 'update'
,#eligible = #eligible
SELECT #eligible
As I mentioned, if I isolate the stored procedure SELECT statement and hard code the variables, it works great, so it's probably my newbie inexperience with passing data.
(#event_type will be used later when I adapt this into a table trigger)
Thank you,
Kevin
It seems that you have to specify OUTPUT keyword as well, when you call your stored proc:
EXECUTE proc_Eligible
#Control = '3034'
,#Checkno = '5011'
,#event_type = 'update'
,#eligible = #eligible OUTPUT -- <--- mark parameter as output
Related
I have a stored procedure A on server 1 that takes 2 parameters from the user, and then using a linked server (ew), pulls in the results (a table) from server 2.
ALTER PROCEDURE [DW].[StoredProcA]
#InvFromDate date OUTPUT,
#InvToDate date OUTPUT
AS
WITH CTE_Labor AS
(
SELECT blabla
FROM LinkedServer.Database.schema.table
<lots more ctes, etc.>
For performance, I'd like to instead have a stored procedure A still accept the 2 parameters, but then pass them on to stored procedure B that sits on Server 2, and return those results back to the user.
Say - I can put the stored procedure on server 2, and call it from Server 1
DECLARE #return_value int
EXEC #return_value = [LinkedServer].[DB].[Schema].[StoredProcB]
#InvFromDate = '2022-10-01',
#InvToDate = '2022-10-31'
That works.
But I'm not clear on the syntax to do the above, but have those 2 parameters be entered by the user in stored procedure 1.
Clearly this attempt is wrong:
ALTER PROCEDURE dbo.StoredProc1
#InvFromDate DATE,
#InvToDate DATE
AS
BEGIN
DECLARE #return_value int;
EXEC #return_value = [LinkedServer].[DB].[Schema].[StoredProcB]
#InvFromDate = #InvFromDate,
#InvToDate = #InvToDate;
RETURN #return_value;
END
Edit: Maybe this attempt isn't wrong.
It works when I right click and run the stored procedure, returning both the desired table and Return Value = 0. It just doesn't work when I point our front-end GUI at it. But that might not be a question for here.
Since you are already using a linked server you could utilise this openquery approach Insert results of a stored procedure into a temporary table
Noting the following:
OPENQUERY/ linked servers are generally bad but I'm sure you're all over this
parameter string concatenation is bad
Your wrapper proc has output parameters but I don't see any reason for it... so I've removed them. See if it makes a difference.
--
ALTER PROCEDURE [DW].[StoredProcA]
#InvFromDate date,
#InvToDate date
AS
DECLARE #sql VARCHAR(4000)
SET #sql = 'EXEC [DB].[Schema].[StoredProcB] #InvFromDate = ''' + FORMAT(#InvFromDate + 'yyyy-MM-dd') + ''',#InvToDate = ''' + FORMAT(#InvToDate,'yyy-MM-dd') + ''''
PRINT(#sql) -- for degbugging cause this never works first time
SELECT *
INTO #tmpTable
FROM OPENQUERY([LinkedServer], #SQL)
SELECT * FROM #tmpTable
Got it.
1.) For this method, have to go into the Linked Server, and set [Enable Promotion of Distribution Transaction] = FALSE.
2.) Syntax
Alter proc [dbo].[999_Test]
#InvFromDate date
,#InvToDate date
as
IF OBJECT_ID('tempdb..#tmpbus') IS NOT NULL drop table #tmpbus;
CREATE TABLE #tmpBus
(
Column 1 (datatype),
Column 2 (datatype),
etc. )
INSERT INTO #tmpBus
EXEC [LinkedServer].[DB].Schema.[StoredProcInLinkedServerO]
#InvFromDate,
#InvToDate;
select *
from #tmpBus
GO
When exactly do we use stored procedures with output parameters and when do we use stored procedures without parameters?
I base my question on an example:
Stored procedure with output parameter
CREATE PROCEDURE uspGetContactsCountByCity
#City nvarchar(60),
#ContactsCount int OUT
AS
BEGIN
SELECT #ContactsCount = COUNT(ContactID)
FROM Contacts
WHERE City = #City
END
Stored procedure executing
DECLARE #ContactsTotal INT
EXEC uspGetContactsCountByCity #ContactsCount = #ContactsTotal OUT, #city = 'Berlin'
SELECT #ContactsTotal
Results: 2
Stored procedure without output parameter
CREATE PROCEDURE uspGetContactsCountByCity2
#City nvarchar(60)
AS
BEGIN
SELECT COUNT(ContactID)
FROM Contacts
WHERE City = #City
END
Stored procedure executing:
EXEC uspGetContactsCountByCity2 #city = 'Berlin'
Results: 2
Both procedures return the same result, in same form, so what's the difference?
Basically, the result you're seeing is actually the result of your SELECT at the end of the procedure, which is doing the same thing.
Please take a look at this documentation:
If you specify the OUTPUT keyword for a parameter in the procedure definition, the stored procedure can return the current value of the parameter to the calling program when the stored procedure exits. To save the value of the parameter in a variable that can be used in the calling program, the calling program must use the OUTPUT keyword when executing the stored procedure.
So basically if you would like your stored procedure to just return just a value instead of a data set, you could use the output parameter. For example, let's take the procedures you have given as an example. They both do the same thing, this is why you got the same result. But what about changing a little bit in the first procedure that has the output parameter.
Here's an example:
create table OutputParameter (
ParaName varchar(100)
)
insert into OutputParameter values ('one'), ('two'),('three'),('one')
CREATE PROCEDURE AllDataAndCountWhereOne
#name nvarchar(60),
#count int OUT
as
Begin
SELECT #count = COUNT(*) from OutputParameter
Where ParaName = #name
select Distinct(ParaName) from OutputParameter
End
Declare #TotalCount int
Exec AllDataAndCountWhereOne #count = #TotalCount OUT, #name = 'One'
Select #TotalCount
With this example, you are getting all the distinct stored data in the table, plus getting the count of a given name.
ParaName
--------------------
one
three
two
(3 row(s) affected)
-----------
2
(1 row(s) affected)
This is one way of using the output parameter. You got both the distinct data and the count you wanted without doing extra query after getting the initial data set.
At the end, to answer your question:
Both procedures gives us the same result, in same form, so what's the difference?
You didn't make a difference in your own results, this is why you didn't really notice the difference.
Other Examples:
You could use the OUT parameter in other kinds of procedures. Let's assume that your stored procedure doesn't return anything, it's more like a command to the DB, but you still want a kind of message back, or more specifically a value. Take these two examples:
CREATE PROCEDURE InsertDbAndGetLastInsertedId
--This procedure will insert your name in the database, and return as output parameter the last inserted ID.
#name nvarchar(60),
#LastId int OUT
as
Begin
insert into OutputParameterWithId values (#name);
SELECT #LastId = SCOPE_IDENTITY()
End
or:
CREATE PROCEDURE InsertIntoDbUnlessSomeLogicFails
--This procedure will only insert into the db if name does exist, but there's no more than 5 of it
#name nvarchar(60),
#ErrorMessage varchar(100) OUT
as
Begin
set #ErrorMessage = ''
if ((select count(*) from OutputParameterWithId) = 0)
begin
set #ErrorMessage = 'Name Does Not Exist'
return
end
if ((select count(*) from OutputParameterWithId) = 5)
begin
set #ErrorMessage = 'Already have five'
return
end
insert into OutputParameterWithId values (#name);
End
These are just dummy examples, but just to make the idea more clear.
An example, based on yours would be if you introduced paging to the query.
So the result set is constrained to 10 items, and you use a total count out parameter to drive paging on a grid on screen.
Answer from ozz regarding paging does not make sense because there is no input param that implements a contraint on the number of records returned.
However, to answer the question... the results returned by these stored procedures are not the same. The first returns the record count of contacts in given city in the out param ContactsCount. While the count may also be recieved in the second implement through examining the reader.Rows.Count, the actual records are also made a available. In the first, no records are returned - only the count.
Consider the following T-SQL code snippet:
CREATE PROC dbo.SquareNum(#i INT OUTPUT)
AS
BEGIN
SET #i = #i * #i
--SELECT #i
END
GO
DECLARE #a INT = 3, #b INT = 5
EXEC dbo.SquareNum #a OUTPUT
EXEC dbo.SquareNum #b
SELECT #a AS ASQUARE, #b AS BSQUARE
GO
DROP PROC dbo.SquareNum
The result set is:
ASQUARE BSQUARE
----------- -----------
9 5
As can be seen, #b is not squared, b/c it was not passed-in as output parameter (no OUTPUT qualifier when passing in the parameter).
I would like to know if there is a way I could check within stored procedure body (dbo.SquareNum body in this case) to see if a parameter has indeed been passed in as an OUTPUT parameter?
------ THIS WILL GIVE YOU THE BOTH VALUE IN squared------
CREATE PROC dbo.SquareNum(#i INT OUTPUT)
AS
BEGIN
SET #i = #i * #i
--SELECT #i
END
GO
DECLARE #a INT = 3, #b INT = 5
EXEC dbo.SquareNum #a OUTPUT
EXEC dbo.SquareNum #b OUTPUT
SELECT #a AS ASQUARE, #b AS BSQUARE
GO
DROP PROC dbo.SquareNum
-----TO CHECK STORED PROCEDURE BODY-----
SELECT OBJECT_NAME(object_id),
OBJECT_DEFINITION(object_id)
FROM sys.procedures
WHERE OBJECT_DEFINITION(object_id) =(SP_NAME)
Actually, there is a very simple way!
Make the parameter optional by setting a default value (#Qty AS Money = 0 Below)
Then, pass a value OTHER THAN THE DEFAULT when calling the procedure. Then immediately test the value and if it is other than the default value you know the variable has been passed.
Create Procedure MyProcedure(#PN AS NVarchar(50), #Rev AS NVarchar(5), #Qty AS Money = 0 OUTPUT) AS BEGIN
DECLARE #QtyPassed AS Bit = 0
IF #Qty <> 0 SET #QtyPassed = 1
Of course that means the variable cannot be used for anything other than OUTPUT unless you have a default value that you know will never be used as an INPUT value.
You can do this by query to sys views:
select
p.name as proc_name,
par.name as parameter_name,
par.is_output
from sys.procedures p
inner join sys.parameters par on par.object_id=p.object_id
where p.name = 'SquareNum'
or check in Management Studio in database tree:
[database] -> Programmability -> Stored Procedures -> [procedure] -> Parameters
Maybe I'm wrong but I don't believe it's possible. OUTPUT is part of the stored procedure definition so you should know when a parameter is or not OUTPUT. There is no way to set it dynamically so I think it's pointless to determine by code when a parameter is output or not because you already know it.
If you are trying to write a dynamic code, Piotr Lasota's answer should drive you to the correct way to realize when a parameter is Output.
Use the following query to get the name of all the parameters and to check if it is a output parameter:
select name, is_output from sys.parameters
I'm facing this error " must declare the scalar variable #return " in PowerBuilder 9 running on SQL server 14. When I'm executing the stored procedure using the SQL management studio it is returning 10000 as expected. But while calling this SP from PowerBuilder I'm facing the error. Any suggestions are appreciated. Thanks
Function in PowerBuilder code:
Declare sp_v procedure for
#return = proc_v_sp
#eid = :p_eid,
#year = :p_year,
#bid = :p_bid,
#hid = :p_hid
using sqlca;
Execute sp_v;
IF SQLCA.SQLCode <> 0 THEN
lReturn = SQLCA.SQLCode
ELSE
FETCH sp_v INTO :lReturn;
END IF
CLOSE sp_v;
In SQL SERVER SP:
Alter procedure proc_v_sp
#eid int,
#year int,
#bid varchar(8),
#hid char(3)
As
Begin
Declare #count int,
Declare..............
..........ignoring as it is long SP...........
Select #count = count(*)
from sy_e
where sy_e_eid = #eid and sy_e_year= #year
IF #count >0
RETURN 20000
ELSE
RETURN 10000
END
Looking at the documentation, I don't see anything about how to get a RETURN value back from an executed stored procedure. The documentation lists the syntax for the SP declaration in PB as...
DECLARE logical_procedure_name PROCEDURE FOR
SQL_Server_procedure_name
#Param1 = value1, #Param2 = value2,
#Param3 = value3 OUTPUT,
{USING transaction_object} ;
So that's why you're getting the syntax error. It's just not expecting #return there. If you can change the stored procedure, then you should be able to use an OUTPUT parameter. After a bit of googling, it looks like you would still have to use FETCH after the EXECUTE to get the variable specified for the output parameter populated.
I came back to this once I had PB in front of me because I was curious if it was possibles. After a bit of experimentation and looking at the MSDN docs for RETURN, I was able to get the return value populated in the returnValue variable using the code below.
long returnValue
DECLARE sp_test PROCEDURE FOR
#return_status = sp_test_return
USING SQLCA;
EXECUTE sp_test;
FETCH sp_test INTO :returnValue;
Here's the stored procedure.
CREATE PROCEDURE [dbo].[sp_test_return]
AS
RETURN 159
GO
You're making this all way too hard...
Change your RETURN to SELECT, and use a stored procedure datawindow. You can then rip out all that code, and replace it with one line.
dw.retrieve( args )
And your return value will be dw.getItemNumber( 1, "return_status")
-Paul-
I don't have sample code.
But it's super easy... Your SP needs to return a result set instead of a return value - even if that result set is a single value on a single row.
Change the RETURN to SELECT. That returns a result set.
Now, create a datawindow and select Stored Procedure as the datasource. Then select your sp as the source. Test it by providing the values for arguments and seeing if it returns the result you're looking for.
From here, it's just PB code.
datastore myDW
myDW = create datastore
myDW.setTransObject( SQLCA )
myDW.retrieve( args... )
theResult = myDW.getItemNumber( 1, "return_status" )
I have the following SP for SQL Server. Strangely the SP has weired behaviour when executing the query
Select #max_backup_session_time = Max(MachineStat.BackupSessionTime) from MachineStat where MachineStat.MachineID = #machine_id;
It takes 1 second if the MachineStat table has rows pertaining to #machine_id but if there are no rows for a #machine_id then it takes more than half a minute to execute. Can someone please help me understand this.
SET NOCOUNT ON;
DECLARE #MachineStatsMId TABLE (
MachineId INT NULL,
BackupSessiontime BIGINT NULL,
MachineGroupName NVARCHAR(128) NULL )
DECLARE #machine_id AS INT;
DECLARE #Machine_group_id AS INT;
DECLARE #machine_group_name AS NVARCHAR(128);
DECLARE #max_backup_session_time AS BIGINT;
SET #machine_id = 0;
SET #Machine_group_id = 0;
SET #machine_group_name = '';
DECLARE MachinesCursor CURSOR FOR
SELECT m.MachineId,
m.MachineGroupId,
mg.MachineGroupName
FROM Machines m,
MachineGroups mg
WHERE m.MachineGroupId = mg.MachineGroupId;
OPEN MachinesCursor;
FETCH NEXT FROM MachinesCursor INTO #machine_id, #machine_group_id, #machine_group_name;
WHILE ##FETCH_STATUS = 0
BEGIN
SELECT #max_backup_session_time = Max(MachineStat.BackupSessionTime)
FROM MachineStat
WHERE MachineStat.MachineID = #machine_id;
INSERT INTO #MachineStatsMId
VALUES (#machine_id,
#max_backup_session_time,
#machine_group_name);
FETCH NEXT FROM MachinesCursor INTO #machine_id, #machine_group_id, #machine_group_name;
END;
SELECT *
FROM #MachineStatsMId;
CLOSE MachinesCursor;
DEALLOCATE MachinesCursor;
GO
Here is an alternate version that avoids a cursor and table variable entirely, uses proper (modern) joins and schema prefixes, and should run a lot quicker than what you have. If it still runs slow in certain scenarios, please post the actual execution plan for that scenario as well as an actual execution plan for the fast scenario.
ALTER PROCEDURE dbo.procname
AS
BEGIN
SET NOCOUNT ON;
SELECT
m.MachineId,
BackupSessionTime = MAX(ms.BackupSessionTime),
mg.MachineGroupName
FROM dbo.Machines AS m
INNER JOIN dbo.MachineGroups AS mg
ON m.MachineGroupId = mg.MachineGroupId
INNER JOIN dbo.MachineStat AS ms -- you may want LEFT OUTER JOIN here, not sure
ON m.MachineId = ms.MachineID
GROUP BY m.MachineID, mg.MachineGroupName;
END
GO