Querying XML Node with a where criteria - sql-server

I have a table with following attributes:
[Students]
ID Class Data (xml)
1 Secondary XML Data
Below is a sample xml structure that I store on the data attribute.
<Root>
<Data>
<Name>John</Name>
<Rank>1</Rank>
</Data>
<Data>
<Name>Peter</Name>
<Rank>2</Rank>
</Data>
</Root>
I want to write a sql query which will give me following data from the table:
ID, Class and Rank 2 student Name
My Query is
Select ID,Class,Data.value('(/Root/Data/Name)[1]', 'NVARCHAR(3)') AS [Rank 2 Student Name]
FROM [Students]
This works fine, but hardcoding the the node number to 1 is not a good idea here becuase if I add a new Data node in my xml then the data will change. And this is a possibility. So is there a way I can specify that return Name for the node where Rank value is 2.
Thanks.

you can use this:
DECLARE #tbl TABLE (xmlcol xml )
INSERT INTO #tbl
VALUES ('<Root>
<Data>
<Name>John</Name>
<Rank>1</Rank>
</Data>
<Data>
<Name>Peter</Name>
<Rank>2</Rank>
</Data>
</Root>')
SELECT *
FROM
(
SELECT r.d.value ('(./Name/text())[1]', 'varchar(50)') [Name],
r.d.value ('(./Rank/text())[1]', 'varchar(50)') [Rank]
FROM #tbl
CROSS APPLY xmlcol.nodes ('/Root/Data') R(d)
) P
WHERE P.[Rank] = 2
which will output the following result:
Name Rank
Peter 2

You can apply filter in xml as follows:
DECLARE #Students TABLE(ID int, Class varchar(10), Data xml)
INSERT #Students VALUES (1,'A', '<Root>
<Data>
<Name>John</Name>
<Rank>1</Rank>
</Data>
<Data>
<Name>Peter</Name>
<Rank>2</Rank>
</Data>
</Root>')
SELECT ID,Class,Data.value('(/Root/Data[Rank=2]/Name)[1]', 'NVARCHAR(5)') AS [Rank 2 Student Name]
FROM #Students
Result
ID Class Rank 2 Student Name
----------- ---------- -------------------
1 A Peter
Edit. If Rank contains free text, use quotes:
DECLARE #Students TABLE(ID int, Class varchar(10), Data xml)
INSERT #Students VALUES (1,'A', '<Root>
<Data>
<Name>John</Name>
<Rank>1</Rank>
</Data>
<Data>
<Name>Peter</Name>
<Rank>0:Company name</Rank>
</Data>
</Root>')
SELECT ID,Class,Data.value('(/Root/Data[Rank="0:Company name"]/Name)[1]', 'NVARCHAR(5)') AS [Rank 2 Student Name]
FROM #Students

Related

Create XML nodes for values in SQL

I'm having an issue to create XML nodes. Help is much appreciated !
This is a sample code
declare #tbl as table
(
employeeName nvarchar(50),
payFrequency nvarchar(50)
)
insert into #tbl
select 'John', 'Monthly'
union
select 'Carl', 'Biweekly'
select
employeeName AS 'Company/Employee',
payFrequency AS 'Company/PayFrequency'
from #tbl
for xml path ('employees'), root('paySchedule')
above code creates this output:
<paySchedule>
<employees>
<Company>
<Employee>John</Employee>
<PayFrequency>Monthly</PayFrequency>
</Company>
</employees>
<employees>
<Company>
<Employee>Carl</Employee>
<PayFrequency>Biweekly</PayFrequency>
</Company>
</employees>
</paySchedule>
I want to get the "paymentFrequency" values as a node. Is there a way to do this?
<paySchedule>
<employees>
<Company>
<Employee>John</Employee>
<PayFrequency>
<Monthly/>
</PayFrequency>
</Company>
</employees>
<employees>
<Company>
<Employee>Carl</Employee>
<PayFrequency>
<Biweekly/>
</PayFrequency>
</Company>
</employees>
</paySchedule>
You can use a CASE conditional for each possibility, returning an empty string when you want that node, and null otherwise.
SELECT
t.employeeName AS [Company/Employee],
CASE WHEN t.payFrequency = 'Monthly' THEN '' END AS [Company/PayFrequency/Monthly],
CASE WHEN t.payFrequency = 'Biweekly' THEN '' END AS [Company/PayFrequency/Biweekly]
FROM #tbl t
FOR XML PATH('employees'), ROOT('paySchedule'), TYPE;
You can do this also in a nested FOR XML.
SELECT
t.employeeName AS [Company/Employee],
(
SELECT
CASE WHEN t.payFrequency = 'Monthly' THEN '' END AS Monthly,
CASE WHEN t.payFrequency = 'Biweekly' THEN '' END AS Biweekly
FOR XML PATH(''), TYPE
) AS [Company/PayFrequency]
FROM #tbl t
FOR XML PATH('employees'), ROOT('paySchedule'), TYPE;
db<>fiddle
Note that <Monthly></Monthly> and <Monthly /> are semantically equivalent.
Please try the following solution.
It is using XQuery's FLWOR expression to compose the desired XML.
SQL
-- DDL and sample data population, start
DECLARE #tbl as table (employeeName NVARCHAR(50), payFrequency NVARCHAR(50));
INSERT INTO #tbl VALUES
('John', 'Monthly'),
('Carl', 'Biweekly');
-- DDL and sample data population, end
SELECT (
SELECT * FROM #tbl
FOR XML PATH('r'), TYPE, ROOT('root')
).query('<paySchedule>
{
for $r in /root/r
return <employees>
<Company>
<Employee>{data($r/employeeName)}</Employee>
<PayFrequency>
{
if ($r/payFrequency/text()="Monthly") then <Monthly/>
else <Biweekly/>
}
</PayFrequency>
</Company>
</employees>
}
</paySchedule>');
Output
<paySchedule>
<employees>
<Company>
<Employee>John</Employee>
<PayFrequency>
<Monthly />
</PayFrequency>
</Company>
</employees>
<employees>
<Company>
<Employee>Carl</Employee>
<PayFrequency>
<Biweekly />
</PayFrequency>
</Company>
</employees>
</paySchedule>

Select into xml from table in sql server with distinct id

I have table that contains three columns:
[ID],[Name],[Value]
My select for xml result:
DECLARE #xml XML
SET #xml = (
SELECT
ID
,[Name]
,[Value]
FROM [CustomerDetails]
WHERE ID = 1
FOR XML PATH(''), ROOT('Customer')
)
SELECT #xml
My select return xml with multiple ID properties:
<Customer>
<ID>1</ID>
<Name>FirstName</Name>
<Value>firstName</Value>
<ID>1</ID>
<Name>LastName</Name>
<Value>lastName</Value>
<ID>1</ID>
<Name>Age</Name>
<Value>20</Value>
<ID>1</ID>
<Name>City</Name>
<Value>London</Value>
</Customer>
I need next xml:
<Customer>
<ID>1</ID>
<Name>FirstName</Name>
<Value>firstName</Value>
<Name>LastName</Name>
<Value>lastName</Value>
<Name>Age</Name>
<Value>20</Value>
<Name>City</Name>
<Value>London</Value>
</Customer>
How to return this kind of XML?
I have shortened name of columns:
declare #id int = 1
select id, n, v from
(select #id id, null n, null v, 1 as rn from t
union
select null, n, v, 2 as rn from t
where id = #id
) t order by rn
for xml path(''), root('customer')
Output:
<customer><id>1</id><n>n1</n><v>v1</v><n>n2</n><v>v2</v><n>n3</n><v>v3</v></customer>
Fiddle http://sqlfiddle.com/#!3/70ea0/4

TSQL Inserting records from XML string

I have a SQL query that is inserting records into a table from an XML string that I pass to it. The string could contain 1 node or multiple so each one is a new record.
Here is my XML string:
<root>
<data>
<segment>
<trainingEventID>9</trainingEventID>
<localeID>641</localeID>
<numOfTeammates>12</numOfTeammates>
<nonProdHrs>21</nonProdHrs>
<segmentDate>10/10/2014</segmentDate>
<trainers>
<trainer>
<empID>HUS123</empID>
</trainer>
<trainer>
<empID>Dan123</empID>
</trainer>
</trainers>
</segment>
</data>
<data>
<segment>
<trainingEventID>9</trainingEventID>
<localeID>641</localeID>
<numOfTeammates>12</numOfTeammates>
<nonProdHrs>21</nonProdHrs>
<segmentDate>10/25/2014</segmentDate>
<trainers>
<trainer>
<empID>HUS123</empID>
</trainer>
<trainer>
<empID>Dan123</empID>
</trainer>
</trainers>
</segment>
</data>
</root>
Every segment is a new record that is added into the table.
Now, I have a separate table called trainers. For each trainer, I need to also insert a record into that table but it needs to have the last inserted record id of the segment.
Here is my query:
INSERT INTO myTable(trainingEventID, localeID, segmentDate, numofTeammates, nonProdHrs)
SELECT ParamValues.x1.value('trainingEventID[1]', 'INT'),
ParamValues.x1.value('localeID[1]', 'INT'),
ParamValues.x1.value('segmentDate[1]', 'DATE'),
ParamValues.x1.value('numOfTeammates[1]', 'INT'),
ParamValues.x1.value('nonProdHrs[1]', 'FLOAT')
FROM #xml.nodes('/root/data/segment') AS ParamValues(x1);
How can I go about inserting the trainers into another table with the record ID that was created from the segment insert?
Given the clarification of this statement in the question:
For each trainer, I need to also insert a record into that table but it needs to have the last inserted record id of the segment.
being (as found in the comments on the question):
There would be a total of 4 records inserted into the trainer table, 2 have the segment id of 1 and the other 2 with the segment id of 2.
The following will insert this data into related tables that have auto-incrementing IDs. In the sample data, I varied the EmpID values slightly to make it clearer that it is indeed working as expected.
DECLARE #DocumentID INT, #ImportData XML;
SET #ImportData = N'
<root>
<data>
<segment>
<trainingEventID>9</trainingEventID>
<localeID>641</localeID>
<numOfTeammates>12</numOfTeammates>
<nonProdHrs>21</nonProdHrs>
<segmentDate>10/10/2014</segmentDate>
<trainers>
<trainer>
<empID>HUS123</empID>
</trainer>
<trainer>
<empID>Dan123</empID>
</trainer>
</trainers>
</segment>
</data>
<data>
<segment>
<trainingEventID>9</trainingEventID>
<localeID>641</localeID>
<numOfTeammates>12</numOfTeammates>
<nonProdHrs>21</nonProdHrs>
<segmentDate>10/25/2014</segmentDate>
<trainers>
<trainer>
<empID>HUS1234</empID>
</trainer>
<trainer>
<empID>Dan1234</empID>
</trainer>
</trainers>
</segment>
</data>
</root>';
DECLARE #Segment TABLE (SegmentId INT NOT NULL IDENTITY(1, 1) PRIMARY KEY,
TrainingEventID INT NOT NULL, -- Unique
LocaleID INT NOT NULL, -- Unique
NumOfTeammates INT,
NonProdHrs INT,
SegmentDate DATE); -- Unique
-- Ideally create UNIQUE INDEX with the 3 fields noted above
DECLARE #Trainer TABLE (TrainerId INT NOT NULL IDENTITY(1, 1) PRIMARY KEY,
SegmentID INT NOT NULL, -- FK to Segment.SegmentID
EmpID VARCHAR(50) NOT NULL);
EXEC sp_xml_preparedocument #DocumentID OUTPUT, #ImportData;
-- First pass: extract "Segment" rows
INSERT INTO #Segment
(TrainingEventID, LocaleID, NumOfTeammates, NonProdHrs, SegmentDate)
SELECT TrainingEventID, LocaleID, NumOfTeammates, NonProdHrs, SegmentDate
FROM OPENXML (#DocumentID, N'/root/data/segment', 2)
WITH (TrainingEventID INT './trainingEventID/text()',
LocaleID INT './localeID/text()',
NumOfTeammates INT './numOfTeammates/text()',
NonProdHrs INT './nonProdHrs/text()',
SegmentDate DATE './segmentDate/text()');
-- Second pass: extract "Trainer" rows
INSERT INTO #Trainer (SegmentID, EmpID)
SELECT seg.SegmentID, trnr.EmpID
FROM OPENXML (#DocumentID, N'/root/data/segment/trainers/trainer', 2)
WITH (TrainingEventID INT '../../trainingEventID/text()',
LocaleID INT '../../localeID/text()',
SegmentDate DATE '../../segmentDate/text()',
EmpID VARCHAR(50) './empID/text()') trnr
INNER JOIN #Segment seg
ON seg.[TrainingEventID] = trnr.[TrainingEventID]
AND seg.[LocaleID] = trnr.[LocaleID]
AND seg.[SegmentDate] = trnr.[SegmentDate];
EXEC sp_xml_removedocument #DocumentID;
-------------------
SELECT * FROM #Segment ORDER BY [SegmentID];
SELECT * FROM #Trainer ORDER BY [SegmentID];
Output:
SegmentId TrainingEventID LocaleID NumOfTeammates NonProdHrs SegmentDate
1 9 641 12 21 2014-10-10
2 9 641 12 21 2014-10-25
TrainerId SegmentID EmpID
1 1 HUS123
2 1 Dan123
3 2 HUS1234
4 2 Dan1234
References:
OPENXML
sp_xml_preparedocument
sp_xml_removedocument

tsql populate xml tag when there is no data

Is there a way to generate the xml tag when there is no data retrieved for that row?
For example:
Select
firstname,
middlename,
lastname
from
Names
for xml path('name'), type)
Outputs:
<name>
<firstname>John</firstname>
<middlename>Jim</middlename>
<lastname>Smith</lastname>
</name>
But if there is no middle name it skips that row in the xml.
<name>
<firstname>Jane</firstname>
<lastname>Smith</lastname>
</name>
I'm looking for this output if there is no data for middle name:
<name>
<firstname>Jane</firstname>
<middlename />
<lastname>Smith</lastname>
</name>
Can this be achieved?
Thanks!
You could try something like this:
SELECT
FirstName,
MiddleName = ISNULL(MiddleName, ''),
LastName
FROM
#input
FOR XML PATH('name')
which gives you
<name>
<FirstName>John</FirstName>
<MiddleName></MiddleName>
<LastName>Smith</LastName>
</name>
You can do this using XSNIL like this:
DECLARE #DataSource TABLE
(
[FirstName] NVARCHAR(32)
,[MiddleName] NVARCHAR(32)
,[LastName] NVARCHAR(32)
)
INSERT INTO #DataSource ([FirstName], [MiddleName], [LastName])
VALUES ('John','Smith','Tomas')
,('John',NULL,'Tomas')
,('John',NULL,NULL)
,(NULL,'Smith','Tomas')
SELECT [FirstName]
,[MiddleName]
,[LastName]
FROM #DataSource
FOR XML PATH('NAME'), ELEMENTS XSINIL, TYPE

XML Parsing & T-SQL

Given the following from an XML field in a table:
<View>
<Criminal xmlns="http://tempuri.org/crimes.xsd">
<Person>
<PersonID>1234</PersonID>
<LastName>SMITH</LastName>
<FirstName>KEVIN</FirstName>
<Cases>
<PersonID>1234</PersonID>
<CaseNumber>12CASE34</CaseNumber>
</Cases>
</Person>
</Criminal>
</View>
How would I pull the Person/PersonID, LastName, Firstname info? Same goes for the CaseNumber.
My next issue is similar to above but lets add a second namespace:
<MessageContent xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Content>Content in here!!</Content>
<Type>Empty</Type>
</MessageContent>
Notice I have 2 namespaces in there AND they also have ":xsi" and ":xsd" in there too. I think those are referred to as schemas.
Try this:
DECLARE #table TABLE (ID INT NOT NULL, XmlContent XML)
INSERT INTO #table VALUES(1, '<View>
<Criminal xmlns="http://tempuri.org/crimes.xsd">
<Person>
<PersonID>1234</PersonID>
<LastName>SMITH</LastName>
<FirstName>KEVIN</FirstName>
<Cases>
<PersonID>1234</PersonID>
<CaseNumber>12CASE34</CaseNumber>
</Cases>
</Person>
</Criminal>
</View>')
;WITH XMLNAMESPACES('http://tempuri.org/crimes.xsd' AS ns)
SELECT
PersonID = XmlContent.value('(/View/ns:Criminal/ns:Person/ns:PersonID)[1]', 'int'),
FirstName = XmlContent.value('(/View/ns:Criminal/ns:Person/ns:FirstName)[1]', 'varchar(50)'),
LastName = XmlContent.value('(/View/ns:Criminal/ns:Person/ns:LastName)[1]', 'varchar(50)')
FROM #table
WHERE ID = 1
Returns an output of:
And for your second part of the question: yes, you have two namespaces defined - but they're not being used at all - so you can basically just ignore them:
INSERT INTO #table VALUES(2, '<MessageContent xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Content>Content in here!!</Content>
<Type>Empty</Type>
</MessageContent>')
SELECT
Content = XmlContent.value('(/MessageContent/Content)[1]', 'varchar(50)'),
Type = XmlContent.value('(/MessageContent/Type)[1]', 'varchar(50)')
FROM #table
WHERE ID = 2
Returns:

Resources