SELECT FOR XML AUTO and return datatypes

后端 未结 2 1951
灰色年华
灰色年华 2021-02-20 10:27

During playing with sys.dm_exec_describe_first_result_set I get to this point:

CREATE TABLE #tab(col INT, x XML );
INSERT INTO #tab(col,x) VALUES (1         


        
2条回答
  •  说谎
    说谎 (楼主)
    2021-02-20 10:50

    FOR XML was introduced in SQL Server 2000.

    SQL Server 2000 did not have MAX datatypes or the XML datatype. Nor was it possible to use FOR XML in a sub query.

    The article What does server side FOR XML return? explains

    In SQL Server 2000 ... FOR XML... was implemented in the layer of code between the query processor and the data transport layer ... the query processor produces the result the same way as without FOR XML and then FOR XML code formats the rowset as XML. For maximum XML publishing performance FOR XML does steaming XML formatting of the resulting rowset and directly sends its output to the server side TDS code in small chunks without buffering whole XML in the server space. The chunk size is 2033 UCS-2 characters. Thus, XML larger than 2033 UCS-2 characters is sent to the client side in multiple rows each containing a chunk of the XML. SQL Server uses a predefined column name for this rowset with one column of type NTEXT - “XML_F52E2B61-18A1-11d1-B105-00805F49916B” – to indicate chunked XML rowset in UTF-16 encoding.

    So it appears that this is still implemented the same way for top level FOR XML in later versions too.

    SQL Server 2005 introduced the ability to use FOR XML in subqueries (meaning that these now need to be handled by the query processor rather than a layer outside it whilst streaming the results to the client)

    The same article explains that these will be typed as NVARCHAR(MAX) or XML dependant on the presence or not of a type directive.

    As well as the datatype difference this does mean the additional SELECT wrapper can make a drastic difference in performance if #tab is big.

    /*Can be streamed straight out to client without using server storage*/
    SELECT col
    FROM #tab
    FOR XML AUTO
    
    /*XML constructed in its entirety in tempdb first*/
    SELECT(SELECT col
    FROM #tab
    FOR XML AUTO) AS wrapped_subquery
    

    It is possible to see the different approaches in the call stacks as well as execution plans.

    Directly streamed

    sqllang.dll!CXMLExecContext::AddTagAndAttributes()  + 0x5a9 bytes                   
    sqllang.dll!CXMLExecContext::AddXMLRow()  + 0x2b7 bytes                 
    sqltses.dll!CEsExec::FastMoveEval()  + 0x9c bytes                   
    sqllang.dll!CXStmtQuery::ErsqExecuteQuery()  + 0x280 bytes                  
    sqllang.dll!CXStmtXMLSelect::WrapExecute()  + 0x2d7 bytes                   
    sqllang.dll!CXStmtXMLSelect::XretDoExecute()  + 0x355 bytes                 
    sqllang.dll!CXStmtXMLSelect::XretExecute()  + 0x46 bytes                    
    sqllang.dll!CMsqlExecContext::ExecuteStmts<1,1>()  + 0x368 bytes                    
    sqllang.dll!CMsqlExecContext::FExecute()  + 0x6cb bytes                 
    sqllang.dll!CSQLSource::Execute()  + 0x3ee bytes                    
    sqllang.dll!process_request()  + 0x757 bytes    
    

    With sub query

    sqllang.dll!CXMLExecContext::AddTagAndAttributes()  + 0x5a9 bytes
    sqllang.dll!CXMLExecContext::AddXMLRow()  + 0x2b7 bytes
    sqllang.dll!CForXmlSerialize::ProcessRow()  + 0x19 bytes
    sqllang.dll!CUDXR_Base::PushRow()  + 0x30 bytes
    sqlmin.dll!CQScanUdx::Open()  + 0xd5 bytes
    sqlmin.dll!CQueryScan::StartupQuery()  + 0x170 bytes
    sqllang.dll!CXStmtQuery::SetupQueryScanAndExpression()  + 0x391 bytes
    sqllang.dll!CXStmtQuery::InitForExecute()  + 0x34 bytes
    sqllang.dll!CXStmtQuery::ErsqExecuteQuery()  + 0x217 bytes
    sqllang.dll!CXStmtSelect::XretExecute()  + 0xed bytes
    sqllang.dll!CMsqlExecContext::ExecuteStmts<1,1>()  + 0x368 bytes
    sqllang.dll!CMsqlExecContext::FExecute()  + 0x6cb bytes
    sqllang.dll!CSQLSource::Execute()  + 0x3ee bytes
    sqllang.dll!process_request()  + 0x757 bytes
    

    Both end up calling the same underlying XML code but the "unwrapped" version doesn't have any XML iterators in the plan itself, the result is achieved by replacing method calls from CXStmtSelect with CXStmtXMLSelect instead (represented in the plan as an XML Select root node rather than a plain old Select).


    On SQL Server 2016 CTP3 I still see ntext for top level FOR XML. However top level FOR JSON shows up as nvarchar(max)

    At least in the CTP the JSON special column name still contains the GUID F52E2B61-18A1-11d1-B105-00805F49916B despite the fact that the origin of this is the IXMLDocument Interface.

    The plans look much the same though the XML Select is replaced with a JSON Select


    BTW: On build Microsoft SQL Server 2014 - 12.0.4213.0 (X64) I don't see any difference in behaviour between temp tables and permanent tables. This is probably down to the different @@Version between the environments your question uses http://sqlfiddle.com/ (12.0.2000.8) and https://data.stackexchange.com/ (12.0.4213.0).

    Maybe a bug was fixed in sys.dm_exec_describe_first_result_set between the two 2014 builds.

    On 2012 I get the same results as Shnugo on 11.0.5343.0 (with NULL in the first three rows) but after installing SP3 11.0.6020.0 I get the same as your initial results shown in the question.

提交回复
热议问题