SQL - Blank default namespaces - sql

I have a relation which has an XML column storing data in the following structure
<Report id="b5d9b8da-7af4-4257-b825-b28af91dd833">
<CreatedDate>04-12-2012</CreatedDate>
<LastUpdated>04-12-2012</LastUpdated>
<Reference>abc123</Reference>
</Report>
I'm writing a stored procedure to retrieve all reports and join them and wrap them in a root node called reports. I have the following so far;
WITH XMLNAMESPACES(DEFAULT 'http://www.defaultnamespace.com/1.0')
SELECT
#Xml =
(
SELECT
(
SELECT xml.query('.')
FROM
[database].[Reports]
WHERE
ClientId = #clientId
FOR XML PATH(''),
TYPE
)
FOR XML PATH('Reports'),
TYPE
)
Whilst this returns all the reports in the right format, there exists a blank default namespace on the report element like the following;
<Reports xmlns="http://www.defaultnamespace.com/1.0">
<Report xmlns="" id="b5d9b8da-7af4-4257-b825-b28af91dd833">
<CreatedDate>04-12-2012</CreatedDate>
<LastUpdated>04-12-2012</LastUpdated>
<Reference>abc123</Reference>
</Report>
</Reports>
Could someone explain a suitable way of excluding the namespace on the report element?
Any help is greatly appreciated guys :)

