I was able to find a solution using dynamic SQL by reading another post: Query XML creating field names whithout knowing node names
I ended up using a cursor as well. I've never had to do that, ever, but it seemed practical here.
Here's my stored proc, if anyone is interested.
CREATE PROC [dbo].[myproc]
@VisitType UNIQUEIDENTIFIER
AS
/*************************************************************
THIS PROCEDURE RETURNS ALL OF THE XML FIELDS FOR EACH VISIT
IN A PARTICULAR VISIT SET.
EXAMPLE:
SourceID FacilityID AccountNumber LanguageResp Ethnicity
--------- ---------- ------------- ------------ ---------
AGH AGH V0000001 ENG NON
AGH AGH V0000099 ENG NON
**************************************************************/
/***********************************************
A GLOBAL TEMP TABLE STORES THE XML DATA AS WE
PULL IT OUT OF THE XML COLUMN, RECORD BY RECORD
(VISIT BY VISIT)
***********************************************/
BEGIN
IF (SELECT object_id('tempdb..##TempAudit')) IS NOT NULL
BEGIN
DROP TABLE ##TempAudit
END
/***********************************************
@FooTable WILL CONTAIN THE IDs and XML
COLUMN FOR THE RECORDS THAT WE WANT TO WORK
WITH.
SINCE ONE VISIT CAN HAVE MULTIPLE RECORDS
IN THIS TABLE WE CAN'T USE VISIT ID FOR OUR ID; WE HAVE TO USE
RECORD ID
***********************************************/
declare @FooTable table
(
ID_FIELD INT,
XML_FIELD xml
)
/***********************************************
WE NEED TO FIGURE OUT THE XML STRUCTURE (FIELD
NAMES) FOR THE VISIT TYPE WE ARE WORKING WITH.
EVERY XML FIELD WILL HAVE THE SAME STRUCTURE FOR
A GIVEN VISIT TYPE, SO WE JUST NEED TO LOOK AT THE
FIRST VISIT FOR THAT VISIT TYPE.
************************************************/
declare @FirstRecordID INT --@FirstVisitID VARCHAR(30)
SET @FirstRecordID = (SELECT TOP 1 RecordID FROM VisitTable WHERE VisitType = @VisitType)
/***********************************************
POPULATE @FooTable
***********************************************/
insert into @FooTable
(
ID_FIELD
,XML_FIELD
)
SELECT
RecordID
,VisitDataXML
FROM
dbo.VisitTable
WHERE
VisitType = @VisitType
and RecordID = @FirstRecordID --and VisitID = @FirstVisitID
declare @KnownName varchar(100)
SET @KnownName = 'visit'
/***********************************************
FIND THAT FIRST XLM VALUE/DOCUMENT
***********************************************/
declare @ID INT --varchar(30)
SET @ID = @FirstRecordID -- @FirstVisitID
-- Variable to hold the XML to process
declare @XML xml
select @XML = XML_FIELD
from @FooTable
where ID_FIELD = @ID
/***********************************************
WE NEED TO USE DYNAMIC SQL IN ORDER TO CONSTRUCT
OUR SELECT STATEMENT BECAUSE AT DESIGN TIME
WE DO NOT KNOW THE NAMES OF THE XLM ATTRIBUTES;
THEY ARE DIFFERENT FOR EACH VISIT TYPE
************************************************/
-- Variable for dynamic SQL
declare @SQL nvarchar(max)
-- Build the query
select @SQL = 'select '+stuff(
(
select ',T.N.value('''+T.N.value('local-name(.)', 'sysname')+'[1]'', ''varchar(max)'') as '+T.N.value('local-name(.)', 'sysname')
from @XML.nodes('/root/*[local-name(.)=sql:variable("@KnownName")]/*') as T(N)
for xml path(''), type
).value('.', 'nvarchar(max)'), 1, 1, '')+
' from @XML.nodes(''/root/*[local-name(.)=sql:variable("@KnownName")]'') as T(N)'
/***********************************************
WE ALSO NEED TO USE DYNAMIC SQL TO BUILD THE
THE TEMP TABLE FOR STORING THE RESULTS OF
WHEN WE RUN THE DYNAMIC QUERY
************************************************/
--Build the temp table:
declare @SQL2 nvarchar(max)
select @SQL2 =
'CREATE TABLE ##TempAudit (' + stuff(
(select ' ' + T.N.value('local-name(.)', 'sysname') + ' VARCHAR(MAX),'
from @XML.nodes('/root/*[local-name(.)=sql:variable("@KnownName")]/*') as T(N)
for xml path(''), type
).value('.', 'nvarchar(max)'), 1, 1, '')
--TRIM THE LAST COMMA OFF THE END OF THE TABLE STATEMENT:
SELECT @SQL2 = LEFT(@SQL2,LEN(@SQL2)-1)
--ADD A CLOSING ')' TO THE TABLE STATEMENT
SELECT @SQL2 = @SQL2 + ')'
/**************************************************************
CREATE THE TEMP TABLE BY EXECUTING THE DYMANIC SQL STATEMENT
**************************************************************/
exec sp_executesql @SQL2
--DECLARE @VisitID VARCHAR(30)
DECLARE @RecordID INT
/**************************************************************
USE A CURSOR TO EXECUTE THE XML RETRIEVAL FOR EACH RECORD
IN THE AUDIT
**************************************************************/
DECLARE the_cursor CURSOR FAST_FORWARD
FOR SELECT RecordID --VisitID
FROM dbo.VisitTable
WHERE VisitType = @VisitType
OPEN the_cursor
FETCH NEXT FROM the_cursor INTO @RecordID --@VisitID
WHILE @@FETCH_STATUS = 0
BEGIN
SELECT @XML = VisitDataXML FROM dbo.VisitTable WHERE VistType = @VisitType and RecordID = @RecordID --VisitID = @VisitID
/**************************************************************
RUN OUR DYNAMIC SQL TO PARSE THE XML FIELD
**************************************************************/
INSERT ##TempAudit
exec sp_executesql @SQL,
N'@XML xml, @KnownName varchar(100)',
@XML = @XML,
@KnownName = @KnownName
FETCH NEXT FROM the_cursor INTO @RecordID --@VisitID
END
CLOSE the_cursor
DEALLOCATE the_cursor
/**************************************************************
PERFORM THE FINAL SELECT
*************************************************************/
SELECT * FROM ##TempAudit
END