TVP - are they used for input and output? - tsql

I've started to read the notes on Table Value Parameters
HERE ON MSDN
HERE ON SOMMARSKOG
Can these TVPs be used as input parameters and output parameters?
Is there a point in having them as an output parameter?
I get the feeling it might be possible to have a TVP as output from one stored procedure and then that feeding into another stored procedure - possible?
The syntax of the script which calls the first sproc and then calls the second sproc using the output TVP from the first is the bit I'm unsure of.
EDIT
Apologies for the confusion of my post - it seems that the initial procedures results need to go into the TVP - I thought that the TVP needed to be involved within that sproc. So a model of what I was talking about is the following - hopefully a valid use of TVPs...
CREATE TYPE myfirstTVP AS TABLE (id INT NOT NULL PRIMARY KEY);
GO --<<this sproc will find the ids (+ other fields) that need to be investigated
CREATE PROC test1 as
SELECT 1 UNION
SELECT 2 UNION
SELECT 3;
GO
GO --<<this sproc uses the found ids to do one aspect of the investigation
CREATE PROC test2
#t2 myfirstTVP READONLY
AS
SELECT id*2
FROM #t2;
GO
GO --<<this sproc uses the found ids to do another aspect of the investigation
CREATE PROC test3
#t4 myfirstTVP READONLY
AS
SELECT id*3
FROM #t4;
GO
--<<this is where the TVP is used and the sprocs are called
DECLARE #t3 myfirstTVP ;
INSERT INTO #t3
EXEC test1;
EXEC test2 #t3;
EXEC test3 #t3;

I'm not 100% sure what you want to achieve, but you can in a sense emulate behaviour of 'output' parameters ,
CREATE TYPE LIST_OF_INT AS TABLE (id int not null primary key);
GO
create procedure test1 as
begin
declare #t1 LIST_OF_INT;
insert into #t1 (id) values (1);
select * from #t1;
end;
GO
declare #t2 LIST_OF_INT ;
insert into #t2
EXEC test1;
select * from #t2;

I think you missed this bit from the MSDN link you cited.
Table-valued parameters must be passed as input READONLY parameters to
Transact-SQL routines.

Related

Running a stored procedure using a resultset as the parameter and insert into a table

