Configuração:
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;
XML de amostra para cada linha:
<Number>314</Number>
O trabalho da consulta é contar o número de linhas T
com um valor especificado de <Number>
.
Existem duas maneiras óbvias de fazer isso:
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;
Acontece que value()
e exists()
requer duas definições de caminho diferentes para o índice XML seletivo funcionar.
create selective xml index SIX_T on dbo.T(XMLDoc) for
(
pathSQL = '/Number' as sql int singleton,
pathXQUERY = '/Number' as xquery 'xs:double' singleton
);
A sql
versão é para value()
e a xquery
versão é para exist()
.
Você pode pensar que um índice como esse lhe daria um plano com uma boa procura, mas índices XML seletivos são implementados como uma tabela do sistema com a chave primária T
como a chave principal da chave em cluster da tabela do sistema. Os caminhos especificados são colunas esparsas nessa tabela. Se você deseja um índice dos valores reais dos caminhos definidos, é necessário criar índices seletivos secundários, um para cada expressão de caminho.
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);
O plano de consulta para exist()
faz uma busca no índice XML secundário, seguido de uma pesquisa chave na tabela do sistema para o índice XML seletivo (não sei por que isso é necessário) e, finalmente, faz uma pesquisa T
para garantir que haja realmente linhas lá. A última parte é necessária porque não há restrição de chave estrangeira entre a tabela do sistema e T
.
O plano para a value()
consulta não é tão bom. Ele faz uma varredura de índice agrupada T
com loops aninhados e se junta a uma busca na tabela interna para obter o valor da coluna esparsa e, finalmente, filtra o valor.
Se um índice seletivo deve ser usado ou não, é decidido antes da otimização, mas se um índice seletivo secundário deve ser usado ou não, é uma decisão baseada no custo do otimizador.
Por que o índice seletivo secundário não é usado quando a cláusula where é filtrada value()
?
Atualizar:
As consultas são semanticamente diferentes. Se você adicionar uma linha com o valor
<Number>313</Number>
<Number>314</Number>`
a exist()
versão contaria 2 linhas e a values()
consulta contaria 1 linha. Porém, com as definições de índice conforme especificadas aqui usando a singleton
diretiva SQL Server, você não poderá adicionar uma linha com vários <Number>
elementos.
No entanto, isso não nos permite usar a values()
função sem especificar [1]
para garantir ao compilador que obteremos apenas um único valor. Essa [1]
é a razão pela qual temos um Top N Sort no value()
plano.
Parece que estou me aproximando de uma resposta aqui ...