2013-03-01 11 views
7

używam SQL Server (2008/2012) i wiem, że istnieją podobne odpowiedzi od wielu poszukiwań, jednak nie wydaje się znaleźć odpowiednie example/wskazówki dla moich walizka.Korzystanie z SQL do transponowania/spłaszczyć strukturę XML do kolumny

Mam kolumny XML w tabeli SQL Server trzyma te dane:

<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> 

Więc chociaż struktura <FormItem> będzie taka sama, mogę mieć kilka (najczęściej nie więcej niż 20 -30) zestawy elementów formularza ..

jestem zasadniczo próbuje powrócić z kwerendy SQL w poniższym formacie, czyli dynamiczne kolumny na podstawie/FormItem/Tekst:

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   

Tak, w tej chwili miałem następujące:

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) 

Oczywiście, że nie dokonała transpozycji moje wiersze XML w kolumnach, i oczywiście jest ustalona z dziedzin tak .. Próbowałam różnych „Dynamiczny SQL” podejścia gdzie SQL wykonuje wyraźny wybór (w moim przypadku) węzła <Text>, a następnie wykorzystuje pewien rodzaj Pivota? ale nie mogłem znaleźć magicznej kombinacji, która zwróciłaby potrzebne wyniki jako dynamiczny zestaw kolumn dla każdego wiersza (<Item> w kolekcji <Items>).

Jestem pewien, że można zrobić, widząc tak wielu bardzo podobnych przykładów, jednak znowu rozwiązanie wymyka mi!

Każda pomoc wdzięcznie otrzymana !!

Odpowiedz

7

analizowania XML jest dość drogie, więc zamiast analizowania raz budować dynamiczną kwerendę i raz w celu uzyskania danych można utworzyć tabelę tymczasową z listą nazwa-wartość, a następnie użyć jej jako źródła dla dynamiczne zapytanie przestawne.
dense_rank jest tam, aby utworzyć identyfikator do obracania się.
Aby zbudować listę kolumn w zapytaniu dynamicznym, stosuje się sztuczkę for xml path('').

To rozwiązanie wymaga, aby tabela zawierała klucz podstawowy (identyfikator). Jeśli masz XML w zmiennej, możesz go nieco uprościć.

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

Dzięki! działa tak jak potrzebowałem. Właściwie mam inny poziom powyżej pokazanego przykładu, ale tak naprawdę to jest grupa razem .. ale kluczową częścią jest pokazanie zmiennej xml węzłów jako kolumn z powtarzającymi się wierszami .. bardzo dziękuję za bardzo szczegółowy przykład! –

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

Dzięki, dobry przykład i właściwie nie widziałem tej techniki dużo wcześniej, jednak nie wyświetla ona dynamicznie pól w kolumnach - prawdopodobnie dobrze, gdybyś znała wcześniej ilość kolumn, jednak powyższy przykład z @Mikael robi to, czego potrzebuję - niemniej, dziękuję za odpowiedź i znowu bardzo prosty czysty przykład na pewno. –

2

chciałem dodać moje własne „odpowiedź” tak naprawdę tylko pod względem kompletności, aby ewentualnie pomagać innym .. jednak jest zdecydowanie opiera się na wielką pomoc od @Mikael powyżej !! więc znowu, to jest naprawdę tylko dla kompletności - wszystkie podziękowania dla @Mikael.

W zasadzie skończyłem z następującym proc. Musiałem wybrać niektóre dane/filtr, a także uzyskać kilka połączonych danych i umożliwić niektóre filtrowanie logiczne w niektórych parametrach wejściowych. Następnie przejdź do następnej sekcji, która utworzyła tymczasową tabelę moich danych relacyjnych i obowiązują wymagane węzły xml przez krzyż. Ostatnim krokiem było wtedy obracać wyniki/dynamicznie tworzyć kolumn z wybranego węzła 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 

Ponownie, naprawdę tylko dodał tę odpowiedź, aby zapewnić pełny obraz z mojego punktu widzenia, i mogą pomóc innym.