I need to select all the elements under a specific node, all the elements have the same name.
Tables
Lets say I have 2 tables in my database;
[Access_Groups], This table contains all the groups that can access my application
[User_Accounts], This table contains user details and an XML file that contains their access groups
I want to see which users have access groups in their XML file, that match the groups in my [Access_Groups] table
XML File
The XML files in [User_Accounts] look like this:
<Profile>
<Name>John Smith</Name>
<Role>Developer</Role>
</Profile>
<Groups>
<String>Group_1</String>
<String>Group_2</String>
<String>Group_3</String>
<String>Group_4</String>
<String>Group_5</String>
<String>Group_6</String>
</Groups>
Query
If i run this following query:
SELECT [XML].value('(//Groups)[1]', 'varchar(max)') AS 'XML'
FROM [User_Accounts]
I will get the following result:
XML |
---|
Group_1Group_2Group_3Group_4Group_5Group_6 |
This is of no use to me as i cannot JOIN this table to [Access_Groups]
If I run this query instead:
SELECT
[XML].value('(//Groups/*)[1]', 'varchar(max)') AS 'XMl_1',
[XML].value('(//Groups/*)[2]', 'varchar(max)') AS 'XMl_2',
[XML].value('(//Groups/*)[3]', 'varchar(max)') AS 'XMl_3',
[XML].value('(//Groups/*)[4]', 'varchar(max)') AS 'XMl_4',
[XML].value('(//Groups/*)[5]', 'varchar(max)') AS 'XMl_5',
[XML].value('(//Groups/*)[6]', 'varchar(max)') AS 'XMl_6'
FROM [User_Accounts]
I will get the following result:
XML_1 | XML_2 | XML_3 | XML_4 | XML_5 | XML_6 |
---|---|---|---|---|---|
Group_1 | Group_2 | Group_3 | Group_4 | Group_5 | Group_6 |
I could JOIN this results set to [Access_Groups], However this is no use to me as i have to define every single column. Some users may have up to 100 groups.
Solution
Is there no way i can do something like this?:
SELECT [Name],
[XML].value('(//Groups)[*]', 'varchar(max)') AS 'XML'
FROM [User_Accounts]
To get a result set like this:
Name | XML |
---|---|
John Smith | Group_1 |
John Smith | Group_2 |
John Smith | Group_3 |
John Smith | Group_4 |
John Smith | Group_5 |
John Smith | Group_6 |
I could then SELECT FROM that results set WHERE IN (SELECT * FROM [ACCESS_GROUPS]) to determine if this user had access to my application
Obviously though:
SELECT [Name],
[XML].value('(//Groups)[*]', 'varchar(max)') AS 'XML'
FROM [User_Accounts]
This does not work!
Has anyone got any idea of how i could compare all the 'String' nodes for each user to my [Access_Groups] table?
Thanks!
This is my first post, so it might be a bit rubbish
CodePudding user response:
This is possible, you need to use nodes()
to expand your XML into rows, e.g.
DECLARE @User_Accounts TABLE ([XML] XML);
INSERT @User_Accounts ([XML]) VALUES('<Profile>
<Name>John Smith</Name>
<Role>Developer</Role>
</Profile>
<Groups>
<String>Group_1</String>
<String>Group_2</String>
<String>Group_3</String>
<String>Group_4</String>
<String>Group_5</String>
<String>Group_6</String>
</Groups>');
SELECT Name = ua.[XML].value('(Profile/Name/text())[1]', 'VARCHAR(100)'),
Role = ua.[XML].value('(Profile/Role/text())[1]', 'VARCHAR(100)'),
GroupName = g.x.value('text()[1]', 'VARCHAR(100)')
FROM @User_Accounts AS ua
CROSS APPLY ua.[XML].nodes('/Groups/String') AS g (x);