XML DML query for attribute - tsql

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);

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

understanding complex SP in DB2

I need to make changes to an SP which has a bunch of complex XML functions and what not
Declare ResultCsr2 Cursor For
WITH
MDI_BOM_COMP(PROD_ID,SITE_ID, xml ) AS (
SELECT TC401F.T41PID,TC401F.T41SID,
XMLSERIALIZE(
XMLAGG(
XMLELEMENT( NAME "MDI_BOM_COMP",
XMLFOREST(
trim(TC401F.T41CTY) AS COMPONENT_TYPE,
TC401F.T41LNO AS COMP_NUM,
trim(TC401F.T41CTO) AS CTRY_OF_ORIGIN,
trim(TC401F.T41DSC) AS DESCRIPTION,
TC401F.T41EFR AS EFFECTIVE_FROM,
TC401F.T41EFT AS EFFECTIVE_TO,
trim(TC401F.T41MID) AS MANUFACTURER_ID,
trim(TC401F.T41MOC) AS MANUFACTURER_ORG_CODE,
trim(TC401F.T41CNO) AS PROD_ID,
trim(TC401F.T41POC) AS PROD_ORG_CODE,
TC401F.T41QPR AS QTY_PER,
trim(TC401F.T41SBI) AS SUB_BOM_ID,
trim(TC401F.T41SBO) AS SUB_BOM_ORG_CODE, --ADB01
trim(TC401F.T41VID) AS SUPPLIER_ID,
trim(TC401F.T41SOC) AS SUPPLIER_ORG_CODE,
TC401F.T41UCT AS UNIT_COST
)
)
) AS CLOB(1M)
)
FROM TC401F TC401F
GROUP BY T41PID,T41SID
)
SELECT
RowNum, '<BOM_INBOUND>' ||
XMLSERIALIZE (
XMLELEMENT(NAME "INTEGRATION_MESSAGE_CONTROL",
XMLFOREST(
'FULL_UPDATE' as ACTION,
'POLARIS' as COMPANY_CODE,
TRIM(TC400F.T40OCD) as ORG_CODE,
'5' as PRIORITY,
'INBOUND_ENTITY_INTEGRATION' as MESSAGE_TYPE,
'POLARIS_INTEGRATION' as USERID,
'TA' as RECEIVER,
HEX(Generate_Unique()) as SOURCE_SYSTEM_TOKEN
),
XMLELEMENT(NAME "BUS_KEY",
XMLFOREST(
TRIM(TC400F.T40BID) as BOM_ID,
TRIM(TC400F.T40OCD) as ORG_CODE
)
)
) AS VARCHAR(1000)
)
|| '<MDI_BOM>' ||
XMLSERIALIZE (
XMLFOREST(
TRIM(TC400F.T40ATP) AS ASSEMBLY_TYPE,
TRIM(TC400F.T40BID) AS BOM_ID,
TRIM(TC400F.T40CCD) AS CURRENCY_CODE,
TC400F.T40DPC AS DIRECT_PROCESSING_COST,
TC400F.T40EFD AS EFFECTIVE_FROM,
TC400F.T40EFT AS EFFECTIVE_TO,
TRIM(TC400F.T40MID) AS MANUFACTURER_ID,
TRIM(TC400F.T40MOC) AS MANUFACTURER_ORG_CODE,
TRIM(TC400F.T40OCD) AS ORG_CODE,
TRIM(TC400F.T40PRF) AS PROD_FAMILY,
TRIM(TC400F.T40PID) AS PROD_ID,
TRIM(TC400F.T40POC) AS PROD_ORG_CODE,
TRIM(TC400F.T40ISA) AS IS_ACTIVE,
TRIM(TC400F.T40VID) AS SUPPLIER_ID,
TRIM(TC400F.T40SOC) AS SUPPLIER_ORG_CODE,
TRIM(TC400F.T40PSF) AS PROD_SUB_FAMILY,
CASE TRIM(TC400F.T40PML)
WHEN '' THEN TRIM(TC400F.T40PML)
ELSE TRIM(TC400F.T40PML) || '~' || TRIM(TC403F.T43MDD)
END AS PROD_MODEL
) AS VARCHAR(3000)
)
|| IFNULL(MBC.xml, '') ||
XMLSERIALIZE (
XMLFOREST(
XMLFOREST(
TRIM(TC400F.T40CCD) AS CURRENCY_CODE,
TC400F.T40PRI AS PRICE,
TRIM(TC400F.T40PTY) AS PRICE_TYPE
) AS MDI_BOM_PRICE,
XMLFOREST(
TRIM(TC400F.T40CCD) AS CURRENCY_CODE,
TRIM(TC400F.T40PRI) AS PRICE,
'TRANSACTION_VALUE' AS PRICE_TYPE
) AS MDI_BOM_PRICE,
XMLFOREST(
TRIM(TC400F.T40INA) AS INCLUDE_IN_AVERAGING
) AS MDI_BOM_IMPL_BOM_PROD_FAMILY_AUTOMOBILES
) AS VARCHAR(3000)
)
|| '</MDI_BOM>' ||
'</BOM_INBOUND>' XML
FROM (
SELECT
ROW_NUMBER() OVER (
ORDER BY T40STS
,T40SID
,T40BID
) AS RowNum
,t.*
FROM TC400F t
) TC400F
LEFT OUTER JOIN MDI_BOM_COMP MBC
ON TC400F.T40SID = MBC.SITE_ID
AND TC400F.T40PID = MBC.PROD_ID
LEFT OUTER JOIN TC403F TC403F
ON TC400F.T40PML <> ''
AND TC400F.T40PML = TC403F.T43MDL
WHERE TC400F.T40STS = '10'
AND TC400F.RowNUM BETWEEN
(P_STARTROW + (P_PAGENOS - 1) * P_NBROFRCDS)
AND (P_STARTROW + (P_PAGENOS - 1) * P_NBROFRCDS +
P_NBROFRCDS - 1);
Given above is a cursor declaration in the SP code which I am struggling to understand. The very first WITH itself seems to be mysterious. I have used it along with temporary table names but this is the first time, Im seeing something of this sort which seems to be an SP or UDF? Can someone please guide me on how to understand and make sense out of all this?
Adding further to the question, the actual requirement here is to arrange the data in the XML such a way that that those records which have TC401F.T41SBI field populated should appear in the beginning of the XML output..
This field is being selected as below in the code:
trim(TC401F.T41SBI) AS SUB_BOM_ID. If this field is non-blank, this should appear first in the XML and any records with this field value Blank should appear only after. What would be the best approach to do this? Using ORDER BY in any way does not really seem to help as the XML is actually created through some functions and ordering by does not affect how the items are arranged within the XML. One approach I could think of was using a where clause where TC401F.T41SBI <> '' first then append those records where TC401F.T41SBI = ''
Best I can do is help with the CTE.
WITH
MDI_BOM_COMP(PROD_ID,SITE_ID, xml ) AS (
SELECT TC401F.T41PID,TC401F.T41SID,
This just generates a table named MDI_BOM_COMP with three columns named PROD_ID, SITE_ID, and XML. The table will have one record for each PROD_ID, SITE_ID, and the contents of XML will be an XML snippet with all the components for that product and site.
Now the XML part can be a bit confusing, but if we break it down into it's scalar and aggregate components, we can make it a bit more understandable.
First ignore the grouping. so the CTE retrieves each row in TC401F. XMLELEMENT and XMLFORREST are scalar functions. XMLELEMENT creates a single XML element The tag is the first parameter, and the content of the element is the second in the above example. XMLFORREST is like a bunch of XMLELEMENTs concatenated together.
XMLSERIALIZE(
XMLAGG(
XMLELEMENT( NAME "MDI_BOM_COMP",
XMLFOREST(
trim(TC401F.T41CTY) AS COMPONENT_TYPE,
TC401F.T41LNO AS COMP_NUM,
trim(TC401F.T41CTO) AS CTRY_OF_ORIGIN,
trim(TC401F.T41DSC) AS DESCRIPTION,
TC401F.T41EFR AS EFFECTIVE_FROM,
TC401F.T41EFT AS EFFECTIVE_TO,
trim(TC401F.T41MID) AS MANUFACTURER_ID,
trim(TC401F.T41MOC) AS MANUFACTURER_ORG_CODE,
trim(TC401F.T41CNO) AS PROD_ID,
trim(TC401F.T41POC) AS PROD_ORG_CODE,
TC401F.T41QPR AS QTY_PER,
trim(TC401F.T41SBI) AS SUB_BOM_ID,
trim(TC401F.T41SBO) AS SUB_BOM_ORG_CODE, --ADB01
trim(TC401F.T41VID) AS SUPPLIER_ID,
trim(TC401F.T41SOC) AS SUPPLIER_ORG_CODE,
TC401F.T41UCT AS UNIT_COST
)
)
) AS CLOB(1M)
So in the example, for each row in the table, XMLFORREST creates a list of XML elements, one for each of COMPONENT_TYPE, COMP_NUM, CTRY_OF_ORIGIN, etc. These elements form the content of another XML element MDI_BOM_COMP which is created by XMLELEMENT.
Now for each row in the table we have selected PROD_ID, SITE_ID, and created some XML. Next we group by PROD_ID, and SITE_ID. The aggregation function XMLAGG will collect all the XML for each PROD_ID and SITE_ID, and concatenate it together.
Finally XMLSERIALIZE will convert the internal XML representation to the string format we all know and love ;)
I think I found the answer for my requirement. I had to add an order by field name after XMLELEMENT function

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.

How to use SQL XML and XMLAGG to return multiple xmlelements

In SQL, I need to create xml code that looks like this:
<Phone>
<PhoneTypeCode tc="12">Mobile</PhoneTypeCode>
<Area>801</Area>
<DialNumber>9996666</DialNumber>
</Phone>
<Phone>
<PhoneTypeCode tc="2">Business</PhoneTypeCode>
<Area>801</Area>
<DialNumber>1113333</DialNumber>
</Phone>
using xmlagg, but it is throwing an error on the ',' after p.desc
How does this IBM DB2 SQL function need to be fixed to achieve the above xml?
select
xmlelement(Name "Phone",
xmlagg(xmlelement(name "PhoneTypeCode",
xmlattributes(trim(p.phtype) as "tc"), trim(p.desc)),
xmlelement(name "AreaCode", p.area),
xmlelement(name "DialNumber", p.phone)
)
) as xml
from phone p
where p.entityid = #entity_id
I also wanted to add that it does compile and run with this:
select
xmlelement(Name "Phone",
xmlagg(xmlelement(name "PhoneTypeCode",
xmlattributes(trim(p.phtype) as "tc"), trim(p.desc))
)
) as xml
from phone p
where p.entityid = #entity_id
Here is what it returns:
<Phone>
<PhoneTypeCode tc="12">Mobile</PhoneTypeCode>
<PhoneTypeCode tc="2">Business</PhoneTypeCode>
</Phone>
But of course, I need the Area and DialNumber. It is as if you can't have more than one xmlelement within an xmlagg.
How does this IBM DB2 SQL function need to be fixed to achieve the above xml?
Firstly, you may want to count your parentheses. Normally one would want as many closing parentheses as there are opening parentheses.
Secondly, you don't need XMLAGG() at all. You'd use it when inserting multiple elements of the same type, based on multiple relational records, into a single outer element, like
<phones>
<phone no="1" .../>
<phone no="2" .../>
...
</phones>
For you something like this should work:
with phone (phtype, desc, area, phone) as
(values ('home','blah','555','555-5555'),('office','blah','555','555-1111'))
select
xmlelement(
Name "Phone",
xmlelement(
name "PhoneTypeCode",
xmlattributes(
trim(p.phtype) as "tc"
),
trim(p.desc)
),
xmlelement(name "AreaCode", p.area),
xmlelement(name "DialNumber", p.phone)
) as xml
from phone p

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

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`