How to add namespace to SQL Server generated XML output - sql

I have the below XML output generated using a SQL Server query (added in the rextester link):
<Main>
<ID>1001</ID>
<details>
<name>John</name>
<age>12</age>
</details>
</Main>
I want to know how to add a namespace xmlns:json="http://www.samplenamespace.com/json" to the Main node.
Desired output:
<Main xmlns:json="http://www.samplenamespace.com/json">
<ID>1001</ID>
<details>
<name>John</name>
<age>12</age>
</details>
</Main>
Rextester link: http://rextester.com/OQZH6668
Any help!?

You need to use the WITH XMLNAMESPACES clause, for example:
---Fake tables in Stored procedure1
create table #Cdetails(cid int, name varchar(5), age int)
insert into #Cdetails
values(1001,'John',12),
(1002,'Rick',19),
(1003,'Diane',25),
(1004,'Kippy',26)
--Output of Stored procedure
create table #final(xml_data xml)
insert into #final
select
XML_data =
(select ID = cd1.cid,
details =
(
select cd.name,
cd.age
from #Cdetails cd
where cd.cid = cd1.cid
For XML Path(''), Type)
from #Cdetails cd1
For XML Path('Main'));
WITH XMLNAMESPACES ('http://www.samplenamespace.com/json' as json)
select * from #final
For XML Path('Main')
drop table #Cdetails,#final
Note the extra ; that is required when using WITH statements.
Rextester link: http://rextester.com/EBLL48414

Related

Need a where clause for an XML Node in a SQL Server 2019 stored procedure

