web-dev-qa-db-fra.com

T-SQL SQL dynamique et tables temporaires

Il semble que #temptables créé à l'aide de SQL dynamique via la méthode de chaîne EXECUTE ait une étendue différente et ne puisse pas être référencé par des instructions SQL "fixes" dans la même procédure stockée. Cependant, je peux référencer une table temporaire créée par une instruction SQL dynamique dans un SQL dynamique de sous-séquence, mais il semble qu'une procédure stockée ne renvoie pas le résultat de la requête à un client appelant, à moins que le SQL ne soit corrigé.

Un scénario simple à 2 tables: j'ai 2 tables. Appelons-les des commandes et des articles. Order a une clé primaire OrderId et Items une clé primaire ItemId. Items.OrderId est la clé étrangère permettant d'identifier la commande parent. Une commande peut avoir 1 à n articles.

Je souhaite être en mesure de fournir à l'utilisateur une interface de type "constructeur de requêtes" très flexible lui permettant de sélectionner les éléments qu'il souhaite voir. Les critères de filtrage peuvent être basés sur des champs de la table Items et/ou de la table Order. Si un élément remplit la condition de filtre, y compris une condition relative à la commande parent, le cas échéant, il doit être renvoyé dans la requête ainsi que la commande parent.

Je suppose qu'en général, la plupart des gens construisent une jointure entre la table Item et les tables Order parent. Je voudrais plutôt effectuer 2 requêtes distinctes. Un pour renvoyer tous les articles éligibles et l'autre pour renvoyer tous les ordres parents distincts. La raison est double et vous pouvez être d'accord ou pas.

La première raison est que je dois interroger toutes les colonnes de la table Order et si je faisais une seule requête pour joindre la table Orders à la table Items, je répèterais plusieurs fois les informations de la commande. Comme il y a généralement un grand nombre d'articles par commande, j'aimerais éviter cela, car cela entraînerait un transfert beaucoup plus important de données vers un client lourd. Comme indiqué, je souhaite plutôt renvoyer les deux tables individuellement dans un jeu de données et les utiliser pour renseigner les objets client Order et Child Items personnalisés. (Je ne connais pas encore assez LINQ ou Entity Framework. Je construis mes objets à la main). La deuxième raison pour laquelle je souhaite renvoyer deux tables au lieu d'une est parce que j'ai déjà une autre procédure qui renvoie tous les articles pour un OrderId donné avec l'ordre parent et je voudrais utiliser la même approche à deux tables afin que je pourrait réutiliser le code client pour renseigner mes objets Order et Client personnalisés à partir des 2 tableaux de données renvoyés.

Ce que j'espérais faire était le suivant:

Construisez une chaîne SQL dynamique sur le client qui relie la table orders à la table Items et filtre en fonction de chaque table, comme spécifié par le filtre personnalisé créé sur l'application Winform Fat-Client. La construction SQL sur le client aurait ressemblé à ceci:

TempSQL = "

    INSERT INTO #ItemsToQuery
       OrderId, ItemsId
    FROM
       Orders, Items 
    WHERE
       Orders.OrderID = Items.OrderId AND
       /* Some unpredictable Order filters go here */
      AND
       /* Some unpredictable Items filters go here */
    "

Ensuite, j'appellerais une procédure stockée,

CREATE PROCEDURE GetItemsAndOrders(@tempSql as text)
   Execute (@tempSQL) --to create the #ItemsToQuery table

SELECT * FROM Items WHERE Items.ItemId IN (SELECT ItemId FROM #ItemsToQuery)

SELECT * FROM Orders WHERE Orders.OrderId IN (SELECT DISTINCT OrderId FROM #ItemsToQuery)

Le problème avec cette approche est que la table #ItemsToQuery, puisqu'elle a été créée par SQL dynamique, est inaccessible à partir des deux SQL statiques suivants. Si je modifie les SQL statiques en dynamiques, aucun résultat n'est renvoyé au client lourd.

3 environ me viennent à l’esprit mais j’en cherche un meilleur:

1) Le premier SQL pourrait être effectué en exécutant le SQL construit dynamiquement à partir du client. Les résultats pourraient ensuite être transmis sous forme de tableau à une version modifiée de la procédure stockée ci-dessus. Je suis habitué à transmettre des données de table au format XML. Si je le faisais, le processus stocké pourrait alors insérer les données dans une table temporaire à l'aide d'un code SQL statique qui, du fait qu'il avait été créé par un code SQL dynamique, pouvait ensuite être interrogé sans problème. (Je pourrais également étudier la possibilité de transmettre le nouveau paramètre de type Table au lieu de XML.) Toutefois, j'aimerais éviter de transmettre des listes potentiellement volumineuses à une procédure stockée.

2) Je pourrais effectuer toutes les requêtes du client.

Le premier serait quelque chose comme ceci:

SELECT Items.* FROM Orders, Items WHERE Order.OrderId = Items.OrderId AND (dynamic filter)
SELECT Orders.* FROM Orders, Items WHERE Order.OrderId = Items.OrderId AND (dynamic filter)

Cela me permet néanmoins de réutiliser le code de population d'objets côté client car les ordres et les articles continuent d'être renvoyés dans deux tables différentes.

