Ci sono alcuni misteri del piano di query che deve essere risolto prima. Che cosa fa il calcolo scalare e perché c'è un flusso aggregato.
La funzione con valori di tabella restituisce una tabella di nodi dell'XML triturato, una riga per ogni riga triturata. Quando si utilizza l'XML digitato, quelle colonne sono valore, lvalue, lvaluebin e tid. Queste colonne vengono utilizzate nello scalare del calcolo per calcolare il valore effettivo. Il codice lì sembra un po 'strano e non posso dire di aver capito perché è così com'è, ma il succo è che la funzione xsd_cast_to_maybe_large
restituisce il valore e c'è un codice che gestisce il caso quando il valore è uguale a e maggiore di 128 byte.
Lo stesso scalare di calcolo per XML non tipizzato è molto più semplice e comprensibile.
CASE WHEN datalength(XML Reader with XPath filter.[value])>=(128)
THEN CONVERT_IMPLICIT(int,XML Reader with XPath filter.[lvalue],0)
ELSE CONVERT_IMPLICIT(int,XML Reader with XPath filter.[value],0)
END
Se vi sono più di 128 byte in value
recuperano da lvalue
altro prelevare da value
. Nel caso di XML non tipizzato, la tabella dei nodi restituiti emette solo le colonne id, value e lvalue.
Quando si utilizza l'XML digitato, la memorizzazione dei valori del nodo viene ottimizzata in base al tipo di dati specificato nello schema. Sembra che possa finire nel valore, lvalue o lvaluebin nella tabella dei nodi a seconda del tipo di valore che è e xsd_cast_to_maybe_large è lì per aiutare a risolvere le cose.
L'aggregazione di flusso esegue una min() sui valori restituiti dallo scalare di calcolo.Sappiamo e SQL Server (almeno a volte) sa che ci sarà sempre una sola riga restituita dalla funzione di valore della tabella quando si specifica un XPath nella funzione value(). Il parser si assicura che creiamo l'XPath correttamente ma quando l'ottimizzatore di query guarda le righe stimate vede 200 righe. La stima di base per la funzione valutata a livello di tabella che analizza l'XML è di 10000 righe e quindi sono state apportate alcune rettifiche utilizzando lo XPath utilizzato. In questo caso finisce con 200 righe dove ce n'è solo una. La pura speculazione da parte mia è che l'aggregato del flusso è lì per prendersi cura di questa discrepanza. Non aggregherà mai nulla, invia solo la riga che viene restituita, ma influisce sulla stima della cardinalità per l'intero ramo e si assicura che l'ottimizzatore utilizzi 1 righe come stima per quel ramo. Questo è ovviamente molto importante quando l'ottimizzatore sceglie le strategie di join, ecc.
Quindi, che ne dici di 100 attributi? Sì, ci saranno 100 filiali se si utilizza la funzione valore 100 volte. Ma ci sono alcune ottimizzazioni da fare qui. Ho creato un banco di prova per vedere quale forma e forma della query sarebbe stata la più veloce utilizzando 100 attributi su 10 righe.
Il vincitore era utilizzare XML non tipizzato e non utilizzare la funzione nodes()
per distruggere r
.
select X.value('(/r/@a1)[1]', 'int') as a1,
X.value('(/r/@a2)[1]', 'int') as a2,
X.value('(/r/@a3)[1]', 'int') as a3
from @T
V'è anche un modo per evitare le 100 filiali che utilizzano perno, ma a seconda di ciò che la vostra query reale sembra che potrebbe non essere possibile. Il tipo di dati che esce dal pivot deve essere lo stesso. Ovviamente potresti estrarli come una stringa e convertirli in un tipo appropriato nell'elenco delle colonne. Richiede inoltre che la tabella abbia una chiave primaria/unica.
select a1, a2, a3
from (
select T.ID, -- primary key of @T
A.X.value('local-name(.)', 'nvarchar(50)') as Name,
A.X.value('.', 'int') as Value
from @T as T
cross apply T.X.nodes('/r/@*') as A(X)
) as T
pivot(min(T.Value) for Name in (a1, a2, a3)) as P
piano Domanda per interrogazione perno, 10 righe 100 attributi:
seguito si riportano i risultati e il banco di prova ho usato. Ho provato con 100 attributi e 10 righe e tutti gli attributi int.
Risultato:
Test Duration (ms)
-------------------------------------------------- -------------
untyped XML value('/r[1]/@a') 195
untyped XML value('(/r/@a)[1]') 108
untyped XML value('@a') cross apply nodes('/r') 131
untyped XML value('@a') cross apply nodes('/r[1]') 127
typed XML value('/r/@a') 185
typed XML value('(/r/@a)[1]') 148
typed XML value('@a') cross apply nodes('/r') 176
untyped XML pivot 34
typed XML pivot 52
Codice:
drop type dbo.TRABType
drop type dbo.TType;
drop xml schema collection dbo.RAB;
go
declare @NumAtt int = 100;
declare @Attribs nvarchar(max);
with xmlnamespaces('http://www.w3.org/2001/XMLSchema' as xsd)
select @Attribs = (
select top(@NumAtt) 'a'+cast(row_number() over(order by 1/0) as varchar(11)) as '@name',
'sqltypes:int' as '@type',
'required' as '@use'
from sys.columns
for xml path('xsd:attribute')
)
--CREATE XML SCHEMA COLLECTION RAB AS
declare @Schema nvarchar(max) =
'
<xsd:schema xmlns:schema="urn:schemas-microsoft-com:sql:SqlRowSet1" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:sqltypes="http://schemas.microsoft.com/sqlserver/2004/sqltypes" elementFormDefault="qualified">
<xsd:import namespace="http://schemas.microsoft.com/sqlserver/2004/sqltypes" schemaLocation="http://schemas.microsoft.com/sqlserver/2004/sqltypes/sqltypes.xsd" />
<xsd:element name="r" type="r"/>
<xsd:complexType name="r">[ATTRIBS]</xsd:complexType>
</xsd:schema>';
set @Schema = replace(@Schema, '[ATTRIBS]', @Attribs)
create xml schema collection RAB as @Schema
go
create type dbo.TType as table
(
ID int identity primary key,
X xml not null
);
go
create type dbo.TRABType as table
(
ID int identity primary key,
X xml(document rab) not null
);
go
declare @NumAtt int = 100;
declare @NumRows int = 10;
declare @X nvarchar(max);
declare @C nvarchar(max);
declare @M nvarchar(max);
declare @S1 nvarchar(max);
declare @S2 nvarchar(max);
declare @S3 nvarchar(max);
declare @S4 nvarchar(max);
declare @S5 nvarchar(max);
declare @S6 nvarchar(max);
declare @S7 nvarchar(max);
declare @S8 nvarchar(max);
declare @S9 nvarchar(max);
set @X = N'<r '+
(
select top(@NumAtt) 'a'+cast(row_number() over(order by 1/0) as varchar(11))+'="'+cast(row_number() over(order by 1/0) as varchar(11))+'" '
from sys.columns
for xml path('')
)+
'/>';
set @C =
stuff((
select top(@NumAtt) ',a'+cast(row_number() over(order by 1/0) as varchar(11))
from sys.columns
for xml path('')
), 1, 1, '')
set @M =
stuff((
select top(@NumAtt) ',MAX(CASE WHEN name = ''a'+cast(row_number() over(order by 1/0) as varchar(11))+''' THEN val END)'
from sys.columns
for xml path('')
), 1, 1, '')
declare @T dbo.TType;
insert into @T(X)
select top(@NumRows) @X
from sys.columns;
declare @TRAB dbo.TRABType;
insert into @TRAB(X)
select top(@NumRows) @X
from sys.columns;
-- value('/r[1]/@a')
set @S1 = N'
select T.ID'+
(
select top(@NumAtt) ', T.X.value(''/r[1]/@a'+cast(row_number() over(order by 1/0) as varchar(11))+''', ''int'')'
from sys.columns
for xml path('')
)+
' from @T as T
option (maxdop 1)';
-- value('(/r/@a)[1]')
set @S2 = N'
select T.ID'+
(
select top(@NumAtt) ', T.X.value(''(/r/@a'+cast(row_number() over(order by 1/0) as varchar(11))+')[1]'', ''int'')'
from sys.columns
for xml path('')
)+
' from @T as T
option (maxdop 1)';
-- value('@a') cross apply nodes('/r')
set @S3 = N'
select T.ID'+
(
select top(@NumAtt) ', T2.X.value(''@a'+cast(row_number() over(order by 1/0) as varchar(11))+''', ''int'')'
from sys.columns
for xml path('')
)+
' from @T as T
cross apply T.X.nodes(''/r'') as T2(X)
option (maxdop 1)';
-- value('@a') cross apply nodes('/r[1]')
set @S4 = N'
select T.ID'+
(
select top(@NumAtt) ', T2.X.value(''@a'+cast(row_number() over(order by 1/0) as varchar(11))+''', ''int'')'
from sys.columns
for xml path('')
)+
' from @T as T
cross apply T.X.nodes(''/r[1]'') as T2(X)
option (maxdop 1)';
-- value('/r/@a') typed XML
set @S5 = N'
select T.ID'+
(
select top(@NumAtt) ', T.X.value(''/r/@a'+cast(row_number() over(order by 1/0) as varchar(11))+''', ''int'')'
from sys.columns
for xml path('')
)+
' from @TRAB as T
option (maxdop 1)';
-- value('(/r/@a)[1]')
set @S6 = N'
select T.ID'+
(
select top(@NumAtt) ', T.X.value(''(/r/@a'+cast(row_number() over(order by 1/0) as varchar(11))+')[1]'', ''int'')'
from sys.columns
for xml path('')
)+
' from @TRAB as T
option (maxdop 1)';
-- value('@a') cross apply nodes('/r') typed XML
set @S7 = N'
select T.ID'+
(
select top(@NumAtt) ', T2.X.value(''@a'+cast(row_number() over(order by 1/0) as varchar(11))+''', ''int'')'
from sys.columns
for xml path('')
)+
' from @TRAB as T
cross apply T.X.nodes(''/r'') as T2(X)
option (maxdop 1)';
-- pivot
set @S8 = N'
select ID, '[email protected]+'
from (
select T.ID,
A.X.value(''local-name(.)'', ''nvarchar(50)'') as Name,
A.X.value(''.'', ''int'') as Value
from @T as T
cross apply T.X.nodes(''/r/@*'') as A(X)
) as T
pivot(min(T.Value) for Name in ('[email protected]+')) as P
option (maxdop 1)';
-- typed pivot
set @S9 = N'
select ID, '[email protected]+'
from (
select T.ID,
A.X.value(''local-name(.)'', ''nvarchar(50)'') as Name,
cast(cast(A.X.query(''string(.)'') as varchar(11)) as int) as Value
from @TRAB as T
cross apply T.X.nodes(''/r/@*'') as A(X)
) as T
pivot(min(T.Value) for Name in ('[email protected]+')) as P
option (maxdop 1)';
exec sp_executesql @S1, N'@T dbo.TType readonly', @T;
exec sp_executesql @S2, N'@T dbo.TType readonly', @T;
exec sp_executesql @S3, N'@T dbo.TType readonly', @T;
exec sp_executesql @S4, N'@T dbo.TType readonly', @T;
exec sp_executesql @S5, N'@TRAB dbo.TRABType readonly', @TRAB;
exec sp_executesql @S6, N'@TRAB dbo.TRABType readonly', @TRAB;
exec sp_executesql @S7, N'@TRAB dbo.TRABType readonly', @TRAB;
exec sp_executesql @S8, N'@T dbo.TType readonly', @T;
exec sp_executesql @S9, N'@TRAB dbo.TRABType readonly', @TRAB;
Il solito modo lo faccio è 'SELEZIONA r.value ('@ a', 'int'), r.value ('@ b ',' int '), r.value (' @ c ',' int ') DA @ D APPLICAZIONE CROSS x.nodes ('/r ') come ca (r) 'ma il piano ha la stessa forma (con i nodi TVF dati un costo del sottostrato stimato molto più elevato) –