2013-03-01 13 views
7

SQL Server(2008/2012)を使用していますが、多くの検索から同様の回答があることがわかりますが、適切な例/ポインタが見つかりません場合。 (最も一般的には20を超えるSQLを使用して列にXML構造を変換/平坦化する

<Items> 
<Item> 
    <FormItem> 
    <Text>FirstName</Text> 
    <Value>My First Name</Value> 
    </FormItem> 
    <FormItem> 
    <Text>LastName</Text> 
    <Value>My Last Name</Value> 
    </FormItem> 
    <FormItem> 
    <Text>Age</Text> 
    <Value>39</Value> 
    </FormItem> 
</Item> 
<Item> 
    <FormItem> 
    <Text>FirstName</Text> 
    <Value>My First Name 2</Value> 
    </FormItem> 
    <FormItem> 
    <Text>LastName</Text> 
    <Value>My Last Name 2</Value> 
    </FormItem> 
    <FormItem> 
    <Text>Age</Text> 
    <Value>40</Value> 
    </FormItem> 
</Item> 
</Items> 

だから<FormItem>の構造が同じであることを行っているにもかかわらず、私は複数持つことができます。

私はこのデータを保持しているSQL ServerテーブルのXML列を持ちます-30)フォーム項目のセット..

私は基本的に/ FormItemの/テキストに基づいて、すなわち、以下の形式でSQLからクエリを返すように動的な列をしようとしています:

FirstName   LastName   Age ---> More columns as new `<FormItem>` are returned 
My First Name  My Last Name  39   Whatever value etc.. 
My First Name 2 My Last Name 2 40   

だから、私は次のように持っていた瞬間に:もちろん

select 
    Tab.Col.value('Text[1]','nvarchar(100)') as Question, 
    Tab.Col.value('Value[1]','nvarchar(100)') as Answer 
from 
    @Questions.nodes('/Items/Item/FormItem') Tab(Col) 

列に私のXML行を移調していない、と明らかにとにかくフィールドに固定されている..私は、様々な「動的SQL」のアプローチをしようとしていますSQLは(私の場合は)<Text>というノードを明確に選択して、Pivo​​tを使用していますか?私は、各行(のコレクション内)の列の動的セットとして必要な結果を返すための魔法の組み合わせを見つけることができなかった。

非常に似たような例が数多く見られますが、解決策は私には分かりません。

感謝の気持ちで助けてください! XMLを解析

答えて

7

かなり高価であるので、代わりの動的なクエリを構築するために、一度あなたが名前と値のリストを使用して一時テーブルを作成し、その後のソースとしてそれを使用することができ、データを取得するために一度の解析動的ピボットクエリ。
dense_rank IDを作成してピボットする必要があります。
動的クエリに列リストを作成するには、for xml path('')のトリックを使用します。

このソリューションでは、テーブルに主キー(ID)が必要です。変数にXMLがある場合は、いくぶん単純化することができます。

select dense_rank() over(order by ID, I.N) as ID, 
     F.N.value('(Text/text())[1]', 'varchar(max)') as Name, 
     F.N.value('(Value/text())[1]', 'varchar(max)') as Value 
into #T 
from YourTable as T 
    cross apply T.XMLCol.nodes('/Items/Item') as I(N) 
    cross apply I.N.nodes('FormItem') as F(N) 

declare @SQL nvarchar(max) 
declare @Col nvarchar(max) 

select @Col = 
    (
    select distinct ','+quotename(Name) 
    from #T 
    for xml path(''), type 
).value('substring(text()[1], 2)', 'nvarchar(max)') 

set @SQL = 'select '[email protected]+' 
      from #T 
      pivot (max(Value) for Name in ('[email protected]+')) as P' 

exec (@SQL) 

drop table #T 

SQL Fiddle

+0

ありがとう!実際に私が必要としたとおりに動作します。実際には、表示されている例の上に別のレベルがありますが、それは実際にはをグループ化することです。しかし、重要な部分は、の列として非常に詳細な例のために非常に多くのありがとう! –

2
select Tab.Col.value('(FormItem[Text = "FirstName"]/Value)[1]', 'varchar(32)') as FirstName, 
     Tab.Col.value('(FormItem[Text = "LastName"]/Value)[1]', 'varchar(32)') as LastName, 
     Tab.Col.value('(FormItem[Text = "Age"]/Value)[1]', 'int') as Age 
from @Questions.nodes('/Items/Item') Tab(Col) 
+1

ありがとう、良い例ですが、これまでずっとそのテクニックを見たことはありませんでしたが、フィールドを列として動的に表示することはありません - 事前に列の量を知っていれば大丈夫ですが、@Mikaelそれにもかかわらず、返信してくれてありがとう、もう一度非常に簡単なきれいな例があります。 –

2

私は...しかし、それは最も確かに上記@Mikaelからの大きな助けに基づいているだけで、おそらく他の人を助けるために、完全性のために、本当に私の「自分の答え」を追加したいです!もう一度、これは完全性のためだけです - @Mikaelへのすべての賞賛。

基本的に私は次のprocで終了しました。私はいくつかのデータ/フィルタを選択し、いくつかの結合されたデータを取得し、入力パラメータのいくつかのブール値フィルタリングを許可する必要がありました。その後、次のセクションにドロップします。これは、リレーショナルデータのテンポラリテーブルを作成し、クロスXMLを介して必要なxmlノードを作成します。最後のステップは...そして、動的に選択されたXMLノードから列を作成/結果を旋回するように再び

CREATE PROCEDURE [dbo].[usp_RPT_ExtractFlattenentries] 
    @CompanyID   int, 
    @MainSelector  nvarchar(50) = null, 
    @SecondarySelector  nvarchar(255) = null, 
    @DateFrom   datetime = '01-jan-2012', 
    @DateTo    datetime = '31-dec-2100', 
    @SysReference  nvarchar(20) = null 
AS 
BEGIN 
    SET NOCOUNT ON; 

    -- Create the table var to hold the XML form data from the entries 
    declare @FeedbackXml table (
     ID int identity primary key, 
     XMLCol xml, 
     CompanyName nvarchar(20), 
     SysReference nvarchar(20), 
     RecordDate datetime, 
     EntryName nvarchar(255), 
     MainSelector nvarchar(50) 
    ) 

    -- STEP 1: Get the raw submission data based on the params passed in 
    -- *Note: The double casting is necessary as the "form" field is nvarchar (not varchar) and we need xml in UTF-8 format 
    begin 
     insert into @FeedbackXml 
      (XMLCol, CompanyName, SysReference, RecordDate, EntryName, MainSelector) 
     select cast(cast(e.form as nvarchar(max)) as xml), c.name, e.SysReference, e.RecordDate, e.name, e.wizard 
     from 
      entries s 
     left join 
      companies o on e.companies = c.ID 
     where 
      (@CompanyID = -1 or @CompanyID = e.companies) 
     and 
      (@MainSelector is null or @MainSelector = e.wizard) 
     and 
      (@SecondarySelector is null or @SecondarySelector = e.name) 
     and 
      (@SysReference is null or @SysReference = e.SysReference) 
     and 
      (e.RecordDate >= @DateFrom and e.RecordDate <= @DateTo) 
    end 

    -- STEP 2: Flatten the required XML structure to provide a base for the pivot, and include other fields we wish to output 
    select dense_rank() over(order by ID) as ID, 
      T.RecordDate, T.CompanyName, T.SysReference, T.EntryName, T.MainSelector, 
      F.N.value('(FieldNameNode/text())[1]', 'nvarchar(max)') as FieldName, 
      F.N.value('(FieldNameValue/text())[1]', 'nvarchar(max)') as FieldValue 
    into #TempData 
    from @FeedbackXml as T 
     cross apply T.XMLCol.nodes('/root/companies/') as I(N) -- Xpath to the desired node start point 
     cross apply I.N.nodes('company') as F(N) -- The actual node collection that forms the "field name" and "field value" data 

    -- STEP 3: Pivot the #TempData table creating a dynamic column structure based on the selected XML nodes in step 2 
    declare @SQL nvarchar(max) 
    declare @Col nvarchar(max) 

    select @Col = 
     (
     select distinct ','+quotename(FieldName) 
     from #TempData 
     for xml path(''), type 
    ).value('substring(text()[1], 2)', 'nvarchar(max)') 

    set @SQL = 'select CompanyName, SysReference, EntryName, MainSelector, RecordDate, '[email protected]+' 
       from #TempData 
       pivot (max(FieldValue) for FieldName in ('[email protected]+')) as P' 

    exec (@SQL) 
    drop table #TempData 

END 

た、本当に唯一の私の観点から全体像を提供するために、この答えを追加し、他の人を助けるかもしれません。

関連する問題