J'ai le sentiment que je pourrais avoir quelques options en utilisant un type de données Table dans mon processus stocké, mais c'est aussi nouveau pour moi et j'apprécierais un peu de pouvoir le nourrir à la cuillère.

Si vous avez même parcouru si loin dans ce que j’écris, je suis surpris, mais si c’est le cas, je ne saurais apprécier vos idées sur la meilleure façon d’y parvenir.

14
ChadD

Vous devez d’abord créer votre table, puis elle sera disponible dans le SQL dynamique.

Cela marche:

CREATE TABLE #temp3 (id INT)
EXEC ('insert #temp3 values(1)')

SELECT *
FROM #temp3

Cela ne fonctionnera pas:

EXEC (
        'create table #temp2 (id int)
         insert #temp2 values(1)'
        )

SELECT *
FROM #temp2

En d'autres termes:

  1. Créer une table temporaire
  2. Exécuter proc
  3. Sélectionner dans la table temporaire

Voici un exemple complet:

CREATE PROC prTest2 @var VARCHAR(100)
AS
EXEC (@var)
GO

CREATE TABLE #temp (id INT)

EXEC prTest2 'insert #temp values(1)'

SELECT *
FROM #temp
20
SQLMenace

1st Method - Insérez plusieurs instructions dans le même appel SQL dynamique:

DECLARE @DynamicQuery NVARCHAR(MAX)

SET @DynamicQuery = 'Select * into #temp from (select * from tablename) alias 
select * from #temp
drop table #temp'

EXEC sp_executesql @DynamicQuery

2ème méthode - Utiliser la table de temp globale:
(Attention, vous devez prendre grand soin de la variable globale.)

IF OBJECT_ID('tempdb..##temp2') IS NULL
BEGIN
    EXEC (
            'create table ##temp2 (id int)
             insert ##temp2 values(1)'
            )

    SELECT *
    FROM ##temp2
END

N'oubliez pas de supprimer l'objet ## temp2 manuellement une fois que vous avez terminé:

IF (OBJECT_ID('tempdb..##temp2') IS NOT NULL)
BEGIN
     DROP Table ##temp2
END

Remarque: N'utilisez pas cette méthode 2 si vous ne connaissez pas la structure complète de la base de données.

4
Sid

Je vous suggère fortement de lire http://www.sommarskog.se/arrays-in-sql-2005.html

Personnellement, j’aime l’approche consistant à passer une liste de texte délimitée par des virgules, puis à l’analyser avec du texte à la fonction de tableau et à la rejoindre. L'approche de la table temporaire peut fonctionner si vous la créez d'abord dans la connexion. Mais c'est un peu plus salissant.

2
Sam Saffron

J'ai eu le même problème que @Muflix mentionné. Lorsque vous ne connaissez pas les colonnes renvoyées ou qu'elles sont générées de manière dynamique, j'ai créé un tableau global avec un identifiant unique, puis supprimé celui-ci lorsque j'en ai terminé. Cela ressemble à ce qui est affiché. au dessous de:

DECLARE @DynamicSQL NVARCHAR(MAX)
DECLARE @DynamicTable VARCHAR(255) = 'DynamicTempTable_' + CONVERT(VARCHAR(36), NEWID())
DECLARE @DynamicColumns NVARCHAR(MAX)

--Get "@DynamicColumns", example: SET @DynamicColumns = '[Column1], [Column2]'

SET @DynamicSQL = 'SELECT ' + @DynamicColumns + ' INTO [##' + @DynamicTable + ']' + 
     ' FROM [dbo].[TableXYZ]'

EXEC sp_executesql @DynamicSQL

SET @DynamicSQL = 'IF OBJECT_ID(''tempdb..##' + @DynamicTable + ''' , ''U'') IS NOT NULL ' + 
    ' BEGIN DROP TABLE [##' + @DynamicTable + '] END'

EXEC sp_executesql @DynamicSQL

Certainement pas la meilleure solution, mais cela semble fonctionner pour moi.

1
David Rogers

Les ensembles de résultats issus du SQL dynamique sont renvoyés au client. J'ai souvent fait ça.

Vous avez raison en ce qui concerne le partage de données via des tables temporaires et des variables, entre le code SQL et le code SQL dynamique qu’il génère.

Je pense qu'en essayant de faire fonctionner votre table temporaire, certaines choses sont confuses, car vous pouvez certainement obtenir les données d'un SP qui exécute du code SQL dynamique:

USE SandBox
GO

CREATE PROCEDURE usp_DynTest(@table_type AS VARCHAR(255))
AS 
BEGIN
    DECLARE @sql AS VARCHAR(MAX) = 'SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = ''' + @table_type + ''''
    EXEC (@sql)
END
GO

EXEC usp_DynTest 'BASE TABLE'
GO

EXEC usp_DynTest 'VIEW'
GO

DROP PROCEDURE usp_DynTest
GO

Également:

USE SandBox
GO

CREATE PROCEDURE usp_DynTest(@table_type AS VARCHAR(255))
AS 
BEGIN
    DECLARE @sql AS VARCHAR(MAX) = 'SELECT * INTO #temp FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = ''' + @table_type + '''; SELECT * FROM #temp;'
    EXEC (@sql)
END
GO

EXEC usp_DynTest 'BASE TABLE'
GO

EXEC usp_DynTest 'VIEW'
GO

DROP PROCEDURE usp_DynTest
GO
0
Cade Roux