web-dev-qa-db-ja.com

デフォルトの名前空間と一貫性のないルートノードを使用して型付きXMLを解析する

構造化され、型指定されたxmlファイルがあります。

<WebService xmlns="http://www.orbis-software.com/WebSvcCon">
 <NewLeads>
  <OutputSchema>
  <root xmlns="" type="array">
   <item type="object">
   <SaleProperty type="object">
    <Type type="string">Freehold</Type>
    <PostDistrict type="string">xxx</PostDistrict>
    <Address type="string">address</Address>
     <Value type="number">17.0</Value>
   </SaleProperty>
   <Transaction type="string">SaleOnly</Transaction>
   <Quote type="object">
    <Sale type="object">
     <Fees type="number">450.0</Fees>
     <Disbursements type="number">0.0</Disbursements>
     <StampDuty type="number">0.0</StampDuty>
     <Total type="number">450.0</Total>
    </Sale>
   <Discount type="number">0.0</Discount>
   <VAT type="number">90.0</VAT>
   <Total type="number">540.0</Total>
  </Quote>
  <MoverId type="number">12345678</MoverId>
  <Name type="string">Mr AS</Name>
  <Email type="string">[email protected]</Email>
  <Telephone type="string">0123456789</Telephone>
  <Comments type="string">Joint ownership</Comments>
  <EstimatedMoveDate type="string">2015-11-25T05:57:00</EstimatedMoveDate>
  <Charge type="number">4.99</Charge>
  <ChargeStatus type="string">Chargeable</ChargeStatus>
 </item>
</root>
</OutputSchema></NewLeads></WebService>

さらに、オブジェクトSalePropertyが欠落している可能性があり、代わりにオブジェクトPurchasePropertyである可能性があります。同様に、オブジェクトQuoteには、Sale、Purchase、Remortgageオブジェクト、またはsalesとPurchaseを同時に含めることができます。SQLServer 2012のxqueryを介して1つのテーブルに挿入するためのヒントが見つかりません。基本的な問題は、抽出するクエリを構築するたびに1つの値(トランザクション)でも、エラー以外は空の文字列です。

私のサンプルクエリ(MSDNの例に基づく):

SELECT t.c.value('(.)[1]','varchar(50)') as type
from @ixml.nodes('/root/item/transaction') as t(c)

このxmlをテーブルに挿入する方法の例を示します(可能なすべての要素に独自の列があります)。

名前空間とxmlの厳密な型指定に問題があると思います。ただし、これは私がWebサービスから取得するものです。欠落していて、ルート要素に2番目のxmlns宣言が含まれているxmlと、ルートだけにトリミングされた構造に取り組んでいます。

4
AcePL

私が見ることができるXQueryの3つの問題は次のとおりです(これは型付きXMLとは何の関係もありません)。

  1. /rootノードへの正しいパスを指定していません。そのはず:

    SELECT t.c.value('(.)[1]','varchar(50)') as type
    from @ixml.nodes('/WebService/NewLeads/OutputSchema/root/item/transaction') as t(c)
    
  2. XMLでは大文字と小文字が区別されるため、「Transaction」ノードには大文字の「T」を使用する必要があります。

    SELECT t.c.value('(.)[1]','varchar(50)') as type
    from @ixml.nodes('/WebService/NewLeads/OutputSchema/root/item/Transaction') as t(c)
    
  3. これらの修正により、おそらく1つの値が返されますが(この場合は、 "SaleOnly"になるはずです)、.nodes()に指定されたパスが具体的すぎるため、「item」ノードを反復しません。関数。代わりに、その仕様の最後のノードは「アイテム」である必要があります。これは、繰り返し処理したいものです。その場合は、「トランザクション」の部分を.value()関数まで移動します。

    SELECT t.c.value('(./Transaction)[1]','varchar(50)') as type
    from @ixml.nodes('/WebService/NewLeads/OutputSchema/root/item') as t(c)
    

次のステートメントについて:

欠落していて、ルート要素に2番目のxmlns宣言が含まれているxmlと、ルートだけにトリミングされた構造に取り組んでいます。

「ルートだけにトリミングされた構造」は、最初の//の直前の/root...の間のすべてを削除することで処理できます(つまり、WebService/NewLeads/OutputSchema)。したがって、結果のパスは次のようになります。

from @ixml.nodes('//root/item') as t(c)

注:
<WebService>要素で宣言された名前空間でこれを100%動作させることができません(これはもう当てはまらないので、追加の注意を参照してください)。それを取り除くとうまくいきます。 xmlns:something="http://www.orbis-software.com/WebSvcCon"などのプレフィックスを付けると機能しますが、それをメソッドで宣言する必要があります。今すぐ機能させる唯一の方法は、各XML関数(.nodesおよび.value)でデフォルトの名前空間を次のように宣言することです。

SELECT t.c.value('declare default element namespace "http://www.orbis-software.com/WebSvcCon";
                  (./Transaction)[1]','varchar(50)') as [type]
from @ixml.nodes('declare default element namespace "http://www.orbis-software.com/WebSvcCon";
                  /WebService/NewLeads/OutputSchema/root/item') as t(c)

注2:
さらに良いことに、WITH XMLNAMESPACESを使用して、クエリ全体に使用する1つ以上の名前空間を宣言できるため、各XML関数で定義する必要はありません。次の両方が機能します。

;WITH XMLNAMESPACES (DEFAULT 'http://www.orbis-software.com/WebSvcCon')
SELECT t.c.value('(./Transaction)[1]','varchar(50)') as [type]
from @ixml.nodes('/WebService/NewLeads/OutputSchema/root/item') as t(c)


;WITH XMLNAMESPACES (DEFAULT 'http://www.orbis-software.com/WebSvcCon')
SELECT t.c.value('(./Transaction)[1]','varchar(50)') as [type]
from @ixml.nodes('//root/item') as t(c)

ただし、ドキュメントに<WebService xmlns="http://www.orbis-software.com/WebSvcCon">要素がなく、デフォルトの名前空間がない場合は、;WITH XMLNAMESPACESの部分を削除する必要があることに注意してください。もちろん、<root>要素に独自のデフォルトの名前空間がある場合は、それを維持する必要があります。動作するまで、これらの要素間の関係がわかったので、いじってみてください。

注3:
2つのデフォルト名前空間が宣言されてしまう場合は、1つは<WebService>要素で、もう1つは<root>要素で、<root xmlns="bob">および完全修飾パスの代わりに//構文。したがって、XMLが次のようになっているとします。

<WebService xmlns="http://www.orbis-software.com/WebSvcCon">
 <NewLeads>
  <OutputSchema>
  <root xmlns="http://someplace" type="array">

次に、以下を使用します。

;WITH XMLNAMESPACES (DEFAULT 'http://someplace')
SELECT t.c.value('(./Transaction)[1]','varchar(50)') as [type]
from @ixml.nodes('//root/item') as t(c)

ただし、<WebService>要素があり、<root>要素にxmlns宣言がない場合、これは役に立ちません。その場合でも、<WebService>要素に記載されている名前空間を指定する必要があります。楽しい楽しい楽しい :-)。

注4:
さらに改善:@wBobの answer で言及されたものを組み込むと、実際に;WITH XMLNAMESPACES句を取り除くことができ、代わりに名前空間ワイルドカードを使用できます。 everyノードのeveryXML関数の前に*:を付けるだけです。これで、クエリは次のようになります。

SELECT t.c.value('(./*:Transaction)[1]','varchar(50)') AS [type],
       t.c.value('(./*:SaleProperty/*:PostDistrict)[1]','varchar(50)') AS [PostDistrict]
FROM  @ixml.nodes('//*:root/*:item') t(c);

これにより、クエリはすべてのシナリオで機能します。

  1. 「WebService」ノードで始まる完全な構造、2番目のxmlns宣言:

    <WebService xmlns="http://www.orbis-software.com/WebSvcCon">
      <NewLeads>
        <OutputSchema>
          <root xmlns="uri" type="array">
    
  2. 「WebService」ノードで始まる完全な構造、単一のxmlns宣言:

    <WebService xmlns="http://www.orbis-software.com/WebSvcCon">
      <NewLeads>
        <OutputSchema>
          <root type="array">
    
  3. 「ルート」ノードで始まる構造をトリミングし、単一のxmlns宣言:

    <root xmlns="uri" type="array">
    
8
Solomon Rutzky

名前空間に関係なく、すべての要素の値を行としてダンプする場合は、名前空間のワイルドカードを使用できます。例:

DECLARE @xml XML = '<WebService xmlns="http://www.orbis-software.com/WebSvcCon">
    <NewLeads>
        <OutputSchema>
            <root xmlns="" type="array">
                <item type="object">
                    <SaleProperty type="object">
                        <Type type="string">Freehold</Type>
                        <PostDistrict type="string">xxx</PostDistrict>
                        <Address type="string">address</Address>
                        <Value type="number">17.0</Value>
                    </SaleProperty>
                    <Transaction type="string">SaleOnly</Transaction>
                    <Quote type="object">
                        <Sale type="object">
                            <Fees type="number">450.0</Fees>
                            <Disbursements type="number">0.0</Disbursements>
                            <StampDuty type="number">0.0</StampDuty>
                            <Total type="number">450.0</Total>
                        </Sale>
                        <Discount type="number">0.0</Discount>
                        <VAT type="number">90.0</VAT>
                        <Total type="number">540.0</Total>
                    </Quote>
                    <MoverId type="number">12345678</MoverId>
                    <Name type="string">Mr AS</Name>
                    <Email type="string">[email protected]</Email>
                    <Telephone type="string">0123456789</Telephone>
                    <Comments type="string">Joint ownership</Comments>
                    <EstimatedMoveDate type="string">2015-11-25T05:57:00</EstimatedMoveDate>
                    <Charge type="number">4.99</Charge>
                    <ChargeStatus type="string">Chargeable</ChargeStatus>
                </item>
            </root>
        </OutputSchema>
    </NewLeads>
</WebService>'


-- Show all elements and their values irrespective of namespace
SELECT 
    x.y.value('local-name(..)', 'VARCHAR(MAX)') parentElementName,
    x.y.value('local-name(.)', 'VARCHAR(MAX)') elementName,
    x.y.value('.', 'VARCHAR(MAX)') elementValue
FROM @xml.nodes('//*[not(*)]') AS x(y)


SELECT 
    ws.c.value('local-name(..)', 'VARCHAR(MAX)') parentElementName,
    ws.c.value('local-name(.)', 'VARCHAR(MAX)') elementName,
    ws.c.value('.', 'VARCHAR(MAX)') elementValue
FROM @xml.nodes('*:WebService/*:NewLeads/*:OutputSchema/*:root/*:item/*[not(*)]') AS ws(c)

結果:

Results

これを列に変換する必要がある場合は、動的ピボットなどを実行できます。要素を明示的に指定したい場合は、同様の手法を使用できます。

あなたの質問から判断すると、おそらくXML名前空間についてより多くの時間を費やす必要があるので、ここから始めます:

WITH XMLNAMESPACESを使用して名前空間を追加する http://msdn.Microsoft.com/en-us/library/ms177400.aspx

3
wBob