0

In SQL Server, I am trying to get an array element by index. I do that by casting a string to XML and then calling it by index.

SELECT CAST('<D>A1</D><D>a2</D>' AS XML).value('/D[1]','varchar(15)')

My problem is that I want to have an array index in '/D[1]' as an input parameter. This is useful when I want to have input index as a column from a table, something as following:

SELECT CAST('<D>A1</D><D>a2</D>' AS XML).value('/D['+COLUMN_WITH_INT_INDEX+']','varchar(15)') FROM TABLE_X

However, this does not work. I receive an error message saying

The argument 1 of the XML data type method "value" must be a string literal.

All attempts to convert '/D['+COLUMN_WITH_INDEX+']' to the string literal were in vain.

2
  • Try to use sql:variable, an XQuery function in SQL server to use the value from a T-SQL variable in your XQuery code. Commented Dec 8, 2021 at 10:28
  • Or learn.microsoft.com/en-us/sql/xquery/… is what you need. Commented Dec 8, 2021 at 10:45

1 Answer 1

1

You can use sp_executesql to get around the "must be literal" constraint, by putting the variable into the string as a literal.

You do then need to run a variation for each different value though.

I've using two procedures in the examples below. The first handles a single value of the index and filters out any records where the index is different or where there is no result.

create procedure proc_run_for_one (@i int)  as
begin
declare @stmt nvarchar(max);
set @stmt = 'SELECT source, ' + 
            '       cast(@i as nvarchar) + ',' + 
            '       CAST(source AS XML).value(''/D[' 
                     + cast(@i as nvarchar) + ']'',''varchar(15)'') 
            ' FROM source_data '+
            ' WHERE i = ' +  cast(@i as nvarchar)  + 
                 ' AND CAST(source AS XML).value(''/D[' + 
                       cast(@i as nvarchar) + 
                 ']'',''varchar(15)'') IS NOT NUll' ;
    insert into target
    exec sp_executesql @stmt;
end;
go

and the second procedure runs the first procedure for each value. I'm using a simple counter here but a select distinct would probably be more efficient as it would only need to hit values that actually exist.

create procedure proc_run_for_all as
begin
    declare @min int, @max int, @current int;

    select @min = MIN(i), @max = MAX(i) FROM source_data;
    set @current = @min;
    while @current <= @max
      begin
        exec proc_run_for_one @current
        set @current = @current + 1
      end;
end;
go

Tables and sample data are

create table source_data (source nvarchar(max), i int)
go

insert into source_data values ('<D>A1</D><D>a2</D>',1)
go

insert into source_data values ('<D>A1</D><D>a2</D>',2)
go

insert into source_data values ('<D>A1</D><D>a2</D><D>b3</D>',3)
go

create table target(source nvarchar(max), i int, result nvarchar(max));
go

A full SQL fiddle is available at http://sqlfiddle.com/#!18/d3e0da/1

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.