In SQL Server 2008 (yes, I know) how do you write an XPath to find nodes with different names?
For example, if I had either of the following XML...
DECLARE #XML XML = '<data><id>1</id><id>2</id></data>'
or...
DECLARE #XML XML = '<d><i>1</i><i>2</i></d>'
I can write the following which will work for the first XML, but wouldn't work for the second XML.
SELECT X.value('.','int') FROM #XML.nodes('/data/id') AS X(X)
I was going to use a union (/data/id | /d/i) but SQL Server apparently doesn't support unions.
The simple solution is to do the following...
SELECT X.value('.','int') FROM #XML.nodes('/data/id') AS X(X)
UNION
SELECT X.value('.','int') FROM #XML.nodes('/d/i') AS X(X)
... but I would prefer it if there was a single XPath solution instead
I've just found this answer (which is for general XPath, not SQL based) and it has given me the solution.
Use the , character and wrap in brackets...
SELECT X.value('text()[1]','int') FROM #XML.nodes('(/data/id,/d/i)') AS X(X)
(Note, I've also updated the '.' to be 'text()[1]' as per the suggestion by #YitzhakKhabinsky)
Related
I have a table with an XML column. Some of the XML is very large (8MB) but I'll present a simpler version of the problem here. Overall, I need to update the table and find those rows where the XML contains a node named <CompressedPart> at a known point in the XML tree, take its value, base64-decode it and replace <CompressedPart> with the resulting data.
This question is simply just the first part of that, which is trying to extract the text under a point in the XML tree. I've encountered XQuery once before and it just as life-destroying as it appears to be now.
To this end, I've simplified the XML to just two nodes thus:
<GovTalkMessage xmlns="http://www.govtalk.gov.uk/CM/envelope">
<EnvelopeVersion>2.0</EnvelopeVersion>
</GovTalkMessage>
and I'm simply trying to get the value "2.0". The code I'm using is:
SELECT CAST('<GovTalkMessage xmlns="http://www.govtalk.gov.uk/CM/envelope">
<EnvelopeVersion>2.0</EnvelopeVersion>
</GovTalkMessage>' AS XML).value('(/GovTalkMessage/EnvelopeVersion)[1]', 'VARCHAR(MAX)')
but this returns NULL. I've tried removing/adding forward slashes, removing the [1] (which gives the incredible un-useful error message "requires a singleton"). Whatever I specify in the XQuery I just get NULL or an error.
In time I will want to select across the whole table, as below, so I'm not just looking for a solution that works for a single XML variable in the FROM clause as I've seen in other examples. This type of thing:
SELECT GOVTALK_XML_INPUT_DATA.value('(/GovTalkMessage/EnvelopeVersion)[1]', 'VARCHAR(MAX)')
FROM dbo.IndividualSubmission
How do I go about querying to solve just this first part of my issue?
A couple ways..
DECLARE #X XML = '
<GovTalkMessage xmlns="http://www.govtalk.gov.uk/CM/envelope">
<EnvelopeVersion>2.0</EnvelopeVersion>
</GovTalkMessage>';
SELECT #X.value('(//*:EnvelopeVersion/text())[1]', 'varchar(20)');
Or..
DECLARE #X VARCHAR(1000) = '
<GovTalkMessage xmlns="http://www.govtalk.gov.uk/CM/envelope">
<EnvelopeVersion>2.0</EnvelopeVersion>
</GovTalkMessage>';
SELECT CAST(#X AS XML).value('(//*:EnvelopeVersion/text())[1]', 'varchar(20)');
Below is an Oracle script that I need to execute on an SQL Server.
SELECT
records.pr_id,
SUBSTR (REPLACE (REPLACE (XMLAGG (XMLELEMENT ("x", prad4.selection_value)
ORDER BY prad4.selection_value),'</x>'),'<x>',' ; '),4) as teva_role
FROM records
Thanks for the help,
Barry
I programmed in SQL for years in several environments and it is about 75% the same. So, the SQL statement should work as is, however the functions (REPLACE, SUBSTR) will be what you need to research and change.
Also, you get columns from prad4 without including it in the FROM statement which is a problem.
And, finally, your parentheses aren't balanced which, I would think, would be a problem in Oracle as well.
This is basically concatenating a set of strings with a delimiter. The common way to do this, is using FOR XML PATH('') which seems to be the equivalent of the combination of XMLELEMENT() in Oracle, but with a different syntax. You can also use XML functions to prevent change of certain characters not allowed in XML. The STUFF takes care of the SUBSTR() part of your code. For a more detailed explanation, you can read this article on Creating a comma-separated list.
The code should look similar to this:
SELECT records.pr_id,
STUFF(( SELECT ' ; ' + prad4.selection_value
FROM prad4
WHERE prad4.pr_id = records.pr_id
ORDER BY prad4.selection_value
FOR XML PATH(''), TYPE).value('./text()[1]', 'varchar(max)'), 1, 3, '')
FROM records;
Of course, with the improvements of SQL Server 2017, the code can be simplified to something like this:
SELECT records.pr_id,
STRING_AGG( selection_value, ' ; ') WITHIN GROUP (ORDER BY selection_value ASC)
FROM records;
I am working on a vb.net application, the management wants me to change the applications data source from SQL Server to XML.
I have a class called WebData.vb in the old application I need to somehow find a way to replace the stored procedures in it and make it read xml. So I was thinking of getting the xml structure from the returning result set of the stored procedure. I looked online and they said that for normal select statement you can do something like this:
FOR xml path ('Get_Order'),ROOT ('Get_Orders')
I am looking for something like
EXEC dbo.spMML_GET_ORDERS_FOR_EXPORT
FOR xml path ('Get_Order'),ROOT ('Get_Orders')
so now that I have the structure I can pass that data to a datatable and then return that datatable to the method.
Also if there is an alternative way in creating a XML stored procedure please let me know thanks coders.
Assuming you can't modify the stored proc (due to other dependencies or some other reason) to have the SELECT within the proc have the FOR XML syntax, you can use INSERT/EXEC to insert the results of the stored proc into a temp table or table variable, then apply your FOR XML onto a query of those results.
Something like this should work:
DECLARE #Data TABLE (...) -- Define table to match results of stored proc
INSERT #Data
EXEC dbo.spMML_GET_ORDERS_FOR_EXPORT
SELECT * FROM #Data FOR xml path ('Get_Order'),ROOT ('Get_Orders')
There are a few methods, one adding namespaces using WITH XMLNAMESPACES(<STRING> AS <NAMESPACE string>). XMLNAMESPACES can embed appropriate XML markers to your tables for use with other applications (which hopefully is a factor here), making documentation a little easier.
Depending on your application use, you can use FOR XML {RAW, PATH, AUTO, or EXPLICIT} in your query, as well as XQUERY methods...but for your needs, stick to the simpler method like XML PATH or XML AUTO.
XML PATH is very flexible, however you lose the straightforward identification of the column datatypes.
XMLNAMESPACE
WITH XMLNAMESPACES('dbo.MyTableName' AS SQL)
SELECT DENSE_RANK() OVER (ORDER BY Name ASC) AS 'Management_ID'
, Name AS [Name]
, Began AS [Team/#Began]
, Ended AS [Team/#Ended]
, Team AS [Team]
, [Role]
FROM dbo.SSIS_Owners
FOR XML PATH, ELEMENTS, ROOT('SQL')
XML AUTO
Because you might want to return to the database, I suggest using XML AUTO with XMLSCHEMA, where the sql datatypes are kept in the XML.
SELECT DENSE_RANK() OVER (ORDER BY Name ASC) AS 'Management_ID'
, Name AS [Name]
, Began AS [Team/#Began]
, Ended AS [Team/#Ended]
, Team AS [Team]
, [Role]
FROM dbo.SSIS_Owners
FOR XML AUTO, ELEMENTS, XMLSCHEMA('SSIS_Owners')
Downside is XMLNAMESPACES is not an option, but you can get around this through solutions like XML SCHEMA COLLECTIONS or in the query itself as I showed.
You can also just use XML PATH directly without the namespace, but again, that depends on your application use as you are transforming everything to XML files.
Also note how I defined the embedded attributes. A learning point here, but think about the query in the same order that the XML would appear. That is why I defined the variable attributes first before I then stated what the text for that node was.
Lastly, I think you'll find Paparazzi has a question on this topic that covers quite. TSQL FOR XML PATH Attribute On , Type
Running SQL Server 2014. I have a stored procedure that returns a quite large XML. It goes something like this:
SELECT(
...
FOR XML PATH (N''), ROOT, TYPE
Now, that query runs in 1 second. If I remove TYPE it runs in around half the time:
SELECT(
...
FOR XML PATH (N''), ROOT
Obviously, the latter returns an nvarchar(max) instead of an xml. I want xml data, but if I ask for xml it gets slower! If I want to fetch xml data on the client, is it really necessary to convert it to xml using the TYPE directive above?
Q: Anyway, why is FOR XML ... TYPE significantly slower than FOR XML ...? Is there any way to improve the conversion?
Did you try to set variables with the results as XML and as VARCHAR(MAX) without displaying them? Maybe the time difference you measure is bound to preparing the viewer? Pasting the first letters into a grid column is faster than creating a well formed, indented, displayable XML...
Sepcifying "TYPE" is not needed in most cases. You really need this with nested XML only. Just play around with aliases, PATH- and ROOT-literals and - of course - with or without TYPE:
And - very important! - try to call this with the surrounding SELECT and without:
SELECT
(
SELECT tbls.TABLE_NAME AS [#TableName]
,(
SELECT COLUMN_NAME AS [#ColumName]
FROM INFORMATION_SCHEMA.COLUMNS AS cols
WHERE cols.TABLE_NAME=Tbls.TABLE_NAME
FOR XML PATH('COLUMN') /*,TYPE*/
) /*AS alias*/
FROM INFORMATION_SCHEMA.TABLES AS Tbls
FOR XML PATH('TABLE'),ROOT('ALL_TABLES') /*,TYPE*/
) /*AS alias*/
I don't know, how you continue with your generated XML. If you transfer it to your application it will be a plain string anyway.
Conclusio: Take the faster approach :-)
By the way...
I do not know your Stored Procedure and what else is done there besides the SELECT...
In most cases it is a bad habit to use SPs just to read data.
If your SP is not more than a wrapper around your SELECT you should think about a (single-statement!) table valued function to retrieve your data.
This function is easily queried and transformed to XML with
SELECT *
FROM dbo.MyFunction(/*Parameters*/)
FOR XML PATH('TheRowsName'),ROOT('TheRootName') [,TYPE]
Or - if you need this as XML everytime, you might define a scalar function delivering XML or VARCHAR(MAX). The re-usability of functions is way better than with SPs...
Is there a way to get the xml-safe version of an xml column in sql server ?
By xml-Safe i mean escaping special characters like <,>,', &, etc.
I'd like to avoid doing the replacements myself. Is there a build in function in sql server.
What I want to achieve is to store the xml content into another xml attribute.
It is not a direct answer to this question but to anyone who tries to xml-escape strings in TSQL, here is a little function I wrote :
CREATE FUNCTION escapeXml
(#xml nvarchar(4000))
RETURNS nvarchar(4000)
AS
BEGIN
declare #return nvarchar(4000)
select #return =
REPLACE(
REPLACE(
REPLACE(
REPLACE(
REPLACE(#xml,'&', '&')
,'<', '<')
,'>', '>')
,'"', '"')
,'''', ''')
return #return
end
GO
I assume that by xml-safe you mean escaping of XML special tags. If you have an XML column you wish to include in another XML document then you have two options:
project the column as [*]: select ..., xmlcolumn as [*], ... from ... for xml path... this will embed the XML content of the column in the result XMl. Eg. if the column has the value <element>value</element> then the result will be like <root><row><element>value</element></row></root>.
project the column as the column name: select ..., xmlcolumn, ... from ... for xml path... this will insert the content of the column as a value (ie. it will escape it). Eg. the same value as above will produce <root><row><xmlcolumn><element><value</element>.
If your question is about something else, then you're going to have to rephrase it in a proper manner and use terms correctly. Don't invent new terms no one understands but you.
Update:
If you are inserting XML values into the column, then you don't have to do anything at all. The client libraries know how to handle the proper escaping. As long as you write your code correctly. Remeber, XML is NOT a string and should never, ever be treated as one. If you write XML in your client, use an appropriate XML library (XmlWriter, XML DOM, Linq to XML etc). when passing in the XML into SQL Server, use the appropiate type: SqlXml. Stored procedures should use the appropiate parameter type: XML. When you read it, use the appropriate method to read XML: GetSqlXml(). Same goes for declaring the type in one of the miriad designers (LINQ to SQL , EF etc). Ultimately, there is never any need to escape XML characters manually. If you find yourself doing that, you're using the wrong API and you have to go back to the drawing board.
A good start reading is XML Support in Microsoft SQL Server 2005.
And finally, to manipulate XML as you describe (update XML column of table A with XML column of table B), you use XML methods, specifically modify (... insert...), and you bind the table B column inside the XQuery using sql:column:
update A
set somecolumn.modify('insert {sql:column("B.othercolumn")} before somenode')
from A join B on ...;
In you comment you threat XML as a string and, as I already said, you should never ever do that: strings and XML are as water and oil.
Another simpler way to xml escape a string is to use the following:
SELECT #String FOR XML PATH('')
e.g.
DECLARE #Input NVARCHAR(4000) = 'bacon & eggs'
DECLARE #String = (SELECT #Input FOR XML PATH(''))
then use #string from there
The contents of an XML column are XML. By definition, that is "XML-safe".
Do you need to include XML from a column in an XML element or attribute of another XML document? Then just save the outer XML as a string in the new document.