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
sql:variable, an XQuery function in SQL server to use the value from a T-SQL variable in your XQuery code.