1st day on extracting the xml file from SQLServer, have Qs about 'PATH' - sql

I'm reading a SQL fundamental textbook, and below is the T-SQL code:
SELECT Customer.custid AS [#custid],
Customer.companyname AS [companyname]
FROM Sales.Customers AS Customer
WHERE Customer.custid <= 2
ORDER BY Customer.custid
FOR XML PATH ('Customer'), ROOT('Customers');
Can anybody tell me the point of using PATH, and why there is an # before custid?
Any suggestions are welcomed, Thanks

The # marks the column as being an XML attribute instead of an element- ie: you'll get
<Customers>
<Customer custid="1234">
<companyname>Acme Sprockets Inc</companyname>
</Customer>
</Customers>
If you take the # away, you'd get
<Customers>
<Customer>
<custid>1234</custid>
<companyname>Acme Sprockets Inc</companyname>
</Customer>
</Customers>
Path allows you to customise the XML structures, without having to resort to FOR XML EXPLICIT
I'd start with the documentation online : http://technet.microsoft.com/en-us/library/ms189885.aspx

Related

SQL SERVER xml with CDATA

I have a table in my database with a column containing xml. The column type is nvarchar(max). The xml is formed in this way
<root>
<child>....</child>
.
.
<special>
<event><![CDATA[text->text]]></event>
<event><![CDATA[text->text]]></event>
...
</special>
</root>
I have not created the db, I cannot change the way information is stored in it but I can retrieve it with a select. For the extraction I use
select cast(replace(xml,'utf-8','utf-16')as xml)
from table
It works well except for cdata, whose content in the query output is: text -> text
Is there a way to retrieve also the CDATA tags?
Well, this is - as far as I know - not possible on normal ways...
The CDATA section has one sole reason: include invalid characters within XML for lazy people...
CDATA is not seen as needed at all and therefore is not really supported by normal XML methods. Or in other words: It is supported in the way, that the content is properly escaped. There is no difference between correctly escaped content and not-escaped content within CDATA actually! (Okay, there are some minor differences like including ]]> within a CDATA-section and some more tiny specialties...)
The big question is: Why?
What are you trying to do with this afterwards?
Try this. the included text is given as is:
DECLARE #xml XML =
'<root>
<special>
<event><![CDATA[text->text]]></event>
<event><![CDATA[text->text]]></event>
</special>
</root>'
SELECT t.c.query('text()')
FROM #xml.nodes('/root/special/event') t(c);
So: Please explain some more details: What do you really want?
If your really need nothing more than the wrapping CDATA you might use this:
SELECT '<![CDATA[' + t.c.value('.','varchar(max)') + ']]>'
FROM #xml.nodes('/root/special/event') t(c);
Update: Same with outdated FROM OPENXML
I just tried how the outdated approach with FROM OPENXML handles this and found, that there is absolutely no indication in the resultset, that the given text was within a CDATA section originally. The "Some value here" is exactly returned in the same way as the text within CDATA:
DECLARE #doc XML =
'<root>
<child>Some value here </child>
<special>
<event><![CDATA[text->text]]></event>
<event><![CDATA[text->text]]></event>
</special>
</root>';
DECLARE #hnd INT;
EXEC sp_xml_preparedocument #hnd OUTPUT, #doc;
SELECT * FROM OPENXML (#hnd, '/root',0);
EXEC sp_xml_removedocument #hnd;
This is how to include cdata on child nodes in XML, using pure SQL. But; it's not ideal.
SELECT 1 AS tag,
null AS parent,
'10001' AS 'Customer!1!Customer_ID!Element',
'AirBallon Captain' AS 'Customer!1!Title!cdata',
'Customer!1' = (
SELECT
2 AS tag,
NULL AS parent,
'Wrapped in cdata, using explicit' AS 'Location!2!Title!cdata'
FOR XML EXPLICIT)
FOR XML EXPLICIT, ROOT('Customers')
CDATA is included, but Child element is encoded using
>
instead of >
Which is so weird from a sensable point of view. I'm sure there are technical explanations, but they are stupid, because there is no difference in the FOR XML specification.
You could include the option type on the inner child node and then loose cdata too..
BUT WHY OH WHY?!?!?!?! would you (Microsoft) remove cdata, when I just added it?
<Customers>
<Customer>
<Customer_ID>10001</Customer_ID>
<Title><![CDATA[AirBallon Captain]]></Title>
<Location>
<Title><![CDATA[wrapped in cdata, using explicit]]></Title>
</Location>
</Customer>
</Customers>

SQL Server Grabbing Value from XML parameter to use in later query

I am really new to SQL Server and stored procedures to begin with. I need to be able to parse an incoming XML file for a specific element's value and compare/save it later in the procedure.
I have a few things stacked against me. One the Element I need is buried deeply inside the document. I have had no luck in searching for it by name using methods similar to this:
select CurrentBOD = c.value('(local-name(.))[1]', 'VARCHAR(MAX)'),
c.value('(.)[1]', 'VARCHAR(MAX)') from #xml.nodes('PutMessage/payload/content/AcknowledgePartsOrder/ApplicationArea/BODId') as BODtable(c)
It always returns null.
So, I am trying something similar to this:
declare #BODtable TABLE(FieldName VARCHAR(MAX),
FieldValue VARCHAR(MAX))
SELECT
FieldName = nodes.value('local-name(.)', 'varchar(50)'),
FieldValue = nodes.value('(.)[1]', 'varchar(50)')
FROM
#xml.nodes('//*') AS BODtable(nodes)
declare #CurrentBOD VARCHAR(36)
set #CurrentBOD = ''
SET #CurrentBOD = (SELECT FieldValue from #BODtable WHERE FieldName = 'BODId')
This provides me the list of node names and values correctly (I test this in a query and BODtable has all elements listed with the correct values), but when I set #CurrentBOD it comes up null.
Am I missing an easier way to do this? Am I messing these two approaches up somehow?
Here is a part of the xml I am parsing for reference:
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:wsa="http://schemas.xmlsoap.org/ws/2004/03/addressing" xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity- secext-1.0.xsd" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401- wss-wssecurity-utility-1.0.xsd">
<soap:Header>
<payloadManifest xmlns="???">
<c contentID="Content0" namespaceURI="???" element="AcknowledgePartsOrder" version="4.0" />
</payloadManifest>
<wsa:Action>http://www.starstandards.org/webservices/2005/10/transport/operations/PutMessage</wsa:Action>
<wsa:MessageID>uuid:df8c66af-f364-4b8f-81d8-06150da14428</wsa:MessageID>
<wsa:ReplyTo>
<wsa:Address>http://schemas.xmlsoap.org/ws/2004/03/addressing/role/anonymous</wsa:Address>
</wsa:ReplyTo>
<wsa:To>???</wsa:To>
<wsse:Security soap:mustUnderstand="1">
<wsu:Timestamp wsu:Id="Timestamp-bd91e76f-c212-4555-9b23-f66f839672bd">
<wsu:Created>2013-01-03T21:52:48Z</wsu:Created>
<wsu:Expires>2013-01-03T21:53:48Z</wsu:Expires>
</wsu:Timestamp>
<wsse:UsernameToken xmlns:wsu="???" wsu:Id="???">
<wsse:Username>???</wsse:Username>
<wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText">???</wsse:Password>
<wsse:Nonce>???</wsse:Nonce>
<wsu:Created>2013-01-03T21:52:48Z</wsu:Created>
</wsse:UsernameToken>
</wsse:Security>
</soap:Header>
<soap:Body>
<PutMessage xmlns="??????">
<payload>
<content id="???">
<AcknowledgePartsOrder xmlns="???" xmlns:xsi="???" xsi:schemaLocation="??? ???" revision="???" release="???" environment="???n" lang="en-US" bodVersion="???">
<ApplicationArea>
<Sender>
<Component>???</Component>
<Task>???</Task>
<ReferenceId>???</ReferenceId>
<CreatorNameCode>???</CreatorNameCode>
<SenderNameCode>???</SenderNameCode>
<DealerNumber>???</DealerNumber>
<PartyId>???</PartyId>
<LocationId />
<ServiceId />
</Sender>
<CreationDateTime>2013-01-03T21:52:47</CreationDateTime>
<BODId>71498800-c098-4885-9ddc-f58aae0e5e1a</BODId>
<Destination>
<DestinationNameCode>???</DestinationNameCode>
You need to respect the XML namespaces!
First of all, your target XML node <BODId> is inside the <soap:Envelope> and <soap:Body> tags - both need to be included in your selection.
Secondly, both the <PutMessage> as well as the <AcknowledgePartsOrder> nodes appear to have default XML namespaces (those xmlns=.... without a prefix) - and those must be respected when you select your data using XPath.
So assuming that <PutMessage xmlns="urn:pm"> and <AcknowledgePartsOrder xmlns="urn:apo"> (those are just guesses on my part - replace with the actual XML namespaces that you haven't shown use here), you should be able to use this XPath to get what you're looking for:
;WITH XMLNAMESPACES('http://schemas.xmlsoap.org/soap/envelope/' AS soap,
'urn:pm' AS ns, 'urn:apo' AS apo)
SELECT
XC.value('(apo:BODId)[1]', 'varchar(100)')
FROM
#YourXmlVariable.nodes('/soap:Envelope/soap:Body/ns:PutMessage/ns:payload/ns:content/apo:AcknowledgePartsOrder/apo:ApplicationArea') AS XT(XC)
This does return the expected value (71498800-c098-4885-9ddc-f58aae0e5e1a) in my case.

Xml node name Parsing using SQL

I need to get the value of the tag "supervisor....." The problem is that those tag name are dynamic, they change for each person. I have tried using Extract value but it works only if you use a static tag name.
It is in Oracle environment.
Thanks for your help.
Ourson
XML Extract
<?xml version="1.0" encoding="UTF-8"?>
<TRANSACTION>
<TransCtx>
<supervisorCalcAttributes classType="Ht">
<SupervisorId10977 classType="s">Matt, Clinton</SupervisorId10977>
<SupervisorId4753 classType="s">Bob, Sponge</SupervisorId4753>
<FND_ENTERPRISE_ID classType="s">1</FND_ENTERPRISE_ID>
</supervisorCalcAttributes>
</TransCtx>
</TRANSACTION>
SQL Query
select
Extractvalue( xmltype('<root>'||txndata.data_cache||'</root>'),
'root/TRANSACTION[1]/TransCtx[1]/supervisorCalcAttributes[1]/**LineItem**[1]' ) as test
from hrc_txn_data txndata, hrc_txn_header txnhe
where txnhe.transaction_id=txndata.transaction_id

Add additional nodes in XML

I create an XML document using the below code
For Each cust As Customer In Customers
XDoc = <?xml version="1.0" encoding="UTF-16" standalone="yes"?>
<Customers>
<Customer>
<Name>Mike</Name>
<Age>0</Age>
</Customer>
</Customers>
Next
XDoc.Save("C:\myXmlfile.xml")
However it only seems to add one record, but i dont know how to readd the nodes for each record? So if theres 2 records then i would expect
<?xml version="1.0" encoding="UTF-16" standalone="yes"?>
<Customer>
<Name>Mike</Name>
<Age>0</Age>
</Customer>
<Customer>
<Name>Mike</Name>
<Age>0</Age>
</Customer>
but it should only generate one xml file.
Could anyone guide me in what i need to do please? Even after searching around im not 100% sure as theres too many methods and ive probably got confused.
Thanks
Inside the loop you set XDoc to the the customer. This means that when you save XDOC ONLY the last customer will be saved.
Even if you fix the above, you still have the issue that you are trying to add multiple root elements (each customer) to the XML document. This is not allowed, only one root element is allowed. So to produce a valid XML document, you want something like:
<Customers>
<Customer>
<Name>Mike</Name>
<Age>0</Age>
</Customer>
<Customer>
<Name>Mike</Name>
<Age>0</Age>
</Customer>
</Customers>
I'm sorry but I can recommend how to change your code, because I don't know the VB.Net extensions for XML. Hopefully, someone else will shed some light.
BTW, interesting handle.
You can generate XDocument and XElement separately.
Further, you can set data from variable using <%= %>.
Therefore, you should generate and combine like below.
' Generate customers
Dim customers As XEelement = <Customers></Customers>
For Each cust As Customer In Customers
customers.Add(
<Customer>
<Name><%= cust.Name %></Name>
<Age><%= cust.Age %></Age>
</Customer>
)
Next
' Combine customers
Dim XDoc As XDocument =
<?xml version="1.0" encoding="UTF-16" standalone="yes"?><%= customers %>
XDoc.Save("C:\myXmlfile.xml")
In addition to #RichardSchneider's answer, this is sample snippet in VB.
First, construct XDocument with single <Customers> element as root. Then in each iteration of For Each loop, add single <Customer> element to the root element of the XDocument :
XDoc As XDocument = <?xml version="1.0" encoding="UTF-16" standalone="yes"?><Customers></Customers>
For Each cust As Customer In customers
XDoc.Root.Add(
<Customer>
<Name>Mike</Name>
<Age>0</Age>
</Customer>
)
Next
XDoc.Save("C:\myXmlfile.xml")

Parsing SQL with bad xml namespace

Hi I have the following SQL to try and parse xml and extract the "OrderNumber". The problem i have is this xml (which i have no control over) has a wierd xml namespace. I changed it to abc.com just for this example, but its something else. Anyway, when that namepace is present, the T-SQL returns a null in the result. So it doesn't play nicely with the namespace. If I remove the namespace manually or doing a search and replace via T-SQL, it works just fine. I guess i can just do a search and replace but that solution just bothers me. Was wondering if anyone else nows a better way around this? And maybe an explanation of why it doesn't like namespaces? Would really appreciate some advice. Thanks!
Declare #Transmission xml
set #Transmission = '<Transmission>
<Requests>
<SubmitOrdersRequest>
<Orders>
<Order xmlns="http://www.abc.com">
<OrderNumber>123</OrderNumber>
</Order>
</Orders>
</SubmitOrdersRequest>
</Requests>
</Transmission>'
select #Transmission.value('(Transmission/Requests/SubmitOrdersRequest/Orders/Order/OrderNumber/text())[1]', 'varchar(100)')
Children nodes inherit the namespace of the parent, unless given a namespace themselves. you have to define namespaces using WITH XMLNAMESPACES, and properly qualify node names using them.
Declare #Transmission xml
set #Transmission = '<Transmission>
<Requests>
<SubmitOrdersRequest>
<Orders>
<Order xmlns="http://www.abc.com">
<OrderNumber>123</OrderNumber>
</Order>
</Orders>
</SubmitOrdersRequest>
</Requests>
</Transmission>';
with xmlnamespaces('http://www.abc.com' as ns1)
select #Transmission.value('(Transmission/Requests/SubmitOrdersRequest/Orders/ns1:Order/ns1:OrderNumber/text())[1]', 'varchar(100)')
Note: The reason for namespaces is that names are contextual things. Order can mean in your case a purchase but in another context it could mean display rack order. The namespace gives the name more uniqueness.