How to avoid namespace in child nodes using FOR XML PATH? - tsql

I want to create a sitemap xml file (including images) directly from the database without another process (like transformation or another trick).
My query is:
;WITH XMLNAMESPACES(
DEFAULT 'http://www.sitemaps.org/schemas/sitemap/0.9',
'http://www.google.com/schemas/sitemap-image/1.1' as [image] )
SELECT
(SELECT
'mysite' as [loc],
(select
'anotherloc'
as [image:loc]
for XML path('image:image'), type
)
for xml path('url'), type
)
for xml path('urlset'), type
Returns:
<urlset xmlns:image="http://www.google.com/schemas/sitemap-image/1.1" xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url xmlns:image="http://www.google.com/schemas/sitemap-image/1.1" xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<loc>mysite</loc>
<image:image xmlns:image="http://www.google.com/schemas/sitemap-image/1.1" xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<image:loc>anotherloc</image:loc>
</image:image>
</url>
</urlset>
But I need this output, without repeated namespace declaration:
<urlset xmlns:image="http://www.google.com/schemas/sitemap-image/1.1" xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url>
<loc>mysite</loc>
<image:image>
<image:loc>anotherloc</image:loc>
</image:image>
</url>
</urlset>

I'm sure you realise that the additional otiose namespace declarations don't change the meaning of the XML document, so if the result is going to be consumed by an XML-conformant tool, they shouldn't matter. Nevertheless I know there are some tools out there which don't do XML Namespaces correctly, and in a large XML instance superfluous repeated namespace declarations can bloat the size of the result significantly, which may cause its own problems.
In general there is no getting around the fact that each SELECT...FOR XML statement within the scope of a WITH XMLNAMESPACES prefix will generate namespace declarations on the outermost XML element(s) in its result set, in all XML-supporting versions of SQL Server up to SQL Server 2012.
In your specific example, you can get fairly close to the desired XML by separating the SELECTs rather than nesting them, and using the ROOT syntax for the enveloping root element, thus:
DECLARE #inner XML;
WITH XMLNAMESPACES('http://www.google.com/schemas/sitemap-image/1.1' as [image])
SELECT #inner =
(
SELECT
'anotherloc' AS [image:loc]
FOR XML PATH('image:image'), TYPE
)
;WITH XMLNAMESPACES(
DEFAULT 'http://www.sitemaps.org/schemas/sitemap/0.9'
)
SELECT
'mysite' AS [loc],
#inner
FOR XML PATH('url'), ROOT('urlset'), TYPE
The result being:
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url>
<loc>mysite</loc>
<image:image xmlns:image="http://www.google.com/schemas/sitemap-image/1.1" xmlns="">
<image:loc>anotherloc</image:loc>
</image:image>
</url>
</urlset>
But this approach doesn't provide a completely general solution to the problem.

You can use UDF. Example:
ALTER FUNCTION [dbo].[udf_get_child_section] (
#serviceHeaderId INT
)
RETURNS XML
BEGIN
DECLARE #result XML;
SELECT #result =
(
SELECT 1 AS 'ChildElement'
FOR XML PATH('Child')
)
RETURN #result
END
GO
DECLARE #Ids TABLE
(
ID int
)
INSERT INTO #Ids
SELECT 1 AS ID
UNION ALL
SELECT 2 AS ID
;WITH XMLNAMESPACES (DEFAULT 'http://www...com/content')
SELECT
[dbo].[udf_get_child_section](ID)
FROM
#Ids
FOR XML PATH('Parent')
Result:
<Parent xmlns="http://www...com/content">
<Child xmlns="">
<ChildElement>1</ChildElement>
</Child>
</Parent>
<Parent xmlns="http://www...com/content">
<Child xmlns="">
<ChildElement>1</ChildElement>
</Child>
</Parent>

Maybe too late for answer, but this is a quick solution.
`DECLARE #PageNumber Int = 1;
DECLARE #siteMapXml XML ;
;WITH XMLNAMESPACES (
'http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd' as "schemaLocation",
'http://www.w3.org/2001/XMLSchema-instance' as xsi,
'http://www.google.com/schemas/sitemap-image/1.1' as [image],
DEFAULT 'http://www.sitemaps.org/schemas/sitemap/0.9'
)
SELECT #siteMapXml = (
SELECT
Slug loc,
convert(varchar(300),[Image]) as [image:image/image:loc]
,
Convert(char(10), UpdatedOnUtc, 126) as lastmod,
'hourly' as changefreq,
'0.5' as priority
FROM Products(NOLOCK)
WHERE Pagenumber = #PageNumber
FOR XML PATH ('url'), ROOT ('urlset'))
SELECT #siteMapXml = REPLACE(CAST(#siteMapXml AS NVARCHAR(MAX)), ' xmlns:schemaLocation=', ' xsi:schemaLocation=')
SELECT #siteMapXml`

Related

Postgres use xpath_table parsing with xmlnamespaces

Can I use xpath_table parsing with xmlnamespaces
drop table if exists _xml;
create temporary table _xml (fbf_xml_id serial,str_Xml xml);
insert into _xml(str_Xml)
select '<DataSet1 xmlns="http://tempuri.org/DataSet_LocalMaMC.xsd">
<Stations>
<ID>5</ID>
</Stations>
<Stations>
<ID>1</ID>
</Stations>
<Stations>
<ID>2</ID>
</Stations>
<Stations>
<ID>10</ID>
</Stations>
<Stations>
<ID/>
</Stations>
</DataSet1>' ;
drop table if exists _y;
create temporary table _y as
SELECT *
FROM xpath_table('FBF_xml_id','str_Xml','_xml',
'/DataSet1/Stations/ID',
'true') AS t(FBF_xml_id int,ID text);
select * from _y
If I take of the xmlnamespaces it works fine.
I thought to work with Xpath, but when there is null, it gives me wrong results.
With Postgres 10 or later, xmltable() is the preferred way to do this.
You can easily specify a list of namespaces with that.
SELECT fbf_xml_id, xt.id
FROM _xml
cross join
xmltable(xmlnamespaces ('http://tempuri.org/DataSet_LocalMaMC.xsd' as x),
'/x:DataSet1/x:Stations'
passing str_xml
columns
id text path 'x:ID') as xt
Note that in the XPath expression used for the xmltable() function, the tags are prefixed with the namespace alias defined in the xmlnamespaces option even though they are not prefixed in the input XML.
Online example

Oracle XML DB with XMLEXISTS don't work with xmlns on root XML

I have a table with XMLTYPE column and the XMLEXIST works fine.
But when the root element have a xmlns atribute the XMLEXISTS don't find the record what i'm looking for.
Without the xmlns attribute the XMLEXISTS works fine.
What's wrong?
Example:
<employe>
<employe_id>12345</employe_id>
<employe_name>John</employe_name>
</employe>
The Query:
SELECT count(*) FROM mytable
WHERE XMLEXISTS('/employe[employe_id="12345"]'
PASSING xmltype_col
)
Result: 1
But with xmlns attribute on the root element the query return 0.
<employe xmlns="http://www.example.com/version_01_01_00">
<employe_id>12345</employe_id>
<employe_name>John</employe_name>
</employe>
The mytable is schemaless.
After much research, I discovered the solution.
select *
from (
select
xmlelement("employe",
XMLATTRIBUTES('www.example.com/version_01_01_00' as "xmlns") ,
xmlforest(
'12345' as "employe_id",
'John' as "employe_name"
)
) test
from dual
) x
where XMLEXISTS(
'declare default element namespace "www.example.com/version_01_01_00"; (::)
/employe[employe_id="12345"]'
PASSING test
)

TSQL Group By Issue causing duplicates

I have a query that I created which outputs projects as well as who is presenting the project. My issue is that I am trying to group the presenters to the project ID so it lists the presenters under the project ID and not include it multiple times.
Here is my query:
DECLARE #agendaID AS INT = '23';
SELECT
(SELECT att.empID,
emp.firstName,
emp.lastName,
emp.ntid
FROM dbo.BS_ProjectReview_Attendees AS att
LEFT OUTER JOIN
dbo.EmployeeTable AS emp
ON att.empID = emp.EmpID
WHERE att.agendaID = #agendaID
FOR XML PATH ('attendee'), TYPE, ELEMENTS, ROOT ('attendees')),
(SELECT pres.intID,
int.intTitle,
(SELECT emp.firstName,
emp.lastName,
emp.ntid
FROM dbo.EmployeeTable AS emp
WHERE emp.EmpID = pres.empID
FOR XML PATH ('presenter'), TYPE, ELEMENTS, ROOT ('presenters'))
FROM dbo.BS_ProjectReview_ProjectPresenters AS pres
INNER JOIN dbo.BS_Initiatives AS int
ON pres.intID = int.intID
WHERE pres.agendaID = #agendaID
GROUP BY int.intID <----- ISSUE IS HERE
FOR XML PATH ('project'), TYPE, ELEMENTS, ROOT ('projects'))
FOR XML PATH ('data'), TYPE, ELEMENTS, ROOT ('root');
Here is my XML OUTPUT:
<root>
<data>
<attendees>
<attendee>
<empID>1234</empID>
<firstName>Mike</firstName>
<lastName>Smith</lastName>
<ntid>msmith</ntid>
</attendee>
</attendees>
<projects>
<project>
<intID>202</intID>
<intTitle>Infrastructure Expansion</intTitle>
<presenters>
<presenter>
<firstName>Bob</firstName>
<lastName>Jones</lastName>
<ntid>bjones</ntid>
</presenter>
</presenters>
</project>
<project>
<intID>202</intID>
<intTitle>Infrastructure Expansion</intTitle>
<presenters>
<presenter>
<firstName>User 1</firstName>
<lastName>Last 1</lastName>
<ntid>ulast1</ntid>
</presenter>
</presenters>
</project>
</projects>
</data>
</root>
Here is the desired output:
<root>
<data>
<attendees>
<attendee>
<empID>1234</empID>
<firstName>Mike</firstName>
<lastName>Smith</lastName>
<ntid>msmith</ntid>
</attendee>
</attendees>
<projects>
<project>
<intID>202</intID>
<intTitle>Infrastructure Expansion</intTitle>
<presenters>
<presenter>
<firstName>Bob</firstName>
<lastName>Jones</lastName>
<ntid>bjones</ntid>
</presenter>
<presenter>
<firstName>User 1</firstName>
<lastName>Last 1</lastName>
<ntid>ulast1</ntid>
</presenter>
</presenters>
</project>
</projects>
</data>
</root>
It should be listing both presenters under the same intID however its duplicating them.
I am getting an error with the group by clause as its not contained in an aggregate function and wants me to include pres.empID but then it would not make it unique as they are all different.
Any ideas?
This may be solved using this, however, since SQLFiddle is down ATM, no example.
DECLARE #agendaID AS INT = '23';
SELECT
(SELECT att.empID,
emp.firstName,
emp.lastName,
emp.ntid
FROM dbo.BS_ProjectReview_Attendees AS att
LEFT OUTER JOIN
dbo.EmployeeTable AS emp
ON att.empID = emp.EmpID
WHERE att.agendaID = #agendaID
FOR XML PATH ('attendee'), TYPE, ELEMENTS, ROOT ('attendees')),
(SELECT pres.intID,
int.intTitle,
(SELECT emp.firstName,
emp.lastName,
emp.ntid
FROM dbo.EmployeeTable AS emp
INNER JOIN dbo.BS_ProjectReview_ProjectPresenters AS pres ON emp.EmpID = pres.empID
WHERE pres.intID = int.intID
FOR XML PATH ('presenter'), TYPE, ELEMENTS, ROOT ('presenters'))
FROM dbo.BS_Initiatives AS int
WHERE #agendaID IN ( SELECT pres.agendaID FROM dbo.BS_ProjectReview_ProjectPresenters AS pres WHERE pres.intID = int.intID)
FOR XML PATH ('project'), TYPE, ELEMENTS, ROOT ('projects'))
FOR XML PATH ('data'), TYPE, ELEMENTS, ROOT ('root');

using INSERT for an xml statement

