Получить определение столбца для набора результатов хранимой процедуры - PullRequest
34 голосов
/ 10 сентября 2011

Я работаю с хранимыми процедурами в SQL Server 2008 и узнал, что мне нужно INSERT INTO временная таблица, которая была предварительно определена для работы с данными. Это нормально, кроме как выяснить, как определить мою временную таблицу, если я не тот, кто написал хранимую процедуру, кроме перечисления ее определения и чтения кода?

Например, как будет выглядеть моя временная таблица для EXEC sp_stored_procedure? Это простая хранимая процедура, и я, вероятно, мог бы догадаться о типах данных, но, похоже, должен быть способ просто прочитать тип и длину столбцов, возвращаемых при выполнении процедуры.

Ответы [ 5 ]

52 голосов
/ 10 сентября 2011

Допустим, у вас есть хранимая процедура в базе данных tempdb:

USE tempdb;
GO

CREATE PROCEDURE dbo.my_procedure
AS
BEGIN
    SET NOCOUNT ON;

    SELECT foo = 1, bar = 'tooth';
END
GO

Существует довольно запутанный способ определения метаданных, которые выдает хранимая процедура. Существует несколько предостережений, в том числе процедура может выводить только один результирующий набор, и о том, что тип данных будет лучше всего определен, если он не может быть точно определен. Это требует использования OPENQUERY и сервера с обратной связью со свойством 'DATA ACCESS', установленным в true. Вы можете проверить sys.servers, чтобы убедиться, что у вас уже есть действительный сервер, но давайте просто создадим один из них, называемый loopback:

EXEC master..sp_addlinkedserver 
    @server = 'loopback',  
    @srvproduct = '',
    @provider = 'SQLNCLI',
    @datasrc = @@SERVERNAME;

EXEC master..sp_serveroption 
    @server = 'loopback', 
    @optname = 'DATA ACCESS',
    @optvalue = 'TRUE';

Теперь, когда вы можете запросить это как связанный сервер, вы можете использовать результат любого запроса (включая вызов хранимой процедуры) как обычный SELECT. Таким образом, вы можете сделать это (обратите внимание, что префикс базы данных важен , в противном случае вы получите ошибки 11529 и 2812):

SELECT * FROM OPENQUERY(loopback, 'EXEC tempdb.dbo.my_procedure;');

Если мы можем выполнить SELECT *, мы также можем выполнить SELECT * INTO:

SELECT * INTO #tmp FROM OPENQUERY(loopback, 'EXEC tempdb.dbo.my_procedure;');

И как только эта таблица #tmp существует, мы можем определить метаданные, сказав (при условии, что SQL Server 2005 или выше):

SELECT c.name, [type] = t.name, c.max_length, c.[precision], c.scale
  FROM sys.columns AS c
  INNER JOIN sys.types AS t
  ON c.system_type_id = t.system_type_id
  AND c.user_type_id = t.user_type_id
  WHERE c.[object_id] = OBJECT_ID('tempdb..#tmp');

(Если вы используете SQL Server 2000, вы можете сделать что-то подобное с syscolumns, но у меня нет экземпляра 2000, удобного для проверки эквивалентного запроса.)

Результаты:

name      type    max_length precision scale
--------- ------- ---------- --------- -----
foo       int              4        10     0
bar       varchar          5         0     0

В Денали это будет намного, намного, намного проще. Опять же, существует ограничение первого набора результатов, но вам не нужно настраивать связанный сервер и перепрыгивать через все эти циклы. Вы можете просто сказать:

DECLARE @sql NVARCHAR(MAX) = N'EXEC tempdb.dbo.my_procedure;';

SELECT name, system_type_name
    FROM sys.dm_exec_describe_first_result_set(@sql, NULL, 1);

Результаты:

name      system_type_name
--------- ----------------
foo       int             
bar       varchar(5)      

До Денали, я полагаю, было бы проще просто засучить рукава и выяснить типы данных самостоятельно. Не только потому, что выполнять вышеуказанные шаги утомительно, но и потому, что вы с гораздо большей вероятностью сделаете правильное (или, по крайней мере, более точное) предположение, чем будет делать движок, так как тип данных предполагает, что движок будет основан на времени выполнения. вывод, без каких-либо внешних знаний о области возможных значений. Этот фактор останется верным и в Denali, так что не создавайте впечатление, что новые функции обнаружения метаданных являются конечной целью, они просто делают вышеупомянутое менее утомительным.

Да, и о некоторых других потенциальных ошибках с OPENQUERY, см. Статью Эрланда Соммарскога здесь:

http://www.sommarskog.se/share_data.html#OPENQUERY

7 голосов
/ 04 декабря 2013

Менее изощренный способ (в некоторых случаях этого может быть достаточно): отредактируйте исходный SP, после последнего SELECT и перед предложением FROM добавьте INSERT INTO tmpTable, чтобы сохранить результат SP в tmpTable.

Запустите модифицированный SP, желательно со значимыми параметрами, чтобы получить фактические данные. Восстановите исходный код процедуры.

Теперь вы можете получить скрипт tmpTable из студии управления SQL-сервером или запросить sys.columns для получения описания полей.

6 голосов
/ 07 мая 2017

Похоже, в SQL 2012 появился новый SP, чтобы помочь с этим.

exec sp_describe_first_result_set N'PROC_NAME'

https://docs.microsoft.com/en-us/sql/relational-databases/system-stored-procedures/sp-describe-first-result-set-transact-sql

6 голосов
/ 12 марта 2014

Вот код, который я написал.Идея заключается в том, чтобы (как кто-то еще заявил) получить код SP, изменить его и выполнить.Тем не менее, мой код не меняет исходный SP.

Первый шаг, получить определение SP, убрать часть «Создать» и избавиться от «AS» после объявления параметров, если существует.

Declare @SPName varchar(250)
Set nocount on

Declare @SQL Varchar(max), @SQLReverse Varchar(MAX), @StartPos int, @LastParameterName varchar(250) = '', @TableName varchar(36) = 'A' + REPLACE(CONVERT(varchar(36), NewID()), '-', '')

Select * INTO #Temp from INFORMATION_SCHEMA.PARAMETERS where SPECIFIC_NAME = 'ADMIN_Sync_CompareDataForSync'

if @@ROWCOUNT > 0
    BEGIN
        Select @SQL = REPLACE(ROUTINE_DEFINITION, 'CREATE PROCEDURE [' + ROUTINE_SCHEMA + '].[' + ROUTINE_NAME + ']', 'Declare') 
        from INFORMATION_SCHEMA.ROUTINES 
        where ROUTINE_NAME = @SPName

        Select @LastParameterName = PARAMETER_NAME + ' ' + DATA_TYPE + 
            CASE WHEN CHARACTER_MAXIMUM_LENGTH is not null THEN '(' + 
                CASE WHEN CHARACTER_MAXIMUM_LENGTH = -1 THEN 'MAX' ELSE CONVERT(varchar,CHARACTER_MAXIMUM_LENGTH) END + ')' ELSE '' END 
        from #Temp 
        WHERE ORDINAL_POSITION = 
            (Select MAX(ORDINAL_POSITION) 
            From #Temp)

        Select @StartPos = CHARINDEX(@LastParameterName, REPLACE(@SQL, '  ', ' '), 1) + LEN(@LastParameterName)
    END
else
    Select @SQL = REPLACE(ROUTINE_DEFINITION, 'CREATE PROCEDURE [' + ROUTINE_SCHEMA + '].[' + ROUTINE_NAME + ']', '') from INFORMATION_SCHEMA.ROUTINES where ROUTINE_NAME = @SPName

DROP TABLE #Temp

Select @StartPos = CHARINDEX('AS', UPPER(@SQL), @StartPos)

Select @SQL = STUFF(@SQL, @StartPos, 2, '')

(обратите внимание на создание нового имени таблицы на основе уникального идентификатора). Теперь найдите последнее слово «От» в коде, предполагая, что это код, который выполняет выборку, возвращающую набор результатов..

Select @SQLReverse = REVERSE(@SQL)

Select @StartPos = CHARINDEX('MORF', UPPER(@SQLReverse), 1)

Изменить код для выбора набора результатов в таблицу (таблицу, основанную на уникальном идентификаторе)

Select @StartPos = LEN(@SQL) - @StartPos - 2

Select @SQL = STUFF(@SQL, @StartPos, 5, ' INTO ' + @TableName + ' FROM ')

EXEC (@SQL)

Набор результатов теперь находится в таблице, это не имеет значенияесли таблица пуста!

Позволяет получить структуру таблицы

Select * from INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = @TableName

Теперь вы можете творить чудеса с этим

Не забудьте сбросить этот уникальныйТаблица

Select @SQL = 'drop table ' + @TableName

Exec (@SQL)

Надеюсь, это поможет!

0 голосов
/ 19 июля 2016

Если вы работаете в среде с ограниченными правами, где такие вещи, как сервер с обратной связью, кажутся чёрной магией и определенно «ни за что!», Но у вас есть несколько прав на схему, и для обработки есть только несколько хранимых процедур. очень простое решение.

Вы можете использовать очень полезный синтаксис SELECT INTO , который создаст новую таблицу с набором результатов запроса.

Допустим, ваша процедура содержит следующий запрос Select:

SELECT x, y, z
FROM MyTable t INNER JOIN Table2 t2 ON t.id = t2.id...

Вместо этого замените его на:

SELECT x, y, z
INTO MyOutputTable
FROM MyTable t INNER JOIN Table2 t2 ON t.id = t2.id...

Когда вы выполните его, он создаст новую таблицу MyOutputTable с результатами, возвращаемыми запросом.

Вам просто нужно щелкнуть правой кнопкой мыши по ее имени, чтобы получить определение таблицы.

Вот и все!

SELECT INTO требуется только возможность создавать новые таблицы, а также работать с временными таблицами (SELECT ... INTO #MyTempTable), но получить определение может быть сложнее.

Однако, конечно, если вам нужно получить выходное определение тысячи SP, это не самый быстрый способ:)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...