问题
Long story short, I have a third-party application which behaves differently when it cannot retrieve the metadata of the query/stored procedure.
It is known, that sys.sp_describe_first_result_set fails to retrieve the metadata for a stored procedure when a temp table is used in it.
For the sake of simplicity, here is a simple example.
CREATE PROCEDURE dbo.Test
@Seed INT = 0
AS
BEGIN
CREATE TABLE #MyTemp (
ID INT NOT NULL
);
INSERT INTO #MyTemp (ID)
VALUES
(@Seed + 1)
, (@Seed + 2)
, (@Seed + 3)
;
SELECT
ID
FROM
#MyTemp
END
Execution this SP will return one result set, in which we have one column (ID) and three records.
EXEC dbo.Test
@Seed = 1;
Result is:
ID
-----------
2
3
4
However, trying to get the metadata will fail:
EXEC sys.sp_describe_first_result_set @tsql = N'EXEC dbo.Test @Seed = 1;';
Result is:
Msg 11526, Level 16, State 1, Procedure sp_describe_first_result_set, Line 1 [Batch Start Line 24]
The metadata could not be determined because statement 'INSERT INTO #MyTemp (ID)
VALUES
(@Seed + 1)
, (@Seed + 2)
, (@Seed + 3)' in procedure 'Test' uses a temp table.
(Which is expected, since this is a known limitation of sp_describe_first_result_set)
The problem is, when our third-party app encounters this error, it executes the SP twice (first to analyze the result set and create a temp table, then to do an INSERT .. EXEC to load the data into the temp table it created).
When the metadata is available, it will get the metadata using sys.sp_describe_first_result_set and use that info to create it's temp table.
Since we don't have any SQL Servers below version 2012, I could use the WITH RESULT SETS clause, however it is not possible in the said app to configure it, or to manually provide metadata info.
How to make the metadata available for applications using this SP?
I am providing two solutions in my answer, but I am curious if there is one what I don't know about.
回答1:
My current solution is to create a wrapper stored procedure, which executes the existing one, passing through all the parameters, but defining the metadata of the result sets.
To continue the example in the question:
EXEC sp_rename 'dbo.Test', 'Test_Logic', 'OBJECT';
GO
CREATE PROCEDURE dbo.Test
@Seed INT = 0
AS
BEGIN
EXEC dbo.Test_Logic
@Seed = @Seed
WITH RESULT SETS (
(
ID INT
)
)
;
END
Now, if I try to get the metadata, I can get it:
EXEC sys.sp_describe_first_result_set @tsql = N'EXEC dbo.Test @Seed = 1;';
And the result is:
is_hidden column_ordinal name is_nullable system_type_id system_type_name max_length precision scale collation_name user_type_id user_type_database user_type_schema user_type_name assembly_qualified_type_name xml_collection_id xml_collection_database xml_collection_schema xml_collection_name is_xml_document is_case_sensitive is_fixed_length_clr_type source_server source_database source_schema source_table source_column is_identity_column is_part_of_unique_key is_updateable is_computed_column is_sparse_column_set ordinal_in_order_by_list order_by_is_descending order_by_list_length tds_type_id tds_length tds_collation_id tds_collation_sort_id
--------- -------------- -------------------------------------------------------------------------------------------------------------------------------- ----------- -------------- -------------------------------------------------------------------------------------------------------------------------------- ---------- --------- ----- -------------------------------------------------------------------------------------------------------------------------------- ------------ -------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------- ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ----------------- -------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------- --------------- ----------------- ------------------------ -------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------- ------------------ --------------------- ------------- ------------------ -------------------- ------------------------ ---------------------- -------------------- ----------- ----------- ---------------- ---------------------
0 1 ID 1 56 int 4 10 0 NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL 0 0 0 NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL 38 4 NULL NULL
(Which looks awful here, but it works)
The downside is that now I have to maintain two SPs and there is a subsequent SP execution.
Alternative solution
An alternative solution is to use dynamic sql and sys.sp_executesql to run it with the WITH RESULT SETS clause.
The main downside of this is, that SQL Server and VisualStudio (database projects) cannot track dependencies in dynamic SQLs.
This would look like something like this:
ALTER PROCEDURE dbo.Test
@Seed INT = 0
AS
BEGIN
CREATE TABLE #MyTemp (
ID INT NOT NULL
);
INSERT INTO #MyTemp (ID)
VALUES
(@Seed + 1)
, (@Seed + 2)
, (@Seed + 3)
;
DECLARE @STMT NVARCHAR(MAX) = N'
SELECT
ID
FROM
#MyTemp
;';
EXEC sys.sp_executesql
@stmt = @STMT
WITH RESULT SETS (
(
ID INT
)
)
END
Of course, the more complex the SP, the harder to maintain the dynamic SQL in my opinion, so I prefer the first solution.
来源:https://stackoverflow.com/questions/47926745/sql-server-describing-first-result-set-fails-when-temp-table-is-used-sp-describ