I have 1.5 million XML documents stored in a SQL Server 2019 database and I need to have a where clause that has multiple nodes in a stored procedure.
<PROJECTS xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<row>
<APPLICATION_ID>1015</APPLICATION_ID>
<ORG_STATE>SC</ORG_STATE>
<ORG_CITY>Charleston</ORG_CITY>
<ORG_ZIPCODE>29407</ORG_ZIPCODE>
<PIS>
<PI>
<PI_NAME>BO, LEO (contact)</PI_NAME>
<PI_ID>9983950 (contact)</PI_ID>
</PI>
<PI>
<PI_NAME>KUZ, BEN I</PI_NAME>
<PI_ID>1862593</PI_ID>
</PI>
</PIS>
<PROJECT_START>08/15/2019</PROJECT_START>
<PROJECT_END>05/31/2024</PROJECT_END>
<INDIRECT_COST_AMT>103034</INDIRECT_COST_AMT>
<TOTAL_COST>638854</TOTAL_COST>
<TOTAL_COST_SUB_PROJECT />
</row>
</PROJECTS>
I need to pull all XML files where PI_ID equals 9983950. The number of PI's in the PIS node could be one or 5.
I'm using this code:
SELECT TOP 100
[APPLICATION_ID], [FileName], [XMLData],
nref.value('ORG_CITY[1]', 'VARCHAR(30)') as ORG_CITY
FROM
[NIH_EXPORTER].[dbo].[ADMIN_Exporter_Files_XML]
CROSS APPLY
XMLData.nodes('//row[1]') AS R(nref)
WHERE
nref.value('ORG_CITY[1]', 'VARCHAR(30)') = 'Charleston'
when I need the city but I'm not sure how to find the value when there are multiple nodes
Please try the following solution.
SQL
-- DDL and sample data population, start
DECLARE #tbl TABLE (ID INT IDENTITY PRIMARY KEY, xmldata XML);
INSERT INTO #tbl (xmldata) VALUES
(N'<PROJECTS xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<row>
<APPLICATION_ID>1015</APPLICATION_ID>
<ORG_STATE>SC</ORG_STATE>
<ORG_CITY>Charleston</ORG_CITY>
<ORG_ZIPCODE>29407</ORG_ZIPCODE>
<PIS>
<PI>
<PI_NAME>BO, LEO (contact)</PI_NAME>
<PI_ID>9983950 (contact)</PI_ID>
</PI>
<PI>
<PI_NAME>KUZ, BEN I</PI_NAME>
<PI_ID>1862593</PI_ID>
</PI>
</PIS>
<PROJECT_START>08/15/2019</PROJECT_START>
<PROJECT_END>05/31/2024</PROJECT_END>
<INDIRECT_COST_AMT>103034</INDIRECT_COST_AMT>
<TOTAL_COST>638854</TOTAL_COST>
<TOTAL_COST_SUB_PROJECT/>
</row>
</PROJECTS>');
-- DDL and sample data population, end
DECLARE #PI_ID VARCHAR(20) = '9983950';
SELECT ID
, xmldata.value('(/PROJECTS/row/ORG_CITY/text())[1]', 'VARCHAR(30)') as ORG_CITY
FROM #tbl
WHERE xmldata.exist('/PROJECTS/row/PIS/PI/PI_ID[contains(./text()[1], sql:variable("#PI_ID"))]') = 1;
You can check if any required PIS/PI node exists with CROSS APPLY down the chain.
SELECT TOP 100
[APPLICATION_ID], [FileName], [XMLData],
nref.value('ORG_CITY[1]', 'VARCHAR(30)') as ORG_CITY
FROM
[NIH_EXPORTER].[dbo].[ADMIN_Exporter_Files_XML]
CROSS APPLY
XMLData.nodes('//row[1]') AS R(nref)
--
cross apply (select top(1) null x
from R.nref.nodes('./PIS/PI') t(n)
where t.n.value('./PI_ID[1]', 'VARCHAR(30)') like '9983950%' ) t
--
WHERE
nref.value('ORG_CITY[1]', 'VARCHAR(30)') = 'Charleston'

Update Temp table to Insert node in XML data using SQL

My question is related to this question. However I am writing a clean scenario on which I need help. I am a beginner in SQL and pardon me If I'm incorrect anywhere.
I have a procedure(huge on. replicating a small sample here) that spits out bunch of XMLs. On my second procedure, based on Parametric condition I have to insert a node in the XML for every CID and do a final select. I am adding a Rextester link below:
UPDATED LINK: http://rextester.com/EFGQB11125
Current output:
<Main>
<ID>1001</ID>
<details>
<name>John</name>
<age>12</age>
</details>
</Main>
<Main>
<ID>1002</ID>
<details>
<name>Rick</name>
<age>19</age>
</details>
</Main>
<Main>
<ID>1003</ID>
<details>
<name>Diane</name>
<age>25</age>
</details>
</Main>
<Main>
<ID>1004</ID>
<details>
<name>Kippy</name>
<age>26</age>
</details>
</Main>
Desired output:
When #type = 'N'/'U' then
<Main>
<ID>1001</ID>
<type>N</type>
<details>
<name>John</name>
<age>12</age>
</details>
</Main>
<Main>
<ID>1002</ID>
<type>U</type>
<details>
<name>Rick</name>
<age>19</age>
</details>
</Main>
<Main>
<ID>1003</ID>
<type>N</type>
<details>
<name>Diane</name>
<age>25</age>
</details>
</Main>
<Main>
<ID>1004</ID>
<type>N</type>
<details>
<name>Kippy</name>
<age>26</age>
</details>
</Main>
So, for every CID, based on #type values, a node should be inserted with that value. Any help?!
(Sorry for being redundant by any chance)
The trick used in the example below uses a bit of Dynamic Sql magic.
Basically building 1 big SQL with all the updates for each ID.
declare #Sql varchar(max);
select #Sql = concat(#Sql,'UPDATE #final SET xml_data.modify(''insert <type>',ctype,'</type> after (/Main/ID)[text()="',cid,'"][1]'');',CHAR(13),CHAR(10))
from #tbl;
-- select #Sql as xml_modify_sqls;
exec(#Sql);
select * from #final;
And here's some SQL to setup the temporary tables with the sample data:
IF OBJECT_ID('tempdb..#tbl') IS NOT NULL DROP TABLE #tbl;
create table #tbl (cid int, ctype varchar(5));
IF OBJECT_ID('tempdb..#cdetails') IS NOT NULL DROP TABLE #cdetails;
create table #cdetails (cid int, name varchar(5), age int);
IF OBJECT_ID('tempdb..#final') IS NOT NULL DROP TABLE #final;
create table #final (xml_data xml);
insert into #tbl (cid, ctype) values
(1001,'N'), (1002,'U'), (1003,'N'), (1004,'N');
insert into #cdetails (cid, name, age) values
(1001,'John',12), (1002,'Rick',19), (1003,'Diane',25), (1004,'Kippy',26);
insert into #final (xml_data)
select xml_data
from (
select
cd1.cid as ID,
-- type = t.ctype as type,
details =
(
select
cd.name,
cd.age
from #cdetails cd
where cd.cid = cd1.cid
For XML Path(''), Type
)
from #cdetails cd1
join #tbl t on cd1.cid = t.cid
For XML Path('Main')
) q(xml_data);
select * from #final;
In your case it is much easier to read the data out of the XML and re-build it from scratch:
;WITH cte AS
(
select m.value(N'(ID/text())[1]','int') AS ID
,m.value(N'(details/name)[1]','nvarchar(max)') AS DetailsName
,m.value(N'(details/age)[1]','int') AS DetailsAge
,t.ctype
from #final f
CROSS APPLY f.xml_data.nodes(N'/Main') A(m)
LEFT JOIN #tbl t ON t.cid=m.value(N'(ID/text())[1]','int') --left join, because I don't know if and ID is found as CID
)
SELECT ID
,ctype AS [type]
,DetailsName AS [details/name]
,DetailsAge AS [details/age]
FROM cte
FOR XML PATH('Main')
returns
<Main>
<ID>1001</ID>
<type>N</type>
<details>
<name>John</name>
<age>12</age>
</details>
</Main>
<Main>
<ID>1002</ID>
<type>U</type>
<details>
<name>Rick</name>
<age>19</age>
</details>
</Main>
<Main>
<ID>1003</ID>
<type>N</type>
<details>
<name>Diane</name>
<age>25</age>
</details>
</Main>
<Main>
<ID>1004</ID>
<type>N</type>
<details>
<name>Kippy</name>
<age>26</age>
</details>
</Main>

SQL Server - Replace text in XML variable based on join

For my situation, I have similar XML to the following:
DECLARE #MyXML XML
SET #MyXML =
'
<Students>
<Student>
<Nickname>Cat</Nickname>
<Name>Catherine</Name>
</Student>
<Student>
<Nickname>Cat</Nickname>
<Name>Joseph</Name>
</Student>
</Students>
'
SELECT T.Students.value('Nickname[1]', 'varchar(20)')
FROM #MyXML.nodes('/Students/Student[Nickname = "Cat"]') T(Students)
JOIN dbo.MyStudents CLASS ON T.Students.value('Name[1]', 'varchar(20)') = CLASS.StudentName
I want to update the records that get returned from this SELECT statement, replacing the Nickname with CLEARED. So, if dbo.MyStudents has Joseph but not Catherine, the resulting #MyXML variable would be:
<Students>
<Student>
<Nickname>Cat</Nickname>
<Name>Catherine</Name>
</Student>
<Student>
<Nickname>CLEARED</Nickname>
<Name>Joseph</Name>
</Student>
</Students>
Is there a way to do this by modifying the XML variable directly? Or will I need to put the XML variable into a temp table and perform a .modify on the temp table?
So far, examples that I have found do not have the conditional update with .modify based on the join.
Give this example a try. For more info see Syntax of the FOR XML Clause.
-- sample #MyStudents table
declare #MyStudents table (StudentName varchar(90), NickName varchar(90));
insert #MyStudents values('Kent', '');
insert #MyStudents values('Robert', 'Bobby');
insert #MyStudents values('Lisa', 'LB');
insert #MyStudents values('Venkat', 'Big Cat');
insert #MyStudents values('Catherine', 'Cat');
insert #MyStudents values('Joseph', 'Joey');
DECLARE #MyXML XML
SET #MyXML =
'<Students>
<Student>
<Nickname>Cat</Nickname>
<Name>Catherine</Name>
</Student>
<Student>
<Nickname>Joey</Nickname>
<Name>Joseph</Name>
</Student>
<Student>
<Nickname>Bonzo</Nickname>
<Name>Clifford</Name>
</Student>
</Students>'
select #MyXML = (
select (
select * from (
-- set existing #MyStudent records to CLEARED
select
'CLEARED' as NickName,
T.Students.value('Name[1]', 'varchar(20)') as Name
from #MyXML.nodes('/Students/Student') T(Students)
join #MyStudents CLASS ON T.Students.value('Name[1]', 'varchar(20)') = CLASS.StudentName
union
-- get students that aren't in #MyStudents table
select
T.Students.value('Nickname[1]', 'varchar(20)') as NickName,
T.Students.value('Name[1]', 'varchar(20)') as Name
from #MyXML.nodes('/Students/Student') T(Students)
where not exists(select 1 from #MyStudents s where s.StudentName = T.Students.value('Name[1]', 'varchar(20)'))
) as t1
for xml path('Student'), type)
for xml path('Students'), type)

SQL: How can I get the value of an attribute in XML datatype?

I have the following xml in my database:
<email>
<account language="en" ... />
</email>
I am using something like this now: but still have to find the attribute value.
SELECT convert(xml,m.Body).query('/Email/Account')
FROM Mail
How can I get the value of the language attribute in my select statement with SQL?
Use XQuery:
declare #xml xml =
'<email>
<account language="en" />
</email>'
select #xml.value('(/email/account/#language)[1]', 'nvarchar(max)')
declare #t table (m xml)
insert #t values
('<email><account language="en" /></email>'),
('<email><account language="fr" /></email>')
select m.value('(/email/account/#language)[1]', 'nvarchar(max)')
from #t
Output:
en
fr
This should work:
DECLARE #xml XML
SET #xml = N'<email><account language="en" /></email>'
SELECT T.C.value('#language', 'nvarchar(100)')
FROM #xml.nodes('email/account') T(C)
It depends a lot on how you're querying the document. You can do this, though:
CREATE TABLE #example (
document NText
);
INSERT INTO #example (document)
SELECT N'<email><account language="en" /></email>';
WITH XmlExample AS (
SELECT CONVERT(XML, document) doc
FROM #example
)
SELECT
C.value('#language', 'VarChar(2)') lang
FROM XmlExample CROSS APPLY
XmlExample.doc.nodes('//account') X(C);
DROP TABLE #example;
EDIT after changes to your question.
if the xml data is stored in sql server as a string column then use cast
select cast(your_field as XML)
.value('(/email/account/#language)[1]', 'varchar(20)')
from your_table

How to remove column name when selecting from a XMLTYPE column using FOR XML PATH

I have a table with a XMLTYPE column named 'InvoiceXML'.
The data in this column is XML in the form:
<Invoice CustomerNum="1234" >
<CustomDeliveryDetails />
</Invoice>
When I do a
SELECT ... FOR XML PATH(''), ROOT('Invoices')
I end up with:
<Invoices>
<InvoiceXML>
<Invoice CustomerNum="1234" >
<CustomDeliveryDetails />
</Invoice>
</InvoiceXML>
</Invoices>
How do I stop the column name InvoiceXML appearing in the output?
declare #T table (invoiceXML xml)
insert into #T values (
'<Invoice CustomerNum="1234" >
<CustomDeliveryDetails />
</Invoice>
')
insert into #T values (
'<Invoice CustomerNum="4321" >
<CustomDeliveryDetails />
</Invoice>
')
select (select T.invoiceXML)
from #T as T
for xml path(''), root('Invoices')
Edit 1 The subquery (select T.invoiceXML) has no column name so it is removed.
Try:
SELECT cast(cast(InvoiceXML as nvarchar(max)) + '' as XML)
FROM whatever
FOR XML PATH(''), ROOT('Invoices')
Try this:
SELECT InvoiceXML.query('//Invoice')
FROM <YOUR_TABLE>
FOR XML PATH('')
Try specifying a xpath query to invoice in the FPR XML PATH
e.g: `FOR XML PATH('//InvoiceXML')`