how would you go about inserting an xml document of information into an existing table, I cannot figure out how the insert statement would work my code is below:
USE MyGuitarShop;
DECLARE #CustomerUpdate XML;
SET #CustomerUpdate = '
<NewCustomers>
<Customer LastName="Chan" FirstName="Isabella" Password="" EmailAddress="izzychan#yahoo.com"/>
<Customer LastName="Prine" FirstName="John" Password="" EmailAddress="johnprine#gmail.com"/>
<Customer LastName="Kitchen" FirstName="Kathy" Password="" EmailAddress="kathykitchen#sbcglobal.net"/>
</NewCustomers>
'
;
INSERT INTO Customers (LastName, Password, EmailAddress)
VALUES (#CustomerUpdate.value('(/NewCustomers/LastName)[1]', 'varchar(50)'),
(#CustomerUpdate.value('(/NewCustomers/FirstName)[1]', 'varchar(50)'),
(#CustomerUpdate.value('(/NewCustomers/Password)[1]', 'varchar(50)'),
(#CustomerUpdate.value('(/NewCustomers/EmailAddress)[1]', 'varchar(50)');
You're selecting the node LastName from NewCustomers, while NewCustomers contains only Customer nodes, which then contain LastName attribute.
In order to select the last name, use the following query instead:
value('(/NewCustomers/Customer/#LastName)[1]', 'varchar(50)')
Since you're extracting the data from a single XML value, the selection is pretty straightforward:
declare #CustomerUpdate xml;
set #CustomerUpdate = '
<NewCustomers>
<Customer LastName="Chan" FirstName="Isabella" Password="" EmailAddress="izzychan#yahoo.com"/>
<Customer LastName="Prine" FirstName="John" Password="" EmailAddress="johnprine#gmail.com"/>
<Customer LastName="Kitchen" FirstName="Kathy" Password="" EmailAddress="kathykitchen#sbcglobal.net"/>
</NewCustomers>';
select
t.Customer.value('#LastName', 'nvarchar(50)') as [LastName],
t.Customer.value('#FirstName', 'nvarchar(50)') as [FirstName],
t.Customer.value('#Password', 'nvarchar(50)') as [Password],
t.Customer.value('#EmailAddress', 'nvarchar(50)') as [EmailAddress]
from #CustomerUpdate.nodes('(/NewCustomers/Customer)') t(Customer)
If you were selecting the data from a row of XML values, you would have used cross apply instead.
Important note: DON'T STORE PASSWORDS IN PLAIN TEXT IN A DATABASE. If you're actually doing it, you do it wrong. If you don't understand why, learn about hash and salt, or, better, let others handle personal information for you: OpenID is one of the ways of moving the responsibility of securing sensitive data from you to Google-scale companies.

XML DML query for attribute

declare #myDoc xml
set #myDoc = '<Form xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://www.mydomain.org/MySchema.xsd" SectionId="ABCD" Description="Some stuff">
<ProductDescription ProductID="1" ProductName="Road Bike">
<Features>
<Warranty>1 year parts and labor</Warranty>
<Maintenance>3 year parts and labor extended maintenance is available</Maintenance>
</Features>
</ProductDescription>
</Form>'
;WITH XMLNAMESPACES( 'http://www.w3.org/2001/XMLSchema-instance' as xsi, 'http://www.w3.org/2001/XMLSchema' as xsd, DEFAULT 'http://www.mydomain.org/MySchema.xsd' )
SELECT #myDoc.value('/Form[#SectionId][0]', 'varchar')
I need to obtain the attribute value of SectionId as a nvarchar ? how do I do it ?...
T and R
Mark
You could write it even simpler:
;WITH XMLNAMESPACES(DEFAULT 'http://www.mydomain.org/MySchema.xsd')
SELECT #myDoc.value('(/Form/#SectionId)[1]', 'VARCHAR(100)') AS SectionId
Since you're never using/referring to any of the xsi or xsd namespaces, there's no need to declare those.
And since you're only fetching one attribute from one element, there's really no point in using the .nodes() function to create an internal "dummy table", either.
;WITH XMLNAMESPACES( 'http://www.w3.org/2001/XMLSchema-instance' as xsi, 'http://www.w3.org/2001/XMLSchema' as xsd, DEFAULT 'http://www.mydomain.org/MySchema.xsd' )
SELECT Node.value('#SectionId', 'VARCHAR(100)') AS SectionId
FROM #myDoc.nodes('/Form') TempXML (Node);