I have a lot of stored procedures which return table result. I figured out dynamic management view to get metadata of a returned table.
create procedure test
(#p int)
as
begin
select 'a' AS Col1, 'b' AS Col2
end
So, for this stored procedure, this dmv works just fine:
SELECT *
FROM sys.dm_exec_describe_first_result_set('EXEC Test #p = 1', NULL, 0)
But if I change stored procedure to this
alter procedure test
(#p int)
as
begin
if #p = 0
begin
select 'a' AS Col1, 'b' AS Col2
end
else
begin
select 1 AS Col1, 2 AS Col2
end
end
That dmv doesn't work - the metadata could not be determined.
Is there a way to determine metadata of a stored procedure as shown in the second example above? Maybe changes in dmv params, etc?
Related
I have a parameter in my stored procedure called internal. If "internal" = yes then I want to display an additional 2 columns in my results. If it's no I don't want to display these columns.
I can do a case statement and then I can set the column to be empty but the column name will still be returned in the results.
My questions are:
Is there a way not to return these 2 columns in the results at all?
Can I do it in one case statement and not a separate case statement for each column?
Thank you
No, CASE is a function, and can only return a single value.
And According to your comment:-
The issue with 2 select statements are that it's a major complicated
select statement and I really don't want to have to have the whole
select statement twice.
so you can use the next approach for avoid duplicate code:-
Create procedure proc_name (#internal char(3), .... others)
as
BEGIN
declare #AddationalColumns varchar(100)
set #AddationalColumns = ''
if #internal = 'Yes'
set #AddationalColumns = ',addtionalCol1 , addtionalCol2'
exec ('Select
col1,
col2,
col3'
+ #AddationalColumns +
'From
tableName
Where ....
' )
END
Try IF Condition
IF(internal = 'yes')
BEGIN
SELECT (Columns) FROM Table1
END
ELSE
BEGIN
SELECT (Columns With additional 2 columns) FROM Table1
END
You can do something like this solution. It allows you to keep only one copy of code if it's so important but you will have to deal with dynamic SQL.
CREATE TABLE tab (col1 INT, col2 INT, col3 INT);
GO
DECLARE #internal BIT = 0, #sql NVARCHAR(MAX)
SET #sql = N'SELECT col1 ' + (SELECT CASE #internal WHEN 1 THEN N', col2, col3 ' ELSE N'' END) + N'FROM tab'
EXEC sp_executesql #sql
GO
DROP TABLE tab
GO
Another option is to create a 'wrapper' proc. Keep your current one untouched.
Create a new proc which executes this (pseudo code):
create proc schema.wrapperprocname (same #params as current proc)
as
begin
Create table #output (column list & datatypes from current proc);
insert into #output
exec schema.currentproc (#params)
if #internal = 'Yes'
select * from #output
else
select columnlist without the extra 2 columns from #output
end
This way the complex select statement remains encapsulated in the original proc.
Your only overhead is keeping the #output table & select lists in in this proc in sync with any changes to the original proc.
IMO it's also easier to understand/debug/tune than dynamic sql.
I'm busy trying to rewrite some PostgreSQL stored procedures/functions for SQL Server 2014s TSQL.
I am struggling to return my values from this stored procedure though, this one is just a test but I am trying to return multiple rows of data in this case the for the two variables si_code and co_desc.
I have my procedure as follows (as a test)
if (object_id('p_get_serial')) is not null
drop procedure p_get_serial
go
create procedure p_get_serial(#par01 char(20), #par02 integer)
as
declare
#co_num integer,
#co_desc char(20),
#si_code char(20),
#log char(40)
declare mycur cursor for
select co_num, co_desc
from colours
where co_num <= #par02
open mycur
fetch next from mycur into #co_num,
#co_desc
while ##FETCH_STATUS = 0
begin
set #si_code = ''
select #si_code = si_code
from sitems
where si_co_num = #co_num
set #log = #co_desc + ' ' + #si_code
raiserror(#log,0,1) with nowait
fetch next from mycur into #co_num, #co_desc
end
close mycur deallocate mycur
go
exec p_get_serial #par01 = 'paramater01', #par02 = 10
what is the best way to return my results knowing that there will be several rows?
In T-SQL you do not need to declare a cursor. Just select what you need and it will be available to the client app.
Cursor is Oracle/DB2/PostgreSQL etc way of returning data. SQL Server does not need it.
create procedure p
as
select 1 as a
returns a recordset containing one record with one column.
create procedure p
as
select 1 as a, 'a' as b
union select 2, 'b'
returns two rows each with two columns.
Example of a more complex processing before returning a result set:
create procedure p
as
begin
declare #a int, #b varchar(10)
select #a = 1
select #b = convert(varchar(10), #a)
select #a = #a + 1
select #a as a, #b as b -- this will be the resultset returned to the client
end
All you need to do is, just save the data for each row in a temp table or table variable and just write a SELECT statement at the the end of the Stored Procedure.
Your question is not clear what you need exactly, you have a cursor and while loop, they seem to be redundant
I'm working on a procedure that should return a o or a 1, depending on result from parameter calculation (parameters used to interrogate 2 tables in a database).
When I excute that code in a query pane, it gives me the results i'm expecting.
code looks like:
SELECT TOP 1 state, updDate INTO #history
FROM [xxx].[dbo].[ImportHystory] WHERE (db = 'EB') ORDER BY addDate DESC;
IF (SELECT state FROM #history) = 'O'
BEGIN
SELECT TOP 1 * INTO #process_status
FROM yyy.dbo.process_status WHERE KeyName = 'eb-importer';
IF(SELECT s.EndDate FROM #process_status s) IS NOT NULL
IF (SELECT s.EndDate FROM #process_status s) > (SELECT h.updDate FROM #history h)
BEGIN
IF (SELECT MessageLog from #process_status) IS NOT NULL SELECT 1;
ELSE SELECT 0;
END
ELSE
SELECT 1;
ELSE
SELECT 1;
END
ELSE
SELECT 0
I'm in the situation where EndDate from #process_status is null, so the execution returns 1.
Once i put the SAME code in a SP, and pass 'EB' and 'eb-importer' as parameters, it returns 0.
And I exec the procedure with the data from the table right in front of me, so i know for sure that result is wrong.
Inside the procedure:
ALTER PROCEDURE [dbo].[can_start_import] (#keyName varchar, #db varchar, #result bit output)
DECLARE #result bit;
and replace every
SELECT {0|1}
with
SELECT #result = {0|1}
Executed from the Query pane:
DECLARE #result bit;
EXEC [dbo].[can_start_import] #KeyName = 'eb-importer', #db = 'EB', #result = #result OUTPUT
SELECT #result AS N'#result'
Why does this happen?
You are doing a top(1) query without an order by. That means SQL Server can pick any row from table1 that matches the where clause.
If you want to guarantee that the result is the same every time you execute that code you need an order by statement that unambiguously orders the rows.
So, apparently 2 things needed to be done:
set the length of the varchar parameter with a higher length,
filter with ' like ' instead of ' = ' for god knows what reason
Now it work as i expected to do, but i still don't get the different results between the query pane and the procedure if i use the equal...
I'm using SQL Server 2008.
I need to be able to pass table output via stored procedure parameters.
Example: MyTable (col1, col2) has the following data:
1, 'A'
2, 'B'
3, 'C'
Stored procedure:
sp_read_tbl (#p1_col1 int OUT,
#p1_col2 char(1) OUT,
#p2_col1 int OUT,
#p2_col2 char(1) OUT,
#p3_col1 int OUT,
#p3_col2 char(1) OUT)
How can I do this inside a stored procedure?
As noted, this isn't the usual way you'd get data out of a table in a stored procedure. And if your table ever gets a fourth row, you'll have to re-do not just the stored procedure, but also the code that calls it.
But if you really need to do this, you can. There are a number of approaches; mine would look like this:
-- DO NOT start your stored procs with "sp_". That's reserved for MS.
-- Many people use "up_" as a substitute.
create procedure dbo.up_read_tbl
(
#p1_col1 int OUT,
#p1_col2 char(1) OUT,
#p2_col1 int OUT,
#p2_col2 char(1) OUT,
#p3_col1 int OUT,
#p3_col2 char(1) OUT
)
AS
SELECT top 1
#p1_col1 = col1
, #p1_col2 = col2
FROM dbo.MyTable
SELECT top 1
#p2_col1 = col1
, #p2_col2 = col2
FROM dbo.MyTable
WHERE col1 <> #p1_col1
SELECT top 1
#p3_col1 = col1
, #p3_col2 = col2
FROM dbo.MyTable
WHERE col1 <> #p1_col1
AND col1 <> #p2_col1
I'm having a stored procedure which returns two result sets based on the success or failure.
SP success result set: name, id ,error,desc
SP failure result sret: error,desc
I'm using the following query to get the result of the stored procedure. It returns 0 for success and -1 for failure.
declare #ret int
DECLARE #tmp TABLE (
name char(70),
id int,
error char(2),
desc varchar(30)
)
insert into #tmp
EXEC #ret = sptest '100','King'
select #ret
select * from #tmp
If the SP is success the four field gets inserted into the temp table since the column matches.
But in case of failure the sp result set has only error and desc which does not matchs with no of columns in the temp table...
.I can't change the Sp, so I need to do some thing (not sure) in temp table to handle both failure and success.
You can't return 2 different recordsets and load the same temp table.
Neither can try and fill 2 different tables.
There are 2 options.
Modify your stored proc
All 4 columns are returned in all conditions
1st pair (name, ID) columns are NULL on error
2nd pair (error, desc) are NULL on success
If you are using SQL Server 2005 then use the TRY/CATCH to separate your success and fail code paths. The code below relies on using the new error handling to pass back the error result set via exception/RAISERROR.
Example:
CREATE PROC sptest
AS
DECLARE #errmsg varchar(2000)
BEGIN TRY
do stuff
SELECT col1, col2, col3, col4 FROM table etc
--do more stuff
END TRY
BEGIN CATCH
SELECT #errmsg = ERROR_MESSAGE()
RAISERROR ('Oops! %s', 16, 1, #errmsg)
END CATCH
GO
DECLARE #tmp TABLE ( name CHAR(70), id INT, error char(2), desc varchar(30)
BEGIN TRY
insert into #tmp
EXEC sptest '100','King'
select * from #tmp
END TRY
BEGIN CATCH
PRINT ERROR_MESSAGE()
END CATCH
My fault!!
Was too quick in the answer.
You need only to relv on the return value, so building up the logic against it is much better.
If you still want to use the temp table, then calling the sptest twice could be a way to deal with it (not optimal though), one time to get the return value and based on it then have 2 different temp tables you are filling up (one would be with the 4 fields, the other only with 2 fields).
declare #ret int
DECLARE #tmp TABLE (name CHAR(70), id INT, error char(2), desc varchar(30))
DECLARE #tmperror TABLE (error char(2), desc varchar(30))
EXEC #ret = sptest '100','King'
IF #ret != 0
BEGIN
INSERT INTO #tmperror
EXEC sptest '100','King';
SELECT * FROM #tmperror;
END
ELSE
BEGIN
INSERT INTO #tmp
EXEC sptest '100','King';
SELECT * FROM #tmp;
END
Keep in mind that this solution is not optimal.
Try modifying your table definition so that the first two columns are nullable:
DECLARE #tmp TABLE (
name char(70) null,
id int null,
error char(2),
desc varchar(30)
)
Hope this helps,
Bill
You cannot do this with just one call. You will have to call it once, either getting the return status and then branching depending on the status to the INSERT..EXEC command that will work for the number of columns that will be returned or Call it once, assuming success, with TRY..CATCH, and then in the Catch call it again assuming that it will fail (which is how it got to the CATCH).
Even better, would be to either re-write the stored procedure so that it returns a consistent column set or to write you own stored procedure, table-valued function or query, by extracting the code from this stored procedure and adapting it to your use. This is the proper answer in SQL.