Eu tenho o seguinte código T-SQL que estou tentando fazer funcionar:
IF EXISTS (SELECT * FROM sys.xml_schema_collections WHERE name = 'TST') DROP XML SCHEMA COLLECTION TST;
GO
CREATE XML SCHEMA COLLECTION TST AS N'<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"><xsd:element name="root"><xsd:complexType><xsd:complexContent><xsd:restriction base="xsd:anyType"><xsd:sequence><xsd:element name="val" minOccurs="0" maxOccurs="unbounded" type="xsd:string" /></xsd:sequence></xsd:restriction></xsd:complexContent></xsd:complexType></xsd:element></xsd:schema>';
GO
DECLARE @xml XML(TST) = '<root><val>1</val><val>2</val><val>3</val></root>';
SELECT @xml.value('sum(//val)','INT')
O xsd
nos diz que o val
nó é do tipo string
. Portanto, a sum()
função não funcionará:
Msg 9308, Level 16, State 1, Line 7
XQuery [value()]: The argument of 'sum()' must be of a single numeric primitive type or 'http://www.w3.org/2004/07/xpath-datatypes#untypedAtomic'. Found argument of type 'xs:string *'.
Eu tentei as seguintes maneiras de fazer isso funcionar:
Opção 1 :
Uma solução alternativa é apenas se livrar do esquema xsd:
DECLARE @xml XML(TST) = '<root><val>1</val><val>2</val><val>3</val></root>';
SELECT (CAST(@xml AS XML)).value('sum(//val)','INT')
Isso retorna o resultado correto: 6. No entanto, parece bastante grosseiro e pode nem sempre ser uma opção (por exemplo, ao lidar com índices XML).
(Não-)Opção 2 :
Uma alternativa é lançar cada valor xs:integer
antes de somá-los. No entanto, é aí que estou preso.
É fácil lançar um único elemento:
DECLARE @xml XML(TST) = '<root><val>1</val><val>2</val><val>3</val></root>';
SELECT @xml.value('sum(xs:integer((//val)[1]))','INT')
Mas isso resulta em 1, que é o resultado errado. Isso é esperado, já que agora (por causa do [1]
) apenas o primeiro val
nó é analisado.
(Não-)Opção 3 :
Existem duas maneiras de escrever um elenco em xquery:
DECLARE @xml XML(TST) = '<root><val>1</val><val>2</val><val>3</val></root>';
SELECT @xml.value('sum(xs:integer((//val)))','INT')
GO
DECLARE @xml XML(TST) = '<root><val>1</val><val>2</val><val>3</val></root>';
SELECT @xml.value('sum(((//val) cast as xs:integer ?))','INT')
No entanto, ambos funcionam apenas com singletons como entrada:
Msg 2365, Level 16, State 1, Line 15
XQuery [value()]: Cannot explicitly convert from 'xs:string *' to 'xs:integer'
Msg 2365, Level 16, State 1, Line 18
XQuery [value()]: Cannot explicitly convert from 'xs:string *' to 'xs:integer ?'
(Não-)Opção 4 :
De acordo com a documentação do xquery, você deve ser capaz de fazer isso:
DECLARE @xml XML(TST) = '<root><val>1</val><val>2</val><val>3</val></root>';
SELECT @xml.value('sum((//val)! xs:integer(.))','INT')
No entanto, isso parece não ser implementado pelo SQL Server:
Msg 2217, Level 16, State 1, Line 21
XQuery [value()]: ',' or ')' expected
Opção 5 :
Você poderia usar a .nodes()
função e resolver o problema fora do xquery:
DECLARE @xml XML(TST) = '<root><val>1</val><val>2</val><val>3</val></root>';
SELECT SUM(X.V.value('.','INT')) FROM @xml.nodes('//val') X(V)
Isso resulta no valor correto de 6. No entanto, estou procurando uma solução dentro do xquery, portanto, essa não é uma opção para mim.
Opção? :
Com isso estou sem ideias. Existe uma solução para este problema?
Adicional :
Além disso, gostaria muito de saber o que ?
significa o nesta linha, retirado de um dos exemplos acima:
SELECT @xml.value('sum(((//val)[1] cast as xs:integer ?))','INT')
O SQL Server exige isso. Você recebe este erro, se tentar sem:
Msg 9301, Level 16, State 1, Line 18
XQuery [value()]: In this version of the server, 'cast as <type>' is not available. Please use the 'cast as <type> ?' syntax.
Esse erro sugere o fato de que esse requisito é uma limitação do SQL Server. No entanto, não consegui encontrar documentação sobre seu significado em nenhum lugar.
Você pode fazer isso em xQuery com uma declaração FLWOR e iteração (XQuery) usando cast em cada valor retornado.
Uma consulta como essa pode fazer você pensar que o SQL Server fará algumas operações de loop demoradas, mas se você der uma olhada no plano de consulta, verá que não é o caso. O loop for é transformado em um plano que obtém todos os valores de uma única chamada para uma função com valor de tabela, os converte em um número inteiro e, em seguida, usa um operador agregado para calcular a soma.