4

次のような要素を持つ XML 列があります。

<Root>
    <Word Type="pre1" Value="A" />
    <Word Type="pre1" Value="D" />

    <Word Type="base" Value="B" />

    <Word Type="post1" Value="C" />
    <Word Type="post1" Value="E" />
    <Word Type="post1" Value="F" />
</Root>

そのモデルは次のようなものです:

ここに画像の説明を入力

MSSQL で XQuery を使用してすべての可能なパスを選択し、次のような結果を得たいとします。

ABC 阿部 ABF DBC DBE DBF

または次のようなもの:

<Root>
    <Word Type="pre1" Value="A" />
    <Word Type="pre1" Value="D" />

    <Word Type="pre2" Value="G" />
    <Word Type="pre2" Value="H" />

    <Word Type="base" Value="B" />

    <Word Type="post1" Value="C" />
    <Word Type="post1" Value="E" />
    <Word Type="post1" Value="F" />
</Root>

ここに画像の説明を入力

この結果で:

AHBC AHBE AHBF DHBC DHBE DHBF AGBC AGBE AGBF DGBC DGBE DGBF

4

3 に答える 3

6

CTE を使用して一意の型リストを作成し、それを再帰 CTE で使用して文字列を作成できます。最後に、最後の反復で生成された文字列を選択します。

with Types as
(
  select row_number() over(order by T.N) as ID,
         T.N.value('.', 'varchar(10)') as Type
  from (select @XML.query('for $t in distinct-values(/Root/Word/@Type) 
                           return <T>{$t}</T>')
       ) as X(T)
    cross apply X.T.nodes('/T') as T(N)
),
Recu as
(
  select T.Type,
         T.ID,
         X.N.value('@Value', 'varchar(max)')  as Value
  from Types as T
    cross apply @XML.nodes('/Root/Word[@Type=sql:column("T.Type")]') as X(N)
  where T.ID = 1
  union all
  select T.Type,
         T.ID,
         R.Value+X.N.value('@Value', 'varchar(max)') as Value
  from Types as T
    inner join Recu as R
      on T.ID = R.ID + 1
    cross apply @XML.nodes('/Root/Word[@Type=sql:column("T.Type")]') as X(N)    
)
select R.Value
from Recu as R
where R.ID = (select max(T.ID) from Types as T)
order by R.Value

SQL フィドル

アップデート

性能を上げたバージョンはこちら。XML を 2 つの一時テーブルに分割します。各タイプに 1 つ、すべての単語に 1 つ。再帰的な CTE は引き続き必要ですが、XML の代わりにテーブルを使用します。CTE の結合で使用される各一時テーブルにも 1 つのインデックスがあります。

-- Table to hold all values
create table #Values
(
  Type varchar(10),
  Value varchar(10)
);

-- Clustered index on Type is used in the CTE
create clustered index IX_#Values_Type on #Values(Type)

insert into #Values(Type, Value)
select T.N.value('@Type', 'varchar(10)'),
       T.N.value('@Value', 'varchar(10)')
from @XML.nodes('/Root/Word') as T(N);

-- Table that holds one row for each Type
create table #Types
(
  ID int identity,
  Type varchar(10),
  primary key (ID)
);

-- Add types by document order
-- Table-Valued Function Showplan Operator for nodes guarantees document order
insert into #Types(Type)
select T.Type
from (
     select row_number() over(order by T.N) as rn,
            T.N.value('@Type', 'varchar(10)') as Type
     from @XML.nodes('/Root/Word') as T(N)
     ) as T
group by T.Type
order by min(T.rn);

-- Last level of types
declare @MaxID int;
set @MaxID = (select max(ID) from #Types);

-- Recursive CTE that builds the strings
with C as 
(
  select T.ID,
         T.Type,
         cast(V.Value as varchar(max)) as Value
  from #Types as T
    inner join #Values as V
      on T.Type = V.Type
  where T.ID = 1
  union all
  select T.ID,
         T.Type,
         C.Value + V.Value
  from #Types as T
    inner join C
      on T.ID = C.ID + 1
    inner join #Values as V
      on T.Type = V.Type
)
select C.Value
from C
where C.ID = @MaxID
order by C.Value;

-- Cleanup
drop table #Types;
drop table #Values;

SQL フィドル

于 2013-04-24T13:51:05.843 に答える
4

私があなたの XML を正しく理解していれば、すべてのグラフは本質的に一連のステップであり、ステップを省略できず、各ステップにはいくつかの選択肢があります。(したがって、グラフを通るパスのセットは、基本的に、さまざまな選択肢のセットのデカルト積です。) そうでない場合、次のことはあなたが望むものではありません。

ここでデカルト積を取得する最も簡単な方法はfor、Jens Erat の最初の回答に示されているように、デカルト積の各要素に対して 1 つの句を含む XQuery FLWOR 式を使用することです。

いくつの要素が存在するかが事前にわからない場合 (グラフで発生する可能性のある「タイプ」値のシーケンスがわからないため)、毎回クエリを新たに定式化したくない場合は、最も簡単な方法は、'Type' 値のシーケンスを 1 つの引数として取り、作業中の 'Root' 要素を別の引数として取り、一度に 1 つの要素を処理する再帰関数を作成することです。

この関数は、サンプル入力に対してその仕事をします:

declare function local:cartesian-product(
  $doc as element(),
  $types as xs:string*
) as xs:string* {

  (: If we have no $types left, we are done.
     Return the empty string. :)
  if (empty($types)) then 
     ''

  (: Otherwise, take the first value off the 
     sequence of types and return the Cartesian
     product of all Words with that type and
     the Cartesian product of all the remaining
     types. :)
  else
     let $t := $types[1],
         $rest := $types[position() > 1]
     for $val in $doc/Word[@Type = $t]/@Value
     for $suffix in 
         local:cartesian-product($doc,$rest)
     return concat($val, $suffix)
  };

残っている唯一の問題は、ドキュメントの順序で異なる「タイプ」値のシーケンスを取得するという、少し難しい問題です。呼び出しdistinct-values($doc//Word/@Type)て値を取得することもできますが、それらがドキュメントの順序になるという保証はありません。

関連する問題に対する Dimitre Novatchev の解決策を借りて、次のように 'Type' 値の適切なシーケンスを計算できます。

let $doc := <Root>
    <Word Type="pre1" Value="A" />
    <Word Type="pre1" Value="D" />

    <Word Type="pre2" Value="G" />
    <Word Type="pre2" Value="H" />

    <Word Type="base" Value="B" />

    <Word Type="post1" Value="C" />
    <Word Type="post1" Value="E" />
    <Word Type="post1" Value="F" />
</Root>

let $types0 := ($doc/Word/@Type),
    $types  := $types0[index-of($types0,.)[1]]

これは、個別の値をドキュメント順に返します。

これで、必要な結果を計算する準備が整いました。

return local:cartesian-product($doc, $types)

結果は、指定した順序とは少し異なる順序で返されます。結果の順序は気にしないと思います。

AGBC AGBE AGBF AHBC AHBE AHBF DGBC DGBE DGBF DHBC DHBE DHBF

于 2013-04-24T22:42:57.870 に答える
4

これら 3 つの要素セットの外積が必要なので、基本的に条件なしで結合を記述します。

for $pre  in //Word[@Type="pre1"]
for $base in //Word[@Type="base"]
for $post in //Word[@Type="post1"]
return concat($pre/@Value, $base/@Value, $post/@Value)

拡張バージョンでは、すべての属性をフェッチし、結果を再帰的に連結する 2 つのヘルパー関数を使用しました。

MSSQL はカスタム XQuery 関数を許可していないようです。このコードは、適合する XQuery 1.0 (およびそれ以降) のプロセッサに対して有効です。

declare function local:call($prefix as xs:string) as xs:string* {
  local:recursion('', 
    for $value in distinct-values(//Word/@Type[starts-with(., $prefix)])
    order by $value
    return $value
  )
};

declare function local:recursion($strings as xs:string*, $attributes as xs:string*) as xs:string* {
  if (empty($attributes))
  then $strings
  else
    for $string in $strings
    for $append in //Word[@Type=$attributes[1]]
    return local:recursion(concat($string, $append/@Value), $attributes[position() != 1])
};

for $pre in local:call('pre')
for $base in local:call('base')
for $post in local:call('post')
return concat($pre, $base, $post)
于 2013-04-24T08:56:36.803 に答える