It's a little messy and probably not very efficient but you can redefine namespaces with an XQuery over your intermediate XML.
Instead of using SQL Server's WITH XMLNAMESPACES you declare the default namespace in XQuery, for example...
if object_id(N'Reports') is not null drop table [Reports];
go
create table [Reports] (
[ClientId] int not null,
[xml] [xml] not null
)
go
insert [Reports] ([ClientID], [xml])
values (1, N'<Report id="b5d9b8da-7af4-4257-b825-b28af91dd833">
<CreatedDate>04-12-2012</CreatedDate>
<LastUpdated>04-12-2012</LastUpdated>
<Reference>abc123</Reference>
</Report>');
go
declare #clientId int = 1
select (
select [xml].query('/*:Report')
from [Reports]
where ClientId = #clientId
for xml path('Reports'), type
).query('declare default element namespace "http://www.defaultnamespace.com/1.0";
for $x in /*:Reports return
<Reports>
{
for $y in $x/*:Report return
<Report>
{attribute id {$y/#id}}
{element CreatedDate {$y/*:CreatedDate/text()}}
{element LastUpdated {$y/*:LastUpdated/text()}}
{element Reference {$y/*:Reference/text()}}
</Report>
}
</Reports>')
go
This will return the following block of XML:
<Reports xmlns="http://www.defaultnamespace.com/1.0">
<Report id="b5d9b8da-7af4-4257-b825-b28af91dd833">
<CreatedDate>04-12-2012</CreatedDate>
<LastUpdated>04-12-2012</LastUpdated>
<Reference>abc123</Reference>
</Report>
</Reports>

Your issue is that the column was not stored with a default namespace of "http://www.defaultnamespace.com/1.0".
So the logical name of your tag is NS = "", name =Report.
SQL Server is being absolutely correct.
What you would like to do is to say
by the way, that XML data I stored, I want you to rewrite every tag from the "" namespace logically attached to every name, to a tag of the same relative name in the "http://www.defaultnamespace.com/1.0" namespace, and then make that the default namespace
AFAIK, You can't do that (but if you find a way let me know!). The closest you can get is cast it to nvarchar(max) with no namespace, then cast it back again with the desired namespace applied.

Related

Find a string with in a TEXT type column that contains an XML file and replace a value in SQL Server

Update:
The column that has the XML file has a data type of text.
Update 2 (This helped me use xml functions instead of using charindex to manipulate the data):
I created a temp table #tmpXML (id int, xmlField XML). id is the Primary key of my main table and xmlField is the XML value from the text type field. I used XML functions on the XML data field in the temp table and got all the values I needed and I updated my main table with these values.
I found this method to be more efficient and clean than using sql server functions like charindex and substring. This is the reason why I accepted the answer because it helped me use xml functions. Just wanted to clarify.
I have a column called Settings that contains xml file with user settings.
For example I have 3 users that have the following rows for this column.
XML for the first user id 1:
<owner>
<product userid="1" productid="3" region="North" country="Usa" ></product>
</owner>
XML for the first user id 2:
<owner>
<product userid="2" productid="3" selectedView="true" region="North" state="AZ" country="Usa" ></product>
</owner>
XML for the first user id 3:
<owner>
<product userid="3" productid="3" selectedView="true" region="South" isSelected="true" state="AZ" country="Usa" ></product>
</owner>
As you can see, the attributes can be in any order within the XML files for each user. I have to update all the users to have region = "East". I tried doing it using CharIndex and substring but it's getting too confusing. Any ideas on how to do this? Thanks!
You can use the .modify() XML function for this, using the replace value of syntax
UPDATE users
SET Settings.modify('replace value of (owner/product/#region)[1] with "East"');
db<>fiddle
This only works on one XML document per row.
If you actually have all these nodes in one big blob, you need to run it in a loop
DECLARE #tries int = 0;
WHILE ##ROWCOUNT > 0 AND #tries < 1000
BEGIN
UPDATE users
SET Settings.modify('replace value of (owner/product/#region[. != "East"])[1] with "East"')
WHERE Settings.exist('owner/product/#region[. != "East"]') = 1;
SET #tries += 1;
END;
db<>fiddle
You could use a case statement in the select
Case
When colName like '%NORTH%' then replace (colName, NORTH, NEWTEXT
When colName like '%SOUTH%'
etc
You could also create a stored function
Please try the following solution.
Text is a deprecated data type in the MS SQL Server.
I used NVARCHAR(MAX) data type to distinguish it from the natural XML data type for the XML data.
SQL
DECLARE #tbl TABLE (id int primary key, Settings NVARCHAR(MAX));
INSERT #tbl VALUES
(1,'<owner>
<product userid="1" productid="3" region="North" country="Usa" ></product>
</owner>'),
(2,'<owner>
<product userid="2" productid="3" selectedView="true" region="North" state="AZ" country="Usa" ></product>
</owner>'),
(3,'<owner>
<product userid="3" productid="3" selectedView="true" region="South" isSelected="true" state="AZ" country="Usa" ></product>
</owner>');
WITH rs AS
(
SELECT *
FROM #tbl
CROSS APPLY (SELECT TRY_CAST(Settings AS XML)
.query('<owner><product>
{
for $x in /owner/product/#*
return if(local-name($x) ne "region") then $x
else attribute region {"East"}
}
</product></owner>')) t(x)
)
UPDATE rs
SET rs.Settings = TRY_CAST(x AS NVARCHAR(MAX));
-- test
SELECT * FROM #tbl;

SQL Replace Typed XML Data

I'm working with third-party software that stores an XML document of parameters as a column. I'm trying to write a SQL-Server script that will replace the email address in the XML below.
<ArrayOfKeyValueOfstringanyType xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.microsoft.com/2003/10/Serialization/Arrays">
<KeyValueOfstringanyType>
<Key>Email</Key>
<Value xmlns:d3p1="http://www.w3.org/2001/XMLSchema" i:type="d3p1:string">Michael#dundermifflin.com</Value>
</KeyValueOfstringanyType>
</ArrayOfKeyValueOfstringanyType>
So far, the closest I've gotten is this... It runs and says rows were affected but does nothing.
update t
set XMLColumn.modify('replace value of (/ArrayOfKeyValueOfstringanyType/KeyValueOfstringanyType/Key/Value/string())[1] with "dwight#staples.com"')
After reviewing other posts and Microsoft's documentation (https://learn.microsoft.com/en-us/sql/t-sql/xml/replace-value-of-xml-dml?view=sql-server-ver15#a-replacing-values-in-an-xml-instance --Item D), it seems I'm missing something regarding the namespaces. If I understand the XML correctly, it appears that there are multiple namespaces to declare. After several attempts with no luck, my lack of XML experience has me turning here.
Any help is greatly appreciated!
Please try the following solution.
As you correctly guessed, the culprit was a default namespace.
Also, I had to adjust the XPath expression.
SQL
-- DDL and sample data population, start
DECLARE #tbl TABLE (ID INT IDENTITY PRIMARY KEY, XMLColumn XML);
INSERT INTO #tbl (XMLColumn) VALUES
(N'<ArrayOfKeyValueOfstringanyType xmlns:i="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://schemas.microsoft.com/2003/10/Serialization/Arrays">
<KeyValueOfstringanyType>
<Key>Email</Key>
<Value xmlns:d3p1="http://www.w3.org/2001/XMLSchema"
i:type="d3p1:string">Michael#dundermifflin.com</Value>
</KeyValueOfstringanyType>
</ArrayOfKeyValueOfstringanyType>');
-- DDL and sample data population, end
-- before
SELECT * FROM #tbl;
;WITH XMLNAMESPACES(DEFAULT 'http://schemas.microsoft.com/2003/10/Serialization/Arrays')
UPDATE #tbl
SET XMLColumn.modify('replace value of (/ArrayOfKeyValueOfstringanyType/KeyValueOfstringanyType/Value/text())[1] with "dwight#staples.com"');
-- after
SELECT * FROM #tbl;
You muse declare default namespace
DECLARE #XML XML = N'<ArrayOfKeyValueOfstringanyType xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.microsoft.com/2003/10/Serialization/Arrays">
<KeyValueOfstringanyType>
<Key>Email</Key>
<Value xmlns:d3p1="http://www.w3.org/2001/XMLSchema" i:type="d3p1:string">Michael#dundermifflin.com</Value>
</KeyValueOfstringanyType>
</ArrayOfKeyValueOfstringanyType> '
set #XML.modify('
declare default element namespace "http://schemas.microsoft.com/2003/10/Serialization/Arrays";
replace value of (/ArrayOfKeyValueOfstringanyType/KeyValueOfstringanyType/Value/text())[1] with "dwight#staples.com"')
SELECT #XML

Query to XML Node is giving me NULL Value

Trying to achieve what stored procedure used for each report in Report Server.
INSERT INTO #ReportRawXML
SELECT
ItemID, RPT.[Name] AS ReportName,
CONVERT(XML, CONVERT(VARBINARY(MAX), RPT.Content)) AS XMLdata
FROM
ReportServer.dbo.[Catalog] AS RPT
WHERE
RPT.[Name] LIKE '%catalog%'
XML sample:
<Report>
<DataSets>
<DataSet Name="DSET_ReportRepository">
<Query>
<DataSourceName>CCA_PROD</DataSourceName>
</Query>
</DataSets>
</Report>
I have a table with a XML column which I want to query, but I'm getting NULL back; I tried all possible ways, please someone advice.
SELECT
b.ItemID, b.ReportName,
n.c.value('#DataSourceName', 'VARCHAR(MAX)') AS id,
n.c.value('/DataSourceName[1]', 'VARCHAR(500)') AS DataSourceName,
n.c.value('/CommandType[1]', 'VARCHAR(100)') AS CommandType,
n.c.value('/CommandText[1]', 'VARCHAR(100)') AS CommandText
FROM
#ReportRawXML b
OUTER APPLY
b.XMLdata.nodes('/Report/DataSets/DataSet/Query') AS n(c)
Question: getting NULL in column 3 above
Some hints for your next question:
Please try to add a MCVE (a stand-alone-sample as I provide it here for you) right from the start. You know all your details, but we don't...
Try to add a sample, where the sample includes everything (e.g. your sample does not show any CommandType or CommandText
Please read about the internal formatting tools on SO how to add code, normal text, how to highlight or cite...
Please run your sample yourself. Doing so, you would have found, that the XML provided is not well-formed (missing </DataSet>).
But now to your question:
DECLARE #mockupTable TABLE(ID INT IDENTITY, XMLdata XML);
INSERT INTO #mockupTable VALUES
(N'<Report>
<DataSets>
<DataSet Name="DSET_ReportRepository">
<Query>
<DataSourceName>CCA_PROD</DataSourceName>
</Query>
</DataSet>
</DataSets>
</Report>');
--The query
SELECT b.ID
,ds.value('(Query/DataSourceName/text())[1]', 'varchar(max)') as id
,ds.value('#Name', 'varchar(max)') as id
FROM #mockupTable b
OUTER APPLY b.XMLdata.nodes('/Report/DataSets/DataSet') as n(ds);
Reading from XML you must know, that the # before the name indicates an attribute. To read the DataSet's Name attribute, you need this, but not before DataSourceName as in your own attempt.

T-SQL XML get a value from a node problem?

I have an XML like:
<?xml version="1.0" encoding="utf-16"?>
<ExportProjectDetailsMessage xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.datacontract.org/2004/07/Project">
<CPProjectId>7665699f-6772-424c-8b7b-405b9220a8e7</CPProjectId>
</ExportProjectDetailsMessage>
I'm trying to get the CPProjectId as a Uniqueidentifier using:
DECLARE #myDoc xml
DECLARE #ProdID varchar(max)
SET #myDoc = '<ExportProjectDetailsMessage xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.datacontract.org/2004/07/Project"><CPProjectId>7665699f-6772-424c-8b7b-405b9220a8e7</CPProjectId></ExportProjectDetailsMessage>'
SET #ProdID = #myDoc.value('(ExportProjectDetailsMessage/CPProjectId)[1]', 'varchar(max)' )
SELECT #ProdID
All i can receive is NULL =/
I've tried many combinations on #myDoc.value but no results =/
How can i retrieve the value from my XML ?
Thanks!
--EDIT:
Something that i noted, when i remove the namespace declaration from the XML it works fine!
The problem is that i need this namespaces! =/
You're right the namespace is the issue. You're query is looking for a node ExportProjectDetailsMessage but such a node doesn't exist in your document, because there is a namespace declared as a default in your document. Since you can't remove that (nor should you) you should include it in your XPATH query like so:
set #ProdId = #myDoc.value('
declare namespace PD="http://schemas.datacontract.org/2004/07/Project";
(PD:ExportProjectDetailsMessage/PD:CPProjectId)[1]', 'varchar(max)' )
You may also want to consider not using varchar(max) but perhaps uniqueidentifier
A better way to do this is to simply declare the namespace before each of your queries:
;WITH XMLNAMESPACES(DEFAULT 'http://schemas.datacontract.org/2004/07/Project')
It's like a temporary default. When you run the next query in the batch you'll get nulls again if you don't specify this before each of your selects.
So instead of using "SET", you can use "SELECT" to set the value like so:
;WITH XMLNAMESPACES(DEFAULT 'http://schemas.datacontract.org/2004/07/Project')
SELECT #ProdID = #myDoc.value('(ExportProjectDetailsMessage/CPProjectId)[1]', 'VarChar(MAX)')
SELECT #ProdID
Same results, just more readable and maintainable.
I found the solution here: http://www.sqlservercentral.com/Forums/Topic967100-145-1.aspx#bm967325

How to delete an attribute from an XML variable in sql server 2008?

I have a table called XML (in SQL Server 2008) and it has a field called XmlDocument of type XML. I am trying to to delete an attribute from an XML variable.
Here is how my xml looks like
<clue_personal_auto xmlns="http://cp.com/rules/client">
<admin>
<receipt_date>03/16/2011</receipt_date>
<date_request_ordered>03/16/2011</date_request_ordered>
<report_usage>Personal</report_usage>
</admin>
</clue_personal_auto>
My query
UPDATE XML
SET XmlDocument.modify('delete (/clue_personal_auto/#xmlns)[1]')
WHERE xmlid = 357
When I run this query in query analyzer I see the message "1 row(s) affected" but in reality the xmlns attribute of clue_personal_auto element is not being removed. Any idea what am I doing wrong.
Thanks
BB
You need to use WITH xmlnamespaces, otherwise "/clue_personal_auto" does not match the NAMESPACED clue_personal_auto xmlns="..." node.
Not only that, you cannot actually remove a namespace since it is not a normal attribute.
Example of removing a regular attribute
declare #xml table (xmlid int, xmldocument xml)
insert #xml select 357, '
<clue_personal_auto xmlns="http://cp.com/rules/client" otherattrib="x">
<admin>
<receipt_date>03/16/2011</receipt_date>
<date_request_ordered>03/16/2011</date_request_ordered>
<report_usage>Personal</report_usage>
</admin>
</clue_personal_auto>'
;WITH XMLNAMESPACES ('http://cp.com/rules/client' as ns)
UPDATE #XML
SET XmlDocument.modify('delete (/ns:clue_personal_auto/#otherattrib)[1]')
WHERE xmlid = 357
select * from #xml
UPDATE XML
SET CONVERT(XML, REPLACE(CONVERT(NVARCHAR(MAX), XmlDocument), N' xmlns=...'))
WHERE ID = 357
I can't seem to find an easy way to do this - but the real question remains: why do you want to remove the namespace?? Using the WITH XMLNAMESPACES ... construct, you can easily make use of the namespaces.
Instead of putting a lot of effort in getting rid of it - learn about XML namespaces and start using them!
You can quite easily use that XML namespace in your queries:
;WITH XMLNAMESPACES (DEFAULT 'http://cp.com/rules/client' )
SELECT
XmlDocument.value('(/clue_personal_auto/admin/report_usage)[1]', 'varchar(25)')
FROM XML
WHERE ID = 357
and be happy with it - no need to artificially remove xmlns= declarations anymore!