web-dev-qa-db-fra.com

Création dynamique d'une table temporaire à l'aide d'une condition

J'essaie de créer une table temporaire de manière dynamique en utilisant le code ci-dessous, et je sais que nous ne pouvons pas avoir deux instructions create pour la même table. Existe-t-il une solution de contournement pour le mettre de manière conditionnelle comme le code ci-dessous.

CREATE PROC test @var1 CHAR(1)
as
BEGIN
IF(@var1 = X)
BEGIN 
SELECT * INTO #result
FROM TABLE1
END
IF(@var1 = Y)
BEGIN 
SELECT * INTO #result
FROM TABLE2
END
IF(@var1 = Z)
BEGIN 
SELECT * INTO #result
FROM TABLE3
END

SELECT * FROM #result r
END

L'objectif est d'avoir enfin une table nommée #result avec des colonnes basées sur la valeur de la variable (@ var1)

Edit 1: Puisque c'est un bon candidat pour l'utilisation de SQL dynamique, comme indiqué ci-dessous, mais je ne pourrai pas utiliser le #result table en dehors de la portée SQL de la dynamique, ce dont j'ai besoin.

CREATE PROC test @var1 CHAR(1)
as
BEGIN
-- USING  dynamic sql
DECLARE @sql VARCHAR(MAX)
IF(@var1 = 'X')
BEGIN 
SET @sql ='SELECT t.[name],t.[object_id],t.[principal_id] INTO #result
FROM sys.tables t'
END
IF(@var1 = 'Y')
BEGIN 
SET @sql ='SELECT t.[name],t.[object_id],t.[principal_id],t.[schema_id] INTO #result
FROM sys.tables t'
END
IF(@var1 = 'Z')
BEGIN 
SET @sql ='SELECT t.[name],t.[object_id],t.[principal_id],t.[schema_id],t.[parent_object_id] INTO #result
FROM sys.tables t'
END

EXEC (@sql)
SELECT * FROM #result r
END
6
Biju jose

Prise en charge?

Je sais que c'est étiqueté 2008R2. Comme cela n'est officiellement pas pris en charge, une mise à niveau est peut-être dans votre avenir. Si vous vous retrouvez sur une version de SQL Server> 2012, vous pouvez utiliser du code comme celui-ci:

CREATE OR ALTER PROCEDURE dbo.dynamic_temp ( @TableName NVARCHAR(128))
AS
BEGIN
    SET NOCOUNT ON;

    CREATE TABLE #t ( Id INT );
    DECLARE @sql NVARCHAR(MAX) = N'';

    IF @TableName = N'Users'
        BEGIN
            SET @sql = @sql + N'SELECT TOP 10 * FROM dbo.Users AS u WHERE u.Reputation > @i';
        END;

    IF @TableName = N'Posts'
        BEGIN
            SET @sql = @sql + N'SELECT TOP 10 * FROM dbo.Posts AS p WHERE p.Score > @i';
        END;

    SELECT   column_ordinal, name, system_type_name
    INTO     #dfr
    FROM     sys.dm_exec_describe_first_result_set(@sql, NULL, 0)
    ORDER BY column_ordinal;

    DECLARE @alter NVARCHAR(MAX) = N'ALTER TABLE #t ADD ';

    SET @alter += STUFF((   SELECT   NCHAR(10) + d.name + N' ' + d.system_type_name + N','
                            FROM     #dfr AS d
                            WHERE    d.name <> N'Id'
                            ORDER BY d.column_ordinal
                            FOR XML PATH(N''), TYPE ).value(N'.[1]', N'NVARCHAR(4000)'), 1, 1, N'');

    SET @alter = LEFT(@alter, LEN(@alter) - 1);

    EXEC ( @alter );

    INSERT #t
    EXEC sys.sp_executesql @sql, N'@i INT', @i = 10000;

    SELECT *
    FROM   #t;

END;
GO

L'idée est d'utiliser dm_exec_describe_first_result_set pour déterminer quelles colonnes votre résultat produit, ainsi que leurs types de données. Nous pouvons l'utiliser pour générer une instruction ALTER TABLE dynamique pour ajouter ces colonnes à une table #temp de base créée en dehors de la portée du SQL dynamique.

Cela rend l'insertion et la sélection de données assez faciles.

Je bloguais à ce sujet quand je me suis souvenu de cette question. Encore une fois, désolé, rien n'est aussi facile pour <2012.

5
Erik Darling

Vous pensez mal au problème.

Vous pouvez utiliser du SQL dynamique pour ce faire - en définissant votre forme de résultat dans l'instruction, mais en créant la table temporaire à l'extérieur de celle-ci. Ainsi:

CREATE PROC test @var1 CHAR(1)
as
BEGIN
-- USING  dynamic sql
DECLARE @sql VARCHAR(MAX)
IF(@var1 = 'X')
BEGIN 
SET @sql ='SELECT t.[name]
                 ,t.[object_id]
                 ,t.[principal_id]
             FROM sys.tables t'
END
IF(@var1 = 'Y')
BEGIN 
SET @sql ='SELECT t.[name]
                 ,t.[object_id]
                 ,t.[principal_id]
                 ,t.[schema_id]
             FROM sys.tables t'
END
IF(@var1 = 'Z')
BEGIN 
SET @sql ='SELECT t.[name]
                 ,t.[object_id]
                 ,t.[principal_id]
                 ,t.[schema_id]
                 ,t.[parent_object_id]
             FROM sys.tables t'
END

INSERT INTO #result
EXEC (@sql);

SELECT * FROM #result r
END

Cela fonctionne car vous pouvez utiliser les résultats générés par l'instruction EXECUTE comme entrée de l'instruction INSERT, cela vous permet ensuite de créer votre table temporaire en dehors du SQL dynamique. Ensuite, vous pouvez y accéder au besoin.

Il existe des limitations, telles que le SQL dynamique lui-même ne peut pas contenir un INSERT EXEC instruction car ils ne peuvent pas être imbriqués. Pour plus d'informations, voir INSERT (Transact-SQL) , en particulier la section sur execute_statement.

[~ # ~] modifier [~ # ~]

Sur la base des commentaires et des tests ci-dessus, il semble que cela ne fonctionne plus (bien que je me souvienne de l'avoir fait il y a quelque temps - peut-être que je vieillis!). La seule autre approche à laquelle je peux penser pour que cela fonctionne est d'utiliser OPENROWSET . En général, je ne le recommanderais pas, mais il semble que ce soit le seul moyen de répondre à vos besoins.

Vous devrez vous assurer que votre serveur est correctement configuré au préalable:

EXEC sp_configure 'Show Advanced Options', 1  
GO
RECONFIGURE 
GO
EXEC sp_configure 'Ad Hoc Distributed Queries', 1
GO
RECONFIGURE
GO

Ensuite, vous pouvez utiliser ce qui suit pour créer la table temporaire:

SELECT * 
  INTO #results 
  FROM OPENROWSET(
         'SQLNCLI', 
         'Server=(local);Trusted_Connection=yes;', -- replace with actual server/instance
         'SELECT * FROM sys.tables'); -- replace with query

SELECT * FROM #results;

C'est extrêmement hacky et je ne le ferais pas personnellement, je préférerais toujours pousser autant que possible dans l'instruction SQL dynamique elle-même. Mais si vous ne pouvez vraiment pas le faire autrement, alors ce qui précède fonctionnera pour vous.

MODIFICATION SUPPLÉMENTAIRE

Ce qui précède ne fonctionnera pas lorsqu'il est placé dans le corps des instructions IF car la deuxième instruction vous indiquera que la table a déjà été définie.

Sur la base de toutes les informations que vous avez fournies, à moins que vous ne puissiez utiliser des tables Global Temp, poussez toute la logique dans le SQL dynamique ou créez des tables intermédiaires comme avec le CREATE TABLE déclaration. Alors ce que vous voulez faire ne peut pas être fait.

2
Mr.Brownstone

@biju, que diriez-vous de mettre tout le code en sql dynamique et de mettre également le select. Il n'y aura alors aucun problème de portée de table.

0
Vinod Narwal

Utilisez une table temporaire globale au lieu d'une table de portée session (table ## au lieu de #table). Cela vous permettra de créer la table de manière conditionnelle à l'aide de SQL dynamique, puis d'y accéder dans la session d'origine.

DECLARE @var1 CHAR(1) = 'X'

IF(@var1 = 'X')
BEGIN 
    EXEC ('SELECT t.[name],t.[object_id],t.[principal_id] INTO ##results
    FROM sys.tables t')
END
ELSE IF(@var1 = 'Y')
BEGIN 
    EXEC ('SELECT t.[name],t.[object_id],t.[principal_id],t.[schema_id] INTO ##results
    FROM sys.tables t')
END
IF(@var1 = 'Z')
BEGIN 
    EXEC ('SELECT t.[name],t.[object_id],t.[principal_id],t.[schema_id],t.[parent_object_id] INTO ##results
    FROM sys.tables t')
END

SELECT * FROM ##results

DROP TABLE ##results

Plus d'informations sur Tables temporaires .

0
HandyD

Option 1

Il semble que vos types de retour pour chacun de vos résultats soient suffisamment liés pour que vous puissiez tout ranger dans le même tableau avec quelques instructions CASE, si vous êtes d'accord avec cela.

CREATE TABLE #results
(
    [name] SYSNAME,
    [object_id] INT,
    [principal_id] INT,
    [schema_id] INT,
    [parent_object_id] INT
)

INSERT INTO #results
SELECT t.[name], 
    t.[object_id], 
    t.[principal_id], 
    CASE 
        WHEN @var1 = 'X' THEN null 
        WHEN @var1 = 'Y' OR @var1 = 'Z' THEN t.[schema_id] 
    END AS [schema_id], 
    CASE 
        WHEN @var1 = 'X' OR @var1 = 'Y' THEN null 
        WHEN @var1 = 'Z' THEN t.[parent_object_id] 
    END AS [parent_object_id]
FROM sys.tables AS t

SELECT r.[name], r.[object_id], r.[principal_id], r.[schema_id], r.[parent_object_id]
FROM #results as r

DROP TABLE #results

Si vous êtes d'accord pour toujours renvoyer le même jeu de résultats avec deux colonnes nullables à la fin, vous êtes bon à ce stade sinon si vous devez retourner le type de retour corrélatif en fonction de votre variable @ var1, alors vous pouvez simplement implémenter votre la logique vérifie votre instruction SELECT à la fin comme ceci:

IF (@var1 = 'X')
BEGIN
    SELECT r.[name], r.[object_id], r.[principal_id]
    FROM #results as r
END

IF (@var1 = 'Y')
BEGIN
    SELECT r.[name], r.[object_id], r.[principal_id], r.[schema_id]
    FROM #results as r
END

IF (@var1 = 'Z')
BEGIN
    SELECT r.[name], r.[object_id], r.[principal_id], r.[schema_id], r.[parent_object_id] 
    FROM #results as r
END

Option 2

Si vous préférez continuer via une solution Dynamic SQL, vous pouvez également résoudre le problème de concurrence avec une table temporaire globale en générant un nom unique pour votre table temporaire globale en procédant comme suit:

DECLARE @UniqueTableId AS VARCHAR(50) = (SELECT CAST(NEWID() AS VARCHAR(50)))

EXEC ('SELECT t.[name],t.[object_id],t.[principal_id] INTO ##results_' + @UniqueTableId + '
    FROM sys.tables t')
0
J.D.