I have the question below,
I do have a query which returns 10 rows for example.
SELECT CarId FROM Car
EXEC spQ_GetCar (#CarId)
Also, I have a stored procedure that uses an id from the table above.
My question is, how can I run the stored procedure and use the output of the table as the parameter and then insert that into another temp table.
Would it be possible by using a cursor and dynamic SQL ?, has anyone faced this before ?
NOTES: I cannot create a table type to use it
A fast answer:
Create a table, then you can insert-exec:
insert yourPreparedtable
EXEC spQ_GetCar (#CarId)
The true answer:
Erland Sommarskog's article is the most in-depth resource you will find. Many alternatives with detailed advantages and disadvantages.
I achieved this by following approach below:
CREATE TABLE #TEMP (CarId INT)
DECLARE #CarId INT
DECLARE Car CURSOR
,CarId INT SET Car = CURSOR STATIC
FOR
SELECT CarId
FROM Car
OPEN Car
WHILE 1 = 1
BEGIN
FETCH Car
INTO #CarId
IF ##FETCH_STATUS <> 0
BREAK
INSERT INTO #TEMP
EXEC spQ_GetCar(#CarId)
END
CLOSE #ServiceAgreements
DEALLOCATE #ServiceAgreements
DROP TABLE #TEMP
Notes: Performance was not relevant as it was a one off script.

IF... ELSE... two mutually exclusive inserts INTO #temptable

I need to insert either set A or set B of records into a #temptable, depending on certain condition
My pseudo-code:
IF OBJECT_ID('tempdb..#t1') IS NOT NULL DROP TABLE #t1;
IF {some-condition}
SELECT {columns}
INTO #t1
FROM {some-big-table}
WHERE {some-filter}
ELSE
SELECT {columns}
INTO #t1
FROM {some-other-big-table}
WHERE {some-other-filter}
The two SELECTs above are exclusive (guaranteed by the ELSE operator). However, SQL compiler tries to outsmart me and throws the following message:
There is already an object named '#t1' in the database.
My idea of "fixing" this is to create #t1 upfront and then executing a simple INSERT INTO (instead of SELECT... INTO). But I like minimalism and am wondering whether this can be achieved in an easier way i.e. without explicit CREATE TABLE #t1 upfront.
Btw why is it NOT giving me an error on a conditional DROP TABLE in the first line? Just wondering.
You can't have 2 temp tables with the same name in a single SQL batch. One of the MSDN article says "If more than one temporary table is created inside a single stored procedure or batch, they must have different names". You can have this logic with 2 different temp tables or table variable/temp table declared outside the IF-Else block.
Using a Dyamic sql we can handle this situation. As a developoer its not a good practice. Best to use table variable or temp table.
IF 1=2
BEGIN
EXEC ('SELECT 1 ID INTO #TEMP1
SELECT * FROM #TEMP1
')
END
ELSE
EXEC ('SELECT 2 ID INTO #TEMP1
SELECT * FROM #TEMP1
')

Explain the effect of a parent column in a nested select

I have a scenario where I need to delete rows from a table using the outcome of a nested select. Like this:
DECLARE #tbl_big TABLE (bigID int);
INSERT INTO #tbl_big (bigID)
VALUES (1),(2),(3),(4),(5);
DECLARE #tbl_small TABLE (smallID int);
INSERT INTO #tbl_small (smallID)
VALUES (1),(2),(3);
DELETE FROM #tbl_big
WHERE (bigID IN (SELECT smallID FROM #tbl_small));
SELECT *
FROM #tbl_big; -- shows 4,5 as expected
However, during development I accidentally made a typo:
DELETE FROM #tbl_big WHERE (bigID IN (SELECT bigID FROM #tbl_small)); --bigID used instead of smallID
SELECT *
FROM #tbl_big; -- no rows
The result was that all rows within the parent table were deleted.
While this may be completely acceptable T-SQL, I've never seen it applied like this, nor would I expect the statement to even compile given that #tbl_small does not contain a bigID column.
Can anybody please clarify why/how this works, and is it valid T-SQL? Also, can you provide a real-world example where this is more useful than risky(!)?
bigID in the DELETE statement you mentioned referes to #tbl_big because it is legal to mention columns from the main table in the sub queries you write in the WHERE clause. For example, you can write the below:
DELETE FROM #tbl_big WHERE (bigID IN (SELECT smallID FROM #tbl_small WHERE smallID = bigID));
So, in your case, you just used all bigID values in your table in the sub query as a constant value.

Navigating the results of a stored procedure via a cursor using T-SQL

Due to a legacy report generation system, I need to use a cursor to traverse the result set from a stored procedure. The system generates report output by PRINTing data from each row in the result set. Refactoring the report system is way beyond scope for this problem.
As far as I can tell, the DECLARE CURSOR syntax requires that its source be a SELECT clause. However, the query I need to use lives in a 1000+ line stored procedure that generates and executes dynamic sql.
Does anyone know of a way to get the result set from a stored procedure into a cursor?
I tried the obvious:
Declare Cursor c_Data For my_stored_proc #p1='foo', #p2='bar'
As a last resort, I can modify the stored procedure to return the dynamic sql it generates instead of executing it and I can then embed this returned sql into another string and, finally, execute that. Something like:
Exec my_stored_proc #p1='foo', #p2='bar', #query='' OUTPUT
Set #sql = '
Declare Cursor c_Data For ' + #query + '
Open c_Data
-- etc. - cursor processing loop etc. goes here '
Exec #sql
Any thoughts? Does anyone know of any other way to traverse the result set from a stored proc via a cursor?
Thanks.
You could drop the results from the stored proc into a temp table and select from that for your cursor.
CREATE TABLE #myResults
(
Col1 INT,
Col2 INT
)
INSERT INTO #myResults(Col1,Col2)
EXEC my_Sp
DECLARE sample_cursor CURSOR
FOR
SELECT
Col1,
Col2
FROM
#myResults
Another option may be to convert your stored procedure into a table valued function.
DECLARE sample_cursor CURSOR
FOR
SELECT
Col1,
Col2
FROM
dbo.NewFunction('foo', 'bar')
You use INSERT ... EXEC to push the result of the procedure into a table (can be a temp #table or a #table variable), the you open the cursor over this table. The article in the link discusses the problems that may occur with this technique: it cannot be nested and it forces a transaction around the procedure.
You could execute your SP into a temporary table and then iterate over the temporary table with the cursor
create table #temp (columns)
insert into #temp exec my_stored_proc ....
perform cursor work
drop table #temp

Return multiple values from a SQL Server function

How would I return multiple values (say, a number and a string) from a user-defined function in SQL Server?
Change it to a table-valued function
Please refer to the following link, for example.
Another option would be to use a procedure with output parameters - Using a Stored Procedure with Output Parameters
Here's the Query Analyzer template for an in-line function - it returns 2 values by default:
-- =============================================
-- Create inline function (IF)
-- =============================================
IF EXISTS (SELECT *
FROM sysobjects
WHERE name = N'<inline_function_name, sysname, test_function>')
DROP FUNCTION <inline_function_name, sysname, test_function>
GO
CREATE FUNCTION <inline_function_name, sysname, test_function>
(<#param1, sysname, #p1> <data_type_for_param1, , int>,
<#param2, sysname, #p2> <data_type_for_param2, , char>)
RETURNS TABLE
AS
RETURN SELECT #p1 AS c1,
#p2 AS c2
GO
-- =============================================
-- Example to execute function
-- =============================================
SELECT *
FROM <owner, , dbo>.<inline_function_name, sysname, test_function>
(<value_for_#param1, , 1>,
<value_for_#param2, , 'a'>)
GO
Erland Sommarskog has an exhaustive post about passing data in SQL Server located here:
http://www.sommarskog.se/share_data.html
He covers SQL Server 2000, 2005, and 2008, and it should probably be read in its full detail as there is ample coverage of each method's advantages and drawbacks. However, here are the highlights of the article (frozen in time as of July 2015) for the sake of providing search terms that can be used to look greater details:
This article tackles two related questions:
How can I use the result set from one stored procedure in another, also expressed as How can I use the result set from a stored
procedure in a SELECT statement?
How can I pass a table data in a parameter from one stored procedure to another?
OUTPUT Parameters
Not generally applicable, but sometimes overlooked.
Table-valued Functions
Often the best choice for output-only, but there are several restrictions.
Examples:
Inline Functions: Use this to reuse a single SELECT.
Multi-statement Functions: When you need to encapsulate more complex logic.
Using a Table
The most general solution. My favoured choice for input/output scenarios.
Examples:
Sharing a Temp Table: Mainly for a single pair of caller/callee.
Process-keyed Table: Best choice for many callers to the same callee.
Global Temp Tables: A variation of process-keyed.
Table-valued Parameters
Req. Version: SQL 2008
Mainly useful when passing data from a client.
INSERT-EXEC
Deceivingly appealing, but should be used sparingly.
Using the CLR
Req. Version: SQL 2005
Complex, but useful as a last resort when INSERT-EXEC does not work.
OPENQUERY
Tricky with many pitfalls. Discouraged.
Using XML
Req. Version: SQL 2005
A bit of a kludge, but not without advantages.
Using Cursor Variables
Not recommendable.
Example of using a stored procedure with multiple out parameters
As User Mr. Brownstone suggested you can use a stored procedure; to make it easy for all i created a minimalist example. First create a stored procedure:
Create PROCEDURE MultipleOutParameter
#Input int,
#Out1 int OUTPUT,
#Out2 int OUTPUT
AS
BEGIN
Select #Out1 = #Input + 1
Select #Out2 = #Input + 2
Select 'this returns your normal Select-Statement' as Foo
, 'amazing is it not?' as Bar
-- Return can be used to get even more (afaik only int) values
Return(#Out1+#Out2+#Input)
END
Calling the stored procedure
To execute the stored procedure a few local variables are needed to receive the value:
DECLARE #GetReturnResult int, #GetOut1 int, #GetOut2 int
EXEC #GetReturnResult = MultipleOutParameter
#Input = 1,
#Out1 = #GetOut1 OUTPUT,
#Out2 = #GetOut2 OUTPUT
To see the values content you can do the following
Select #GetReturnResult as ReturnResult, #GetOut1 as Out_1, #GetOut2 as Out_2
This will be the result: