sql-serverxquery-sql

Use a SQL variable as the entire XQuery expression for the query() method


I would like to declare and set a variable that could be reused as the XQuery expression in a .query() within SQL.

Here is a simplified version of what I'm describing:

DECLARE @xml xml = N'<?xml version="1.0" encoding="utf-16"?>
<Table>
  <Row>
    <Column>123</Column>
  </Row>
</Table>'

DECLARE @string varchar(50) = '//Table/Row'

SELECT @xml.query('{sql:variable("@string")}')

Of course, this causes a syntax error involving the curly brackets in the last line:

XQuery [query()]: Syntax error near '{'

Removing these brackets causes the query to simply return the variable's string as opposed to using the result of the variable to query the XML variable.

Result of removing the curly brackets:

//Table/Row

Desired result:

<Row>
  <Column>123</Column>
</Row>

Is what I'm trying to do possible without using dynamic SQL? If yes, how is this done?


Solution

  • No, this is impossible.

    XQuery is statically typed, which means the whole XQuery is precompiled along with the rest of your query. Therefore there is no eval function, which is what you would otherwise need to basically "execute" a string variable containing your XQuery.

    The best you are going to get without dynamic SQL is to store the separate predicates in variables and use local-name to compare, although this is likely to be slow.

    DECLARE @xml xml = N'<?xml version="1.0" encoding="utf-16"?>
    <Table>
      <Row>
        <Column>123</Column>
      </Row>
    </Table>'
    
    DECLARE @pred1 nvarchar(4000) = 'Table',
            @pred2 nvarchar(4000) = 'Row';
    
    SELECT @xml.query('
      /*[local-name(.) eq sql:variable("@pred1")]
      /*[local-name(.) eq sql:variable("@pred2")]
    ');
    

    You would still need to know the "shape" of the query i.e. how deep you are going.

    db<>fiddle