web-dev-qa-db-ja.com

T-SQL動的SQLおよび一時テーブル

EXECUTE文字列メソッドを介して動的SQLを使用して作成された#temptablesはスコープが異なり、同じストアドプロシージャ内の「固定」SQLから参照できないようです。ただし、サブシーケンスダイナミックSQLのダイナミックSQLステートメントによって作成された一時テーブルを参照できますが、SQLが修正されていない限り、ストアドプロシージャは呼び出し元のクライアントにクエリ結果を返さないようです。

単純な2つのテーブルのシナリオ:2つのテーブルがあります。それらをOrdersとItemsと呼びましょう。 Orderの主キーはOrderIdで、Itemsの主キーはItemIdです。 Items.OrderIdは、親Orderを識別するための外部キーです。オーダーは1からnのアイテムを持つことができます。

非常に柔軟な「クエリビルダー」タイプのインターフェースをユーザーに提供して、ユーザーが表示したい項目をユーザーが選択できるようにしたいと考えています。フィルター条件は、Itemsテーブルまたは親Orderテーブル、あるいはその両方のフィールドに基づくことができます。アイテムが親注文を含むフィルター条件を満たしている場合は、条件が存在する場合は、親注文と同様にクエリで返されます。

通常、ほとんどの人はItemテーブルと親のOrderテーブルの間に結合を構築すると思います。代わりに2つの個別のクエリを実行したいと思います。 1つは対象となるすべてのアイテムを返し、もう1つは個別の親注文をすべて返します。理由は2つあり、同意する場合としない場合があります。

最初の理由は、親のOrderテーブルのすべての列にクエリを実行する必要があるためです。単一のクエリを実行してOrdersテーブルをItemsテーブルに結合すると、Order情報が複数回再要求されます。通常、注文ごとに多数のアイテムがあるため、ファットクライアントに転送されるデータが増えるため、これは避けたいと思います。代わりに、前述のように、2つのテーブルをデータセットで個別に返し、その2つのテーブルを使用して、カスタムのOrderおよび子アイテムのクライアントオブジェクトにデータを入力します。 (まだLINQやEntity Frameworkについてはよくわかりません。オブジェクトを手動で構築しています)。 1つではなく2つのテーブルを返す2つ目の理由は、特定のOrderIdのすべてのアイテムを親Orderとともに返す別のプロシージャが既にあり、同じ2テーブルアプローチを使用して、クライアントコードを再利用して、返された2つのデータテーブルからカスタムのOrderオブジェクトとClientオブジェクトを生成できます。

私がしたいと思っていたのはこれです:

Winformファットクライアントアプリで作成されたカスタムフィルターで指定されているように、ordersテーブルをItemsテーブルに結合し、各テーブルに適切なフィルターをかける動的SQL文字列をクライアントで構築します。クライアント上のSQLビルドは次のようになります。

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 */
    "

次に、ストアドプロシージャを呼び出します。

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)

このアプローチの問題は、#ItemsToQueryテーブルは動的SQLによって作成されたため、次の2つの静的SQLからアクセスできず、静的SQLを動的に変更しても、結果がファットクライアントに返されないことです。

3つ思い浮かびますが、もっと良いものを探しています。

1)最初のSQLは、動的に構築されたSQLをクライアントから実行することで実行できます。次に、結果をテーブルとして上記のストアドプロシージャの変更バージョンに渡すことができます。テーブルデータをXMLとして渡すことに慣れています。これを行った場合、ストアドプロシージャは、静的SQLを使用してデータを一時テーブルに挿入できます。静的SQLは、動的SQLによって作成されたため、問題なくクエリを実行できます。 (XMLの代わりに新しいテーブルタイプのパラメーターを渡すことを検討することもできます。)ただし、潜在的に大きなリストをストアドプロシージャに渡さないようにしたいと思います。

2)クライアントからすべてのクエリを実行できました。

最初は次のようなものです:

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)

OrdersとItemsは引き続き2つの異なるテーブルで返されるため、これでもクライアント側のオブジェクト入力コードを再利用する機能を提供します。

私は、ストアドプロシージャ内でTableデータ型を使用していくつかのオプションがあるかもしれないと感じていますが、それも私にとっては初めてであり、スプーンで少しフィードしていただければ幸いです。

私が書いたものをここまでスキャンしたとしたら、私は驚いていますが、そうだとしたら、これを最高に達成する方法についてのあなたの考えを高く評価します。

18
ChadD

最初にテーブルを作成してから、動的SQLで使用できるようにする必要があります。

これは機能します:

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

SELECT *
FROM #temp3

これは機能しません:

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

SELECT *
FROM #temp2

言い換えると:

  1. 一時テーブルを作成する
  2. プロシージャを実行
  3. 一時テーブルから選択

以下は完全な例です。

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

CREATE TABLE #temp (id INT)

EXEC prTest2 'insert #temp values(1)'

SELECT *
FROM #temp
23
SQLMenace

1つ目の方法-複数のステートメントを同じ動的SQL呼び出しで囲む:

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番目の方法-グローバル一時テーブルを使用:
(慎重に、グローバル変数に特別な注意を払う必要があります。)

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

    SELECT *
    FROM ##temp2
END

## temp2オブジェクトを削除したら、手動で削除することを忘れないでください。

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

注:データベースの完全な構造がわからない場合は、この方法2を使用しないでください。

5
Sid

よくお読みになることを強くお勧めします http://www.sommarskog.se/arrays-in-sql-2005.html

個人的には、コンマ区切りのテキストリストを渡し、それをテキストで解析してテーブル関数に渡し、結合する方法が好きです。一時テーブルのアプローチは、接続で最初に作成した場合に機能します。しかし、少し厄介な感じがします。

2
Sam Saffron

@Muflixが言及したのと同じ問題がありました。返される列がわからない、または動的に生成されている場合は、一意のIDを使用してグローバルテーブルを作成し、それが完了したら削除します。これは、次のようになります。未満:

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

確かに最善の解決策ではありませんが、これは私にとってはうまくいくようです。

1
David Rogers

動的SQLからの結果セットがクライアントに返されます。私はこれをかなり多くしました。

一時テーブルと変数を介してデータを共有する場合の問題、およびSQLとそれが生成する動的SQLの間でのそのようなことについては、あなたは正しいです。

一時テーブルを機能させようとすると、動的SQLを実行するSPから確実にデータを取得できるため、混乱している可能性があります。

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

また:

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