Je travaille avec des procédures stockées dans SQL Server 2008 et j'ai appris que je dois INSERT INTO
une table temporaire qui a été prédéfinie afin de travailler avec les données. C'est bien, sauf comment savoir comment définir ma table temporaire, si ce n'est pas moi qui ai écrit la procédure stockée autre que la liste de sa définition et la lecture du code?
Par exemple, à quoi ressemblerait ma table temporaire pour "EXEC sp_stored_procedure"? Il s'agit d'une simple procédure stockée, et je pourrais probablement deviner les types de données, mais il semble qu'il doit y avoir un moyen de simplement lire le type et la longueur des colonnes renvoyées par l'exécution de la procédure.
Supposons donc que vous ayez une procédure stockée dans tempdb:
USE tempdb;
GO
CREATE PROCEDURE dbo.my_procedure
AS
BEGIN
SET NOCOUNT ON;
SELECT foo = 1, bar = 'tooth';
END
GO
Il existe un moyen assez compliqué de déterminer les métadonnées que la procédure stockée générera. Il y a plusieurs mises en garde, y compris la procédure ne peut produire qu'un seul jeu de résultats, et qu'une meilleure estimation sera faite sur le type de données si elle ne peut pas être déterminée avec précision. Il nécessite l'utilisation de OPENQUERY
et d'un serveur lié en boucle avec le 'DATA ACCESS'
propriété définie sur true. Vous pouvez vérifier sys.servers pour voir si vous avez déjà un serveur valide, mais créons simplement un manuellement appelé loopback
:
EXEC master..sp_addlinkedserver
@server = 'loopback',
@srvproduct = '',
@provider = 'SQLNCLI',
@datasrc = @@SERVERNAME;
EXEC master..sp_serveroption
@server = 'loopback',
@optname = 'DATA ACCESS',
@optvalue = 'TRUE';
Maintenant que vous pouvez l'interroger en tant que serveur lié, vous pouvez utiliser le résultat de toute requête (y compris un appel de procédure stockée) en tant que SELECT
normal. Vous pouvez donc le faire (notez que le préfixe de la base de données est important, sinon vous obtiendrez les erreurs 11529 et 2812):
SELECT * FROM OPENQUERY(loopback, 'EXEC tempdb.dbo.my_procedure;');
Si nous pouvons effectuer un SELECT *
, nous pouvons également effectuer un SELECT * INTO
:
SELECT * INTO #tmp FROM OPENQUERY(loopback, 'EXEC tempdb.dbo.my_procedure;');
Et une fois que cette table #tmp existe, nous pouvons déterminer les métadonnées en disant (en supposant que SQL Server 2005 ou supérieur):
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');
(Si vous utilisez SQL Server 2000, vous pouvez faire quelque chose de similaire avec syscolumns, mais je n'ai pas d'instance 2000 à portée de main pour valider une requête équivalente.)
Résultats:
name type max_length precision scale
--------- ------- ---------- --------- -----
foo int 4 10 0
bar varchar 5 0 0
À Denali, ce sera beaucoup, beaucoup, beaucoup plus facile. Encore une fois, il y a toujours une limitation du premier jeu de résultats, mais vous n'avez pas besoin de configurer un serveur lié et de sauter à travers tous ces cercles. Vous pouvez simplement dire:
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);
Résultats:
name system_type_name
--------- ----------------
foo int
bar varchar(5)
Jusqu'à Denali, je suggère qu'il serait plus facile de simplement retrousser vos manches et de déterminer vous-même les types de données. Non seulement parce qu'il est fastidieux de suivre les étapes ci-dessus, mais également parce que vous êtes beaucoup plus susceptible de faire une supposition correcte (ou au moins plus précise) que le moteur, car le type de données que le moteur suppose sera basé sur l'exécution sortie, sans aucune connaissance externe du domaine des valeurs possibles. Ce facteur restera également vrai à Denali, alors n'ayez pas l'impression que les nouvelles fonctionnalités de découverte de métadonnées sont un atout, elles rendent simplement ce qui précède un peu moins fastidieux.
Oh et pour d'autres problèmes potentiels avec OPENQUERY
, voir l'article d'Erland Sommarskog ici:
Une manière moins sophistiquée (qui pourrait être suffisante dans certains cas): éditez votre SP d'origine, après le SELECT final et avant la clause FROM ajoutez INSERT INTO tmpTable pour enregistrer le résultat SP dans tmpTable).
Exécutez le SP modifié, de préférence avec des paramètres significatifs afin d'obtenir des données réelles. Restaurez le code d'origine de la procédure.
Vous pouvez maintenant obtenir le script de tmpTable à partir de SQL Server Management Studio ou interroger sys.columns pour obtenir les descriptions des champs.
Il semble que dans SQL 2012, il y ait un nouveau SP pour aider à cela.
exec sp_describe_first_result_set N'PROC_NAME'
Voici du code que j'ai écrit. L'idée est (comme quelqu'un d'autre l'a dit) est d'obtenir le code SP, de le modifier et de l'exécuter. Cependant, mon code ne change pas le SP d'origine.
Première étape, obtenez la définition du SP, supprimez la partie 'Créer' et supprimez le 'AS' après la déclaration des paramètres, s'il existe.
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, '')
(Notez la création d'un nouveau nom de table basé sur un identifiant unique) Recherchez maintenant le dernier mot 'From' dans le code en supposant que c'est le code qui fait la sélection qui renvoie le jeu de résultats.
Select @SQLReverse = REVERSE(@SQL)
Select @StartPos = CHARINDEX('MORF', UPPER(@SQLReverse), 1)
Modifiez le code pour sélectionner l'ensemble de résultats dans une table (la table basée sur l'identifiant unique)
Select @StartPos = LEN(@SQL) - @StartPos - 2
Select @SQL = STUFF(@SQL, @StartPos, 5, ' INTO ' + @TableName + ' FROM ')
EXEC (@SQL)
L'ensemble de résultats est maintenant dans une table, peu importe si la table est vide!
Permet d'obtenir la structure de la table
Select * from INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = @TableName
Vous pouvez maintenant faire votre magie avec ce
N'oubliez pas de laisser tomber cette table unique
Select @SQL = 'drop table ' + @TableName
Exec (@SQL)
J'espère que cela t'aides!