简体   繁体   中英

Combine multiple literal substring in xquery sql server

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.

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM