web-dev-qa-db-ja.com

条件を使用して一時テーブルを動的に作成する

以下のコードを使用して一時テーブルを動的に作成しようとしていますが、同じテーブルに対して2つのcreateステートメントを作成することはできません。これを以下のコードのような条件付きの方法で配置するための回避策はありますか?.

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

目的は、変数(@ var1)の値に基づいて列を含む#resultという名前のテーブルを最終的に作成することです

編集1:以下に示すように、これは動的SQLの使用に適した候補であるため、動的SQLスコープの外では#resultテーブルを使用できません。私は欲しい。

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

サポートされていますか?

これは2008R2のタグが付いていることを知っています。正式にはサポートされていないので、おそらくアップグレードはあなたの将来にあります。 SQL Serverのバージョン> 2012になる場合は、次のようなコードを使用できます。

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

アイデアは dm_exec_describe_first_result_set を使用して、結果が生成する列とそのデータ型を決定することです。これを使用して、動的ALTER TABLEステートメントを生成し、動的SQLのスコープ外に作成されたベースの#tempテーブルにこれらの列を追加できます。

これにより、データの挿入と選択が非常に簡単になります。

この質問を思い出したとき、私はこれについてブログを書いていた。繰り返しになりますが、申し訳ありませんが、<2012はそれほど簡単なことではありません。

5
Erik Darling

あなたは問題を間違った方法で考えています。

動的SQLを使用してできますこれを行うには、ステートメント内で結果の形状を定義しますが、その外部に一時テーブルを作成します。そのようです:

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

これは、EXECUTEステートメントによって生成された結果をINSERTステートメントへの入力として使用できるため機能します。これにより、動的SQLの外部で一時テーブルを構築できます。その後、必要に応じてアクセスできます。

動的SQL自体はネストできないため、INSERT EXECステートメントを含めることができないなどの制限があります。詳細については、 INSERT(Transact-SQL) 、特にexecute_statementのセクションを参照してください。

[〜#〜]編集[〜#〜]

上記のフィードバックとテストに基づいて、これは機能しなくなったようです(私はしばらく前にこれを行ったことを覚えていますが、たぶん私は年をとったばかりです!)。これを機能させるために私が考えることができる他の唯一のアプローチは [〜#〜] openrowset [〜#〜] を使用することです。一般的にはお勧めしませんが、要件を満たす唯一の方法のようです。

事前にサーバーが正しく構成されていることを確認する必要があります。

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

次に、以下を使用して一時テーブルを作成できます。

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;

これは非常にハックであり、個人的には行いません。動的SQLステートメント自体にできる限りプッシュすることを常に好みます。しかし、本当に他の方法でそれを行うことができない場合は、上記でうまくいきます。

さらに編集

2番目のステートメントはテーブルがすでに定義されていることを通知するため、上記はIFステートメントの本文内に配置すると機能しません。

提供したすべての情報に基づいて、グローバル一時テーブルを使用できない場合は、すべてのロジックを動的SQLにプッシュするか、CREATE TABLEステートメントなどを使用して中間テーブルを作成します。そうすると、やりたいことができなくなります。

2
Mr.Brownstone

@biju、コード全体を動的SQLに配置し、selectも配置する方法についてはどうでしょうか。その後、テーブルのスコープの問題はありません。

0
Vinod Narwal

セッションスコープのテーブルではなくグローバル一時テーブルを使用します(#tableの代わりに## table)。これにより、動的SQLを使用して条件付きでテーブルを作成し、元のセッションでそれにアクセスできるようになります。

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

一時テーブル に関する詳細情報。

0
HandyD

オプション1

各結果の戻り値の型は密接に関連しているように見えますが、もしそれがうまくいけば、いくつかのCASEステートメントですべてを同じテーブルに詰め込むことができます。

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

常に最後に2つのnull許容列がある同じ結果セットを返すことに問題がない場合は、この時点で問題ありません。変数@ var1に応じて相関する戻り値の型を返す必要がある場合は、ロジックは、次のように最後にSELECTステートメントをチェックします。

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

オプション2

あるいは、動的SQLソリューションを介して続行したい場合は、次のようにしてグローバル一時テーブルに一意の名前を生成することにより、グローバル一時テーブルの同時実行の問題を修正できます。

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.