Where cümlesi `value ()` üzerinde filtrelendiğinde neden ikincil seçici dizin kullanılmıyor?


13

Kurmak:

create table dbo.T
(
  ID int identity primary key,
  XMLDoc xml not null
);

insert into dbo.T(XMLDoc)
select (
       select N.Number
       for xml path(''), type
       )
from (
     select top(10000) row_number() over(order by (select null)) as Number
     from sys.columns as c1, sys.columns as c2
     ) as N;

Her satır için örnek XML:

<Number>314</Number>

Sorgunun işi, Tbelirtilen değeri olan satır sayısını saymaktır <Number>.

Bunu yapmanın iki belirgin yolu vardır:

select count(*)
from dbo.T as T
where T.XMLDoc.value('/Number[1]', 'int') = 314;

select count(*)
from dbo.T as T
where T.XMLDoc.exist('/Number[. eq 314]') = 1;

Bu ortaya çıkıyor value()ve exists()seçici XML dizininin çalışması için iki farklı yol tanımı gerektiriyor.

create selective xml index SIX_T on dbo.T(XMLDoc) for
(
  pathSQL = '/Number' as sql int singleton,
  pathXQUERY = '/Number' as xquery 'xs:double' singleton
);

sqlVersiyon içindir value()ve xqueryversiyon içindir exist().

Bunun gibi bir dizinin size güzel bir arama içeren bir plan vereceğini düşünebilirsiniz, ancak seçici XML dizinleri, sistem tablosunun Tkümelenmiş anahtarının ana anahtarı olarak birincil anahtarla bir sistem tablosu olarak uygulandığını düşünebilirsiniz . Belirtilen yollar, o tablodaki seyrek sütunlardır. Tanımlı yolların gerçek değerlerinin bir dizinini istiyorsanız, her yol ifadesi için bir tane olmak üzere ikincil seçici dizinler oluşturmanız gerekir.

create xml index SIX_T_pathSQL on dbo.T(XMLDoc)
  using xml index SIX_T for (pathSQL);

create xml index SIX_T_pathXQUERY on dbo.T(XMLDoc)
  using xml index SIX_T for (pathXQUERY);

Sorgu planı exist()ikincil XML dizininde bir arama yapar ve ardından seçici XML dizini için sistem tablosunda bir anahtar araması yapar (neden gerekli olduğunu bilmiyorum) ve son olarak Tgerçekten olduğundan emin olmak için bir arama yapar orada satırlar. Son bölüm gereklidir, çünkü sistem tablosu ve arasında yabancı anahtar kısıtlaması yoktur T.

resim açıklamasını buraya girin

value()Sorgu planı çok hoş değil. Tİçerideki döngüler kümelenmiş bir dizin taraması yapar, iç tablodaki bir aramaya karşı seyrek sütundan değeri alır ve son olarak değeri filtreler.

resim açıklamasını buraya girin

Optimizasyondan önce bir seçici endeksin kullanılması gerekip gerekmediğine karar verilirse, ancak ikincil bir seçici endeksin kullanılması gerekip gerekmediği, optimizatör tarafından maliyete dayalı bir karardır.

Where yan tümcesi filtrelendiğinde neden ikincil seçici dizin kullanılmıyor value()?

Güncelleme:

Sorgular anlamsal olarak farklıdır. Değerli bir satır eklerseniz

<Number>313</Number>
<Number>314</Number>` 

exist()sürüm 2 satır sayılır ve values()sorgu 1 satır sayılır. Ancak burada belirtildiği gibi dizin tanımları ile singletonSQL Server yönergesini kullanarak birden çok <Number>öğeye sahip bir satır eklemenizi engeller .

Ancak bu , derleyiciye yalnızca tek bir değer alacağımızı garanti etmek için values()belirtmeden işlevi kullanmamıza izin vermez [1]. Yani [1]biz sırala Top N olması nedeni value()planı.

Görünüşe göre burada bir cevaba yaklaşıyorum ...

Yanıtlar:


11

Dizinin singletonyol ifadesinde bildirimi, birden çok <Number>öğe ekleyemeyeceğinizi zorlar, ancak XQuery derleyicisi value()işlevdeki ifadeyi yorumlarken bunu dikkate almaz . [1]SQL Server'ı mutlu etmek için belirtmeniz gerekir. Şema ile yazılmış XML kullanmak da bu konuda yardımcı olmaz. Ve bu nedenle SQL Server, "uygula" deseni olarak adlandırılabilecek bir şey kullanan bir sorgu oluşturur.

Gösterilmesi en kolay, aslında yürüttüğümüz sorguyu Tve dahili tabloyu simüle eden XML yerine normal tablolar kullanmaktır .

İşte iç tablo için gerçek bir tablo olarak kurulum.

create table dbo.xml_sxi_table
(
  pk1 int not null,
  row_id int,
  path_1_id varbinary(900),
  pathSQL_1_sql_value int,
  pathXQUERY_2_value float
);

go

create clustered index SIX_T on xml_sxi_table(pk1, row_id);
create nonclustered index SIX_pathSQL on xml_sxi_table(pathSQL_1_sql_value) where path_1_id is not null;
create nonclustered index SIX_T_pathXQUERY on xml_sxi_table(pathXQUERY_2_value) where path_1_id is not null;

go

insert into dbo.xml_sxi_table(pk1, row_id, path_1_id, pathSQL_1_sql_value, pathXQUERY_2_value)
select T.ID, 1, T.ID, T.ID, T.ID
from dbo.T;

Her iki tablo da yerinde olduğunda, exist()sorgunun eşdeğerini çalıştırabilirsiniz .

select count(*)
from dbo.T
where exists (
             select *
             from dbo.xml_sxi_table as S
             where S.pk1 = T.ID and
                   S.pathXQUERY_2_value = 314 and
                   S.path_1_id is not null
             );

value()Sorgunun eşdeğeri şöyle görünür.

select count(*)
from dbo.T
where (
      select top(1) S.pathSQL_1_sql_value
      from dbo.xml_sxi_table as S
      where S.pk1 = T.ID and
            S.path_1_id is not null
      order by S.path_1_id
      ) = 314;

top(1)Ve order by S.path_1_idsuçlu ve öyle [1]sorumlu olduğu XPath ifadesinde.

Ben dışarı bırakmak için izin verildi bile Microsoft iç tablonun şimdiki yapısı ile bu sorunu gidermek için mümkün olduğunu sanmıyorum [1]gelen values()işlevi. Optimize edicinin <number>her satır için yalnızca bir öğe olabileceğini garanti etmek için büyük olasılıkla her yol ifadesi için birden fazla dahili tablo oluşturmaları gerekir . Bunun, optimize edicinin "uygulama modelini kırması" için yeterli olacağından emin değilim.

Bu eğlenceli ve ilginç düşünen ve hala bunu okuduğunuz için muhtemelen öylesiniz.

İç tablonun yapısına bakmak için bazı sorgular.

select T.name, 
       T.internal_type_desc, 
       object_name(T.parent_id) as parent_table_name
from sys.internal_tables as T
where T.parent_id = object_id('T');

select C.name as column_name, 
       C.column_id,
       T.name as type_name,
       C.max_length,
       C.is_sparse,
       C.is_nullable
from sys.columns as C
  inner join sys.types as T
    on C.user_type_id = T.user_type_id
where C.object_id in (
                     select T.object_id 
                     from sys.internal_tables as T 
                     where T.parent_id = object_id('T')
                     )
order by C.column_id;

select I.name as index_name,
       I.type_desc,
       I.is_unique,
       I.filter_definition,
       IC.key_ordinal,
       C.name as column_name, 
       C.column_id,
       T.name as type_name,
       C.max_length,
       I.is_unique,
       I.is_unique_constraint
from sys.indexes as I
  inner join sys.index_columns as IC
    on I.object_id = IC.object_id and
       I.index_id = IC.index_id
  inner join sys.columns as C
    on IC.column_id = C.column_id and
       IC.object_id = C.object_id
  inner join sys.types as T
    on C.user_type_id = T.user_type_id
where I.object_id in (
                     select T.object_id 
                     from sys.internal_tables as T 
                     where T.parent_id = object_id('T')
                     );
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.