Can anyone explain the design decision behind preventing table valued parameters from being specified as output parameters to stored procedures?
I can\'t count the n
In the presentation on Optimizing Microsoft SQL Server 2008 Applications Using Table Valued Parameters, XML, and MERGE by Michael Rys he says. (at 32:52)
Note that in SQL Server 2008 table valued parameters are read only. But as you notice we actually require you to write READONLY. So that actually then means that at some point in the future maybe if you say please, please please often enough we might be able to actually make them writable as well at some point. But at the moment they are read only.
Here is the connect item you should use to add your "please". Relax restriction that table parameters must be readonly when SPs call each other.
Srini Acharya made a comment on the connect item.
Allowing table valued parameters to be read/write involves quite a bit of work on the SQL Engine side as well as client protocols. Due to time/resource constraints as well as other priorirites, we will not be able to take up this work as part of SQL Server 2008 release. However, we have investigated this issue and have this firmly in our radar to address as part of the next release of SQL Server.
Note that all code was entered directly into post from memory. I may have a type wrong in the example or some similar error. It is just to demonstrate the technique that this would facilitate, which won't work with any version of SQL Server released at the time of this writing. So it doesn't really matter if it currently compiles or not.
I know this question is old by now, but perhaps someone coming across my post here might benefit from understanding why it's a big deal that TVPs can't be directly manipulated by a stored proc and read as output parameters by the calling client.
"How do you..." questions regarding OUTPUT TVPs have littered SQL Server forums for more than half a decade now. Nearly every one of them involves someone attempting some supposed workaround that completely misses the point of the question in the first place.
It is entirely non sequitur that you can "get a result set that matches a table type" by creating a Table Typed variable, Inserting into it and then returning a read from it. When you do that, the result set is still not a message. It is an ad hoc ResultSet that contains arbitrary columns that "just happen to match" a UDTT. What is needed is the ability for the following:
create database [Test]
create schema [Request]
create schema [Response]
create schema [Resources]
create schema [Services]
create schema [Metadata]
create table [Resources].[Foo] ( [Value] [varchar](max) NOT NULL, [CreatedBy] [varchar](max) NOT NULL) ON [PRIMARY]
insert into [Resources].[Foo] values("Bar", "kalanbates");
create type [Request].[Message] AS TABLE([Value] [varchar](max) NOT NULL)
create type [Response].[Message] AS TABLE([Resource] [varchar](max) NOT NULL, [Creator] [varchar](max) NOT NULL, [LastAccessedOn] [datetime] NOT NULL)
create PROCEDURE [Services].[GetResources]
(@request [Request].[Message] READONLY, @response [response].[Message] OUTPUT)
AS
insert into @response
select [Resource].[Value] [Resource]
,[Resource].[CreatedBy] [Creator]
,GETDATE() [LastAccessedOn]
inner join @request as [Request] on [Resource].[Value] = [Request].[Value]
GO
and have an ADO.NET client be able to say:
public IEnumerable<Resource> GetResources(IEnumerable<string> request)
{
using(SqlConnection connection = new SqlConnection("Server=blahdeblah;database=Test;notGoingToFillOutRestOfConnString")
{
connection.Open();
using(SqlCommand command = connection.CreateCommand())
{
command.CommandText = "[Services].[GetResources]"
command.CommandType = CommandType.StoredProcedure;
SqlParameter _request;
_request = command.Parameters.Add(new SqlParameter("@request","[request].[Message]");
_request.Value = CreateRequest(request,_request.TypeName);
_request.SqlDbType = SqlDbType.Structured;
SqlParameter response = new SqlParameter("@response", "[response].[Message]"){Direction = ParameterDirection.Output};
command.Parameters.Add(response);
command.ExecuteNonQuery();
return Materializer.Create<List<ResourceEntity>>(response).AsEnumerable(); //or something to that effect.
//The point is, messages are sent to and received from the database.
//The "result set" contained within response is not dynamic. It has a structure that can be *reliably* anticipated.
}
}
}
private static IEnumerable<SqlDataRecord> CreateRequest(IEnumerable<string> values, string typeName)
{
//Optimally,
//1)Call database stored procedure that executes a select against the information_schema to retrieve type metadata for "typeName", or something similar
//2)Build out SqlDataRecord from returned MetaData
//Suboptimally, hard code "[request].[Message]" metadata into a SqlMetaData collection
//for example purposes.
SqlMetaData[] metaData = new SqlMetaData[1];
metaData[0] = new SqlMetaData("Value", SqlDbType.Varchar);
SqlDataRecord record = new SqlDataRecord(metaData);
foreach(string value in values)
{
record.SetString(0,value);
yield return record;
}
}
The point here is that with this structure, the Database defines [Response].[Message],[Request].[Message], and [Services].[GetResource] as its Service Interface. Calling clients interact with "GetResource" by sending a pre-determined message type and receive their response in a pre-determined message type. Of course it can be approximated with an XML output parameter, you can somewhat infer a pre-determined message type by instituting tribal requirements that retrieval stored procedures must insert its response into a local [Response].[Message] Table Typed variable and then select directly out of it to return its results. But none of those techniques are nearly as elegant as a structure where a stored procedure fills a response "envelope" provided by the client with its payload and sends it back.
Still in 2020, SQL version "Microsoft SQL Azure (RTM) - 12.0.2000.8", I am not able to edit the Table value parameter within the Stored Procedure. So I did the work around by moving the data into Temp table and edited it.
ALTER PROCEDURE [dbo].[SP_APPLY_CHANGESET_MDTST09_MSG_LANG]
@CHANGESET AS [dbo].[MDTSTYPE09_MSG_LANG] READONLY
AS
BEGIN
SELECT * INTO #TCHANGESET FROM @CHANGESET
UPDATE #TCHANGESET SET DTST08_MSG_K = 0 WHERE ....
...............
With respect to (emphasis added):
So someone please provide me a good reason why TVPs were designed to be readonly input parameters.
I just posted a more detailed answer to this on DBA.StackExchange here:
READONLY parameters and TVP restrictions
But the summary of it goes like this:
According to this blog post ( TSQL Basics II - Parameter Passing Semantics ), a design goal of Stored Procedure OUTPUT parameters is that they merely mimic "by reference" behavior when the Stored Procedure completes successfully! But when there is an error that causes the Stored Procedure to abort, then any changes made to any OUTPUT parameters would not be reflected in the current value of those variables upon control returning to the calling process.
But when TVPs were introduced, they implemented them as truly passing by reference since continuing the "by value" model -- in which a copy of it is made to ensure that changes are lost if the Stored Procedure does not complete successfully -- would not be efficient / scalable, especially if a lot of data is being passed in through TVP.
So there is only one instance of the Table Variable that is the TVP, and any changes made to it within any Stored Procedure (if they were not restricted to being READONLY
) would be immediately persisted and would remain, even if the Stored Procedure encountered an error. This violates the design goal stated at the beginning of this summary. And, there is no option for somehow tying changes made to a TVP to a transaction (even something handled automatically, behind the scenes) since table variables are not bound by transactions.
Hence, marking them as READONLY
is the only way (at the moment) to maintain the design goal of Stored Procedure parameters such that they do not reflect changes made within the Stored Procedure unless: the parameter is declared as OUTPUT
and the Stored Procedure complete successfully.
Table-valued parameters have the following restrictions(source MSDN):
there are few options to over come this restriction one is
CREATE TYPE RTableType AS TABLE(id INT, NAME VARCHAR )
go
CREATE PROCEDURE Rproc @Rtable RTABLETYPE READONLY,
@id INT
AS
BEGIN
SELECT *
FROM @Rtable
WHERE ID = @id
END
go
DECLARE @Rtable RTABLETYPE
DECLARE @Otable RTABLETYPE
INSERT INTO @Rtable
VALUES (1,'a'),
(2,'b')
INSERT @Otable
EXEC Rproc
@Rtable,
2
SELECT *
FROM @Otable
through this you can get the table values out