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>
Related
I can't receive XML in this format:
<ROOT>
<Test ColA="A">B</Test>
</ROOT>
This what I have:
select 'A' as ColumnA, 'B' as ColumnB into _Atest
select ColumnA as [Test/#A]
from _Atest
for XML PATH (''), root ('ROOT')
And output are:
<ROOT>
<Test A="A" />
</ROOT>
How I can receive:
<ROOT>
<Test ColA="A">B</Test>
</ROOT>
"A" - value from ColumnA
B - value from ColumnB
Please try the following.
SQL
-- DDL and sample data population, start
DECLARE #tbl TABLE (ColumnA CHAR(1), ColumnB CHAR(1));
INSERT INTO #tbl (ColumnA, ColumnB) VALUES
('A', 'B');
-- DDL and sample data population, end
SELECT ColumnA AS [Test/#ColA]
, ColumnB AS [Test]
FROM #tbl
FOR XML PATH(''), TYPE, ROOT('ROOT');
Output
<ROOT>
<Test ColA="A">B</Test>
</ROOT>
I have a table which has several columns (Int, Bool) as nullable. I have one stored procedure which is taking XML as the input parameter. I'm trying to pass null values of some of these columns, But its inserting 0 instead of null.
declare #temp XML;
set #temp = '
<ArrayOfTestFileEntity xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<TestFileEntity>
<TestId xsi:nil="true" />
<MainTestNo xsi:nil="true" />
<TestCode xsi:nil="true" />
<TestCode1 />
<FlgTemp xsi:nil="true" />
</TestFileEntity>
</ArrayOfTestFileEntity>'
declare #xmlInput as XML = #temp;
declare #xmlOutput as XML;
BEGIN
SET NOCOUNT ON;
DECLARE #insertedTable table(Id int);
MERGE INTO Test AS Trg USING (
SELECT
d.x.value('TestId[1]', 'int') AS TestId,
d.x.value('MainTestNo[1]', 'int') AS MainTestNo,
d.x.value('TestCode[1]', 'int') AS TestCode,
d.x.value('TestCode1[1]', 'int') AS TestCode1,
d.x.value('FlgTemp[1]', 'bit') AS FlgTemp
FROM
#xmlInput.nodes('/ArrayOfTestFileEntity/TestFileEntity') AS d(x)
) AS Src ON Trg.Id = Src.Id
WHEN Matched THEN
UPDATE SET
Trg.TestId = Src.TestId,
Trg.MainTestNo = Src.MainTestNo,
Trg.TestCode = Src.TestCode,
Trg.TestCode1 = Src.TestCode1,
Trg.FlgTemp = Src.FlgTemp,
WHEN NOT matched BY TARGET THEN
INSERT
([TestId]
,[MainTestNo]
,[TestCode]
,[TestCode1]
,[FlgTemp])
VALUES
(Src.TestId,
Src.MainTestNo,
Src.TestCode,
Src.TestCode1,
Src.FlgTemp)
OUTPUT INSERTED.Id INTO #insertedTable;
set #xmlOutput = (SELECT * FROM #insertedTable for XML AUTO, ROOT('RowsUpserted'));
select #xmlOutput;
END
Basically you need to use [not(#xsi:nil = "true")] when you select value from xml.
So i have applied it in your query. So if you observed the xml you have noticed that i have added value 16 for <TestCode1> tag and keep rest of the tag as it is.
declare #temp XML;
set #temp = '
<ArrayOfTestFileEntity xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<TestFileEntity>
<TestId xsi:nil="true" />
<MainTestNo xsi:nil="true" />
<TestCode xsi:nil="true" />
<TestCode1>16</TestCode1>
<FlgTemp xsi:nil="true" />
</TestFileEntity>
</ArrayOfTestFileEntity>'
DECLARE #xmlInput as XML = #temp;
--MERGE INTO Test AS Trg USING (
SELECT
d.x.value('TestId[1][not(#xsi:nil = "true")]', 'int') AS TestId,
d.x.value('MainTestNo[1][not(#xsi:nil = "true")]', 'int') AS MainTestNo,
d.x.value('TestCode[1][not(#xsi:nil = "true")]', 'int') AS TestCode,
d.x.value('TestCode1[1][not(#xsi:nil = "true")]', 'int') AS TestCode1,
d.x.value('FlgTemp[1][not(#xsi:nil = "true")]', 'bit') AS FlgTemp
FROM
#xmlInput.nodes('/ArrayOfTestFileEntity/TestFileEntity') AS d(x)
In the following code, I want to check if column is a Number. If it is - fill it with leading zeros.
Is there anyway using XML XQuery to check the original data type (int)
of the column?
declare #T table (string nchar(10), id int)
insert #T
select 'test1', 1
insert #T
select 'test2', 2
declare #X xml
SET ANSI_WARNINGS ON
set #X = (select * from #T order by id for xml path('row'), root('root'))
SELECT (
STUFF(
(
SELECT ';' + v.value('.','nvarchar(max)')
FROM r.nodes('*') AS B(v)
FOR XML PATH('')
),1,1,'')
) as [OUTPUT]
FROM #x.nodes('/root/row') AS A(r)
As you know your table you should not use a generical approach, if you do not have a good reason to do so.
What you probably should do is this
Include the formatted value into your XML. This allows you, to carry both information within the structure: typed and formatted.
declare #T table (string nvarchar(10), id int)
insert #T values
('test1', 1)
,('test2', 22)
declare #x xml = (select string
,REPLACE(STR(id,8),' ','0') AS [id/#formatted]
,id
from #T
order by id for xml path('row'), root('root'),TYPE)
SELECT (
STUFF(
(
SELECT ';' + r.value('(string/text())[1]','nvarchar(max)')
+ ';' + r.value('(id/#formatted)[1]','nvarchar(max)')
FOR XML PATH('')
),1,1,'')
) as [OUTPUT]
FROM #x.nodes('/root/row') AS A(r);
If you need the dynamic approach - look at this
declare #T table (string nchar(10), id int)
insert #T values
('test1', 1)
,('test2', 2)
declare #X xml = (select * from #T order by id for xml path('row'), root('root'))
select #x;
The first thing you see, that you - probably want to use NVARCHAR(10) instead of NCHAR(10). You might use LTRIM() too:
<root>
<row>
<string>test1 </string>
<id>1</id>
</row>
<row>
<string>test2 </string>
<id>2</id>
</row>
</root>
Now I start from scratch with NVARCHAR(10)
declare #T2 table (string nvarchar(10), id int);
insert #T2 values
('test1', 1)
,('test2', 22);
declare #X2 xml = (select * from #T2 order by id for xml path('row'), root('root'));
--First try is with ISNUMERIC and CASE WHEN
SELECT (
STUFF(
(
SELECT ';' + CASE WHEN ISNUMERIC(v.value('.','nvarchar(max)'))=1
THEN REPLACE(STR(v.value('.','int'),8),' ','0')
ELSE v.value('.','nvarchar(max)') END
FROM r.nodes('*') AS B(v)
FOR XML PATH('')
),1,1,'')
) as [OUTPUT]
FROM #x2.nodes('/root/row') AS A(r);
But: There are some character formats (scientific notations), which can be taken as numeric incidentically.
Add this to your table and try again
,('3d2',333) --breaks, because SELECT ISNUMERIC('3d2'),ISNUMERIC('1e1') returns 1 (for both)
Other/better approaches
If you are using SQL-Server 2012 or higher, you can use TRY_CAST, which will return NULL instead of an error
SELECT (
STUFF(
(
SELECT ';' + CASE WHEN TRY_CAST(v.value('.','nvarchar(max)') AS INT) IS NOT NULL
THEN REPLACE(STR(v.value('.','int'),8),' ','0')
ELSE v.value('.','nvarchar(max)') END
FROM r.nodes('*') AS B(v)
FOR XML PATH('')
),1,1,'')
) as [OUTPUT]
FROM #x2.nodes('/root/row') AS A(r);
Another option might the the explicit XQuery cast (Find possible XQuery functions here)
SELECT (
STUFF(
(
SELECT ';' + v.query('let $nd:=string(./text()[1])
,$nr:=concat("00000000",string($nd cast as xs:int?))
return if(string-length($nr)=8)
then $nd
else substring($nr,string-length($nr)-7)').value('.','nvarchar(max)')
FROM r.nodes('*') AS B(v)
FOR XML PATH('')
),1,1,'')
) as [OUTPUT]
FROM #x2.nodes('/root/row') AS A(r);
UPDATE
XML is not aware of an underlying type unless you specify a schema. Look at this:
declare #T2 table (string nvarchar(10), id int)
insert #T2 values
('test1', 1)
,('test2', 22)
,('1',333)
declare #x2 xml = (select * from #T2 order by id for xml raw('row'), root('root'),xmlschema)
select #x2;
<root>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:sqltypes="http://schemas.microsoft.com/sqlserver/2004/sqltypes" targetNamespace="urn:schemas-microsoft-com:sql:SqlRowSet1" elementFormDefault="qualified">
<xsd:import namespace="http://schemas.microsoft.com/sqlserver/2004/sqltypes" schemaLocation="http://schemas.microsoft.com/sqlserver/2004/sqltypes/sqltypes.xsd" />
<xsd:element name="row">
<xsd:complexType>
<xsd:attribute name="string">
<xsd:simpleType>
<xsd:restriction base="sqltypes:nvarchar" sqltypes:localeId="1033" sqltypes:sqlCompareOptions="IgnoreCase IgnoreKanaType IgnoreWidth">
<xsd:maxLength value="10" />
</xsd:restriction>
</xsd:simpleType>
</xsd:attribute>
<xsd:attribute name="id" type="sqltypes:int" />
</xsd:complexType>
</xsd:element>
</xsd:schema>
<row xmlns="urn:schemas-microsoft-com:sql:SqlRowSet1" string="test1" id="1" />
<row xmlns="urn:schemas-microsoft-com:sql:SqlRowSet1" string="test2" id="22" />
<row xmlns="urn:schemas-microsoft-com:sql:SqlRowSet1" string="1" id="333" />
</root>
In this case you might ask the schema for the underlying type. But - if I get you correctly - you want to look at any value if it might be a number. This works as shown, but is extremely dangerous...
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:
I can't get the value for the XML attribute 'Country' into my table.
What am I doing wrong?
Here is my XML:
<?xml version="1.0" encoding="utf-8"?>
<CustomerDetails>
<PersonalInfo Country="USA">
<CustID>1001</CustID>
<CustLastName>Smith</CustLastName>
<DOB>2011-05-05T09:25:48.253</DOB>
<Address>
<Addr1>100 Smith St.</Addr1>
<City>New York</City>
</Address>
</PersonalInfo>
</CustomerDetails>
Here is my SQL:
Drop table #Cust
CREATE TABLE #Cust
(CustID INT, CustLastName VARCHAR(10)
, DOB DATETIME, Addr1 VARCHAR(100), City VARCHAR(10), Country VARCHAR(20))
insert into #Cust
select
c3.value('CustID[1]','int'),
c3.value('CustLastName[1]','varchar(10)'),
c3.value('DOB[1]','DATETIME'),
c3.value('(Address/Addr1)[1]','VARCHAR(100)'),
c3.value('(Address/City)[1]','VARCHAR(10)'),
c3.value('Country[1]','VARCHAR(20)')
from
(
select
cast(c1 as xml)
from
OPENROWSET (BULK 'C:\Users\wattronts\Documents\XMLImportTest.xml',SINGLE_BLOB) as T1(c1)
)as T2(c2)
cross apply c2.nodes('/CustomerDetails/PersonalInfo') T3(c3)
Select * from #Cust
Thanks for your help.
Use # to specify that you want an attribute.
T3.c3.value('#Country', 'varchar(50)')