web-dev-qa-db-ja.com

SQLServerの動的列を持つピボット

SQL Server(T-sql)で動的列を持つpvotsを使用してSQLクエリに取り組んでいます。長いクエリを送信するのではなく、単純化されたモデルを使用して問題を説明しています。

Table1とTable2の2つのテーブルを作成し、次のようにいくつかのエントリを入力します。

表1:


Col_ID1 ............... Col_Name

1 ......................... 2011年1月

2 ......................... 2011年2月

3 ......................... 2011年3月

表2:


Col_ID2 ......アカウント.....アカウント名......金額

1 ............... 121 ...........電気............ 10000

2 ............... 121 ...........電気............ 20000

3 ............... 121 ...........電気............ 30000

1 ............... 122 ...........電話.............. 100

2 ............... 122 ...........電話.............. 200

3 ............... 122 ...........電話.............. 300

ピボットを作成していますが、列名を(入力画面から入力された日付に基づいて)パラメトリックに生成し、ハードコーディングしないようにしたいです。

以下のクエリはうまく機能しますが、次のようにいくつかの列しか表示されません。

2011年1月........... 2011年2月........... 2011年3月

10,000.00 ...... 20,000.00 ...... 30,000.00

100.00 ............... 200.00 ........... 300.00

次のように、クエリで説明列も返すようにします。

アカウント...........アカウント名........... 2011年1月............ 2011年2月......。 ...... 2011年3月

121 .................電気.................. 10,000.00 ...... 20,000.00...。 ..... 30,000.00

122 .................電話..................... 100.00.......。 ..200.00 ............. 300.00 ..

誰かが私の目的を達成できるように私のクエリを変更するのを手伝ってくれませんか?

このクエリは、2007年9月にアンドラス博士によって書かれた次の記事を改変したものです。 http://www.simple-talk.com/community/blogs/andras/archive/2007/09/14/37265。 aspx

誰かがコードがインジェクション攻撃を受ける可能性があると述べ、角括弧を連結する代わりにQuotename関数を使用することを提案しました。

クエリでQuotenameを使用する方法を説明してください。

どうもありがとう、

レオンライ。

これが私のクエリです:

------------------------テーブル1の作成と入力---------------------- ----------

CREATE TABLE Table1
(Col_ID1 INT, 
Col_Name varchar(10))  

INSERT INTO Table1 VALUES (1, 'Jan-11')  
INSERT INTO Table1 VALUES (2, 'Feb-11')  
INSERT INTO Table1 VALUES (3, 'Mar-11') 

-------------------------テーブル2の作成と入力--------------------- -------------

CREATE TABLE Table2  
(Col_ID2 INT,  
Account varchar(10),  
AccountName varchar(20),  
Amount numeric(18,6))  

INSERT INTO Table2 VALUES (1, 121, 'Electricity', 10000)  
INSERT INTO Table2 VALUES (2, 121, 'Electricity', 20000)  
INSERT INTO Table2 VALUES (3, 121, 'Electricity', 30000)  
INSERT INTO Table2 VALUES (1, 122, 'Telephone', 100)        
INSERT INTO Table2 VALUES (2, 122, 'Telephone', 200)   
INSERT INTO Table2 VALUES (3, 122, 'Telephone', 300)   

----------------------------------列見出しを作成します------------- ------

DECLARE @cols NVARCHAR(2000)   
SELECT @cols = STUFF(( SELECT DISTINCT TOP 100 PERCENT   
'],[' + t2.Col_Name   
FROM Table1 AS t2   
ORDER BY '],[' + t2.Col_Name 
FOR XML PATH('')   
), 1, 2, '') + ']'   

------------------------------------- create @query ---------- ------------

DECLARE @query NVARCHAR(4000)   

SET @query = N'SELECT '+   
@cols +' 

FROM   

--------------------------サブクエリ-----

(SELECT
t1.Col_Name,
t2.Account,
t2.Amount
FROM Table1 AS t1
JOIN Table2 AS t2 ON t1.Col_ID1 = t2.Col_ID2
) p

--------------------ピボット-------------------------

PIVOT
(
Sum ([Amount] )
FOR Col_Name IN
( '+
@cols +' )
) AS pvt '

---------------------- exec&drop ----------

EXECUTE(@query)
drop table table1
drop table table2

================================================== =====

こんにちはフィリップ、

お返事ありがとうございます。

提案されたクエリはスムーズに機能し、期待される画面を生成しますが、それは私が望んでいたものではありません。

まず、コードに感謝します。SELECT@cols = isnull(@cols + '、'、 '')+ '[' + Col_Name + ']'

それはより単純で、ものとxmlパスを含む私の行を置き換えますが、明らかに同じ効果があります。

私がやりたいことを説明させてください。

Sap Business 1(アカウンティングパッケージ-またはERPと呼びます)でクエリを開発したいと思います。 SapはMicrosoftServer 2008でT-sqlを使用し、独自のクエリジェネレーターを備えています。ごくわずかな例外を除いて、SapsqlはT-sqlに似ています。

クエリで、12か月間のすべての収入と支出のリストを月ごとに表示したいと思います。

ただし、次のように列見出しをハードコーディングしたくありません(これにより、クエリを時々修正する必要があるため)。

1月11日、2月11日、3月11日、4月11日、..... 12月11日

むしろ、ユーザーが入力画面に入力した日付から列見出しを動的に生成する必要があります。

すでに述べたように、フォーラムに投稿したクエリは、実際のクエリを単純化しすぎたものであり、説明のためにのみ使用されています。実際のクエリにはいくつかの変数が含まれており、入力画面(Sap b1では[クエリ]-[選択基準]ボックスと呼ばれます)を使用すると、ユーザーは日付を入力できます。列名を動的に決定するために使用されるのはこの日付です。

これが、@ cols、@ query、pivotなどの複雑なツールが必要だった理由です。

入力画面に「01.06.11」(2011年6月1日)と入力すると、この日付がSQLに渡され、列見出しの名前が次のように決定されます。

6月11日、7月11日、8月11日..... 5月12日。

別の日付、たとえば「01.09.10」(2010年9月1日)を入力すると、列見出しは次のように変わります。

9月10日、10月10日、.... 8月11日

あなたは私の列見出しをハードコーディングしたようです。

私のクエリをもう一度見て、列名をハードコーディングする代わりにパラメトリックに生成できるようにするものを提案していただけませんか?

ありがとう

レオンライ

9
Leon Lai

これらの列の追加は非常に簡単です。最終的なクエリは次のようになります

SELECT Account, AccountName, [Feb-11],[Jan-11],[Mar-11]   FROM   
(SELECT
t1.Col_Name,
t2.Account,
t2.AccountName,
t2.Amount
FROM Table1 AS t1
JOIN Table2 AS t2 ON t1.Col_ID1 = t2.Col_ID2
) p
PIVOT
(
Sum ([Amount] )
FOR Col_Name IN
( [Feb-11],[Jan-11],[Mar-11] )
) AS pvt 

サブクエリにt2.AccountNameが追加され、最初のSELECTにAccountとAccountNameが追加されています。それらをビルドステートメントに入れれば、完了です。

DECLARE @query NVARCHAR(4000)
SET @query = N'SELECT Account, AccountName, ' +    @cols +'   FROM   

(SELECT
t1.Col_Name,
t2.Account,
t2.AccountName,
t2.Amount
FROM Table1 AS t1
JOIN Table2 AS t2 ON t1.Col_ID1 = t2.Col_ID2
) p

PIVOT
(
Sum ([Amount] )
FOR Col_Name IN
( '+
@cols +' )
) AS pvt ' 

SQLインジェクションに関しては、誰かが悪意のあるコードをTable1.Col_Nameに埋め込んだ場合にのみ発生することがわかります。それについて心配する必要がある場合は、この動的クエリを「ロックダウン」するよりも大きな問題が発生します。

また、以下を使用して列のリスト(@Cols)を作成します。これは、列が短くて読みやすいためですが、主にXMLが好きではないためです。

DECLARE @cols NVARCHAR(2000)    
SELECT @cols = isnull(@cols + ',', '') + '[' + Col_Name + ']'
 FROM Table1
 ORDER BY Col_Name
11
Philip Kelley

別の答えを追加します。これは、ほぼ2番目の質問を編集するためです。 (詳細と詳細がなければ、一般的な概要と擬似コードしか提供できません。SAPはわかりません。)

ピボットから始めましょう。おそらく月でラベル付けされた列を生成する必要があります。これは、例ではTable1.Col_Name、varchar(10)として持っていました。これらの値が抽出され、列名としてピボットクエリに動的に追加されます。データベースにそのような列がない場合は、ユーザーが入力したデータに基づいてクエリ用に列を作成する必要があります。次の仮定で作業します。-データにはdateime列があり、任意の値(年からミリ秒)が見つかります-ユーザーが「開始日」(常に月の最初ですか?)を指定すると、その月と次の11か月の列を生成し、各ターゲット月に含まれるデータを集約する必要があります。

ステップ1、12個のターゲット列を含む一時テーブルを設定してデータを入力します。

CREATE TABLE #Months
 (
   Col_Name    varchar(10)
  ,MonthStart  datetime
  ,MonthEnd    datetime
)

ラベルは表示したい形式に設定され、MonthStartはその月の絶対開始(たとえば、2011年10月1日00:00:00.000)になり、MonthEndは翌月の絶対開始(2011年11月1日00)になります。 :00:00.000)–これにより、SELECT … from <table> where DataDate >= MontStart and DataDate < MonthEndを使用してその月のすべてのデータを取得できます。

次に、このテーブルをデータテーブルに結合し、次のように集計します。

SELECT
   mt.Col_Name
  ,sum(dt.RawData)  Amount
 from #Months mt
  inner join MyData dt
   on dt.DataDate >= mt.MonthStart
    and dt.DataDate < mt.MonthEnd  --  Yes, ON clauses don't have to be simple equivalencies!
  inner join <other tables as necessary for Account, AccountName, etc.>

これをピボットステートメントの最も内側のクエリとしてプラグインし、非XMLクエリを使用して一時テーブルからCol_Namesのリストを抽出/ビルドし(他に何を呼び出すかわかりません)、動的にビルドして実行する必要があります。良い。

1
Philip Kelley