I am working with XML data on an SQL Server. The (exemplary) SQL looks as follows:
<Document xmlns="urn:iso:std:iso:20022:some:test:xmlns">
<Testnode>a</Testnode>
</Document>
The XML is available in a table with a column named <fata of type XML.
My question is: How can I create a SELECT query that shows the text of the namespace in one column?
The expected output should be:
+----------------------------------------+
| xmlns |
+----------------------------------------+
| urn:iso:std:iso:20022:some:test:xmlns |
+----------------------------------------+
The result column should be a character string (no XML).
So far I have tried this query, however the result is NULL:
SELECT Data.value('(./Document)[1]','nvarchar(max)') AS xmlns
FROM xmltable
You can try something along this:
DECLARE #xml XML=
N'<Document xmlns="urn:iso:std:iso:20022:some:test:xmlns">
<Testnode>a</Testnode>
</Document>';
--The XQuery-function namespace-uri() takes a singleton and returns its namespace uri
SELECT #xml.value('namespace-uri((/*:Document)[1])','nvarchar(max)');
As the <Document> element is living within the default namespace itself, we would have to know the namespace in advance in order to declare it. But - luckily - we can use the wildcard with *:.
Another option - one of the rare cases - is the usage of the outdated FROM OPENXML:
Try this:
DECLARE #xml XML=
N'<Document xmlns="urn:iso:std:iso:20022:some:test:xmlns">
<Testnode>a</Testnode>
</Document>';
DECLARE #docHandle INT;
EXEC sp_xml_preparedocument #docHandle OUTPUT, #xml;
SELECT * FROM OPENXML (#docHandle, '/*',1);
EXEC sp_xml_removedocument #docHandle;
This returns the complete XML with a lot of meta-data:
+----+----------+----------+-----------+--------+---------------------------------------+----------+------+---------------------------------------+
| id | parentid | nodetype | localname | prefix | namespaceuri | datatype | prev | text |
+----+----------+----------+-----------+--------+---------------------------------------+----------+------+---------------------------------------+
| 0 | NULL | 1 | Document | NULL | urn:iso:std:iso:20022:some:test:xmlns | NULL | NULL | NULL |
+----+----------+----------+-----------+--------+---------------------------------------+----------+------+---------------------------------------+
| 2 | 0 | 2 | xmlns | xmlns | NULL | NULL | NULL | NULL |
+----+----------+----------+-----------+--------+---------------------------------------+----------+------+---------------------------------------+
| 4 | 2 | 3 | #text | NULL | NULL | NULL | NULL | urn:iso:std:iso:20022:some:test:xmlns |
+----+----------+----------+-----------+--------+---------------------------------------+----------+------+---------------------------------------+
| 3 | 0 | 1 | Testnode | NULL | urn:iso:std:iso:20022:some:test:xmlns | NULL | NULL | NULL |
+----+----------+----------+-----------+--------+---------------------------------------+----------+------+---------------------------------------+
| 5 | 3 | 3 | #text | NULL | NULL | NULL | NULL | a |
+----+----------+----------+-----------+--------+---------------------------------------+----------+------+---------------------------------------+
Related
I have a Text String which contains JSON, something like this:
'{ "d" : [ "test0", "test1", "test2" ] }'
and I would like to retrieve the item of the Array as rows.
+------------+
| data |
+------------+
| test0 |
| test1 |
| test2 |
+------------+
all examples on the Web, show how it is done with "Object Array", but I would like to do it with a simple "String Array" MS example.
The default query
select * from OPENJSON('{"d":["test0","test1","test2"]}', '$.d')
just returns a table with the key, value, type of each entry
+-----+-------+------+
| key | value | type |
+-----+-------+------+
| 0 | test0 | 1 |
| 1 | test1 | 1 |
| 2 | test2 | 1 |
+-----+-------+------+
the problem is, I don't know how to set the with part of the query, so that the query returns a row.
select * from OPENJSON('{"d":["test0","test1","test2"]}', '$.d')
with(data nvarchar(255) '$.d')
only return:
+------+
| data |
+------+
| NULL |
| NULL |
| NULL |
+------+
select * from OPENJSON('{"d":["test0","test1","test2"]}', '$.d')
with(data nvarchar(255) '$')
Question
Is there any way to use XPath which matches on the namespace axis in SQL Server? i.e. I'm aware that SQL does not natively support this axis; but is there any query which would be functionally similar which may work?
Context
I'm hoping to write code to debloat duplicate namespaces from my XML, leaving only those declarations which exist on the root element. I've seen various other solutions for this, but all are quite painful; so I investigated alternate solutions & in doing so realised that SQL does not support the namespace axis.
declare #demo xml = '
<hello:a xmlns:hello="test" xmlns:world="me">
<hello:b>
<world:c xmlns:world="me">demo</world:c>
<hello:d xmlns:hello="test">demo</hello:d>
<world:e xmlns:hello="test" xmlns:world="me">demo</world:e>
<hello:f xmlns:hello="test" xmlns:world="me" world:demo=''x''>demo</hello:f>
</hello:b>
</hello:a>
'
set #demo.modify('delete (/*//namespace::*)')
--set #demo.modify('delete (/*//#*[not(namespace-uri() > "")])') --tried just in case xmlns is treated as an attribute in SQL; no joy :/
select #demo
Research
NB: There's a similar question asking how this is done in XSLT; but SQL-Server does not include the namespace:: axis. A list of the available axes in SQL is available here.
There are other ways to remove this bloat; but none are that straightforwards, and these posts are now quite dated, hence my researching alternate approaches:
a good overview of the various available methods: https://social.msdn.microsoft.com/Forums/sqlserver/en-US/2f7bdfbf-8e40-456b-84e8-195318649703/how-to-remove-namespaces-from-xml-tags-when-using-for-xml-option-with-xmlnamespaces?forum=transactsql&prof=required
a few answers on how to avoid this issue when generating the XML within SQL How do I remove redundant namespace in nested query when using FOR XML PATH
Current Solution
NB: Since I've been unable to find a straight forwards solution, my current method for removing these namespaces is as below. There are some risks with this (e.g. losing attributes on the root element, issues if the expected spaces are missing / other whitespace characters are used instead), but this is good enough for my purposes, if very hacky & non-generic.
declare #demo xml = '
<hello:a xmlns:hello="test" xmlns:world="me">
<hello:b>
<world:c xmlns:world="me">demo</world:c>
<hello:d xmlns:hello="test">demo</hello:d>
<world:e xmlns:hello="test" xmlns:world="me">demo</world:e>
<hello:f xmlns:hello="test" xmlns:world="me" world:demo=''x''>demo</hello:f>
</hello:b>
</hello:a>
'
;with xmlnamespaces('test' as hello, 'me' as world)
select #demo = cast(
'<hello:a xmlns:hello="test" xmlns:world="me">'
+ replace(
replace(
cast(#demo.query('/*/*') as nvarchar(max))
,' xmlns:hello="test"'
,''
)
,' xmlns:world="me"'
,''
)
+ '</hello:a>'
as xml
)
select #demo
SQL-Server's abilities to deal with XML namespaces is really - uhm - painful...
the only way I know to define namespaces just as you want them is FOR XML EXPLICIT (unless you want to walk the string-manipulation route...)
You can create the XML you want with:
SELECT 1 AS Tag
,NULL AS Parent
,'test' AS [hello:a!1!xmlns:hello]
,'me' AS [hello:a!1!xmlns:world]
,NULL AS [hello:b!2]
,NULL AS [world:c!3]
,NULL AS [hello:d!4]
,NULL AS [world:e!5]
,NULL AS [hello:f!6]
,NULL AS [hello:f!6!world:demo]
UNION ALL
SELECT 2
,1
,NULL
,NULL
,''
,NULL
,NULL
,NULL
,NULL
,NULL
UNION ALL
SELECT 3
,2
,NULL
,NULL
,''
,'demo'
,NULL
,NULL
,NULL
,NULL
UNION ALL
SELECT 4
,2
,NULL
,NULL
,''
,NULL
,'demo'
,NULL
,NULL
,NULL
UNION ALL
SELECT 5
,2
,NULL
,NULL
,''
,NULL
,NULL
,'demo'
,NULL
,NULL
UNION ALL
SELECT 6
,2
,NULL
,NULL
,''
,NULL
,NULL
,NULL
,'demo'
,'x'
FOR XML EXPLICIT;
The result
<hello:a xmlns:hello="test" xmlns:world="me">
<hello:b>
<world:c>demo</world:c>
<hello:d>demo</hello:d>
<world:e>demo</world:e>
<hello:f world:demo="x">demo</hello:f>
</hello:b>
</hello:a>
As Jeroen Mostert has pointed out in comments you might use outdated FROM OPEN XML like here:
declare #demo xml =
'<hello:a xmlns:hello="test" xmlns:world="me">
<hello:b>
<world:c xmlns:world="me">demo</world:c>
<hello:d xmlns:hello="test">demo</hello:d>
<world:e xmlns:hello="test" xmlns:world="me">demo</world:e>
<hello:f xmlns:hello="test" xmlns:world="me" world:demo=''x''>demo</hello:f>
</hello:b>
</hello:a>';
DECLARE #hdoc int;
EXEC sp_xml_preparedocument #hdoc OUTPUT, #demo;
SELECT *
FROM OPENXML(#hdoc, '//*');
EXEC sp_xml_removedocument #hdoc;
GO
The result
+----+----------+----------+-----------+--------+--------------+----------+------+------+
| id | parentid | nodetype | localname | prefix | namespaceuri | datatype | prev | text |
+----+----------+----------+-----------+--------+--------------+----------+------+------+
| 0 | NULL | 1 | a | hello | test | NULL | NULL | NULL |
+----+----------+----------+-----------+--------+--------------+----------+------+------+
| 2 | 0 | 2 | hello | xmlns | NULL | NULL | NULL | NULL |
+----+----------+----------+-----------+--------+--------------+----------+------+------+
| 20 | 2 | 3 | #text | NULL | NULL | NULL | NULL | test |
+----+----------+----------+-----------+--------+--------------+----------+------+------+
| 3 | 0 | 2 | world | xmlns | NULL | NULL | NULL | NULL |
+----+----------+----------+-----------+--------+--------------+----------+------+------+
| 21 | 3 | 3 | #text | NULL | NULL | NULL | NULL | me |
+----+----------+----------+-----------+--------+--------------+----------+------+------+
| 4 | 0 | 1 | b | hello | test | NULL | NULL | NULL |
+----+----------+----------+-----------+--------+--------------+----------+------+------+
| 5 | 4 | 1 | c | world | me | NULL | NULL | NULL |
+----+----------+----------+-----------+--------+--------------+----------+------+------+
| 6 | 5 | 2 | world | xmlns | NULL | NULL | NULL | NULL |
+----+----------+----------+-----------+--------+--------------+----------+------+------+
| 22 | 6 | 3 | #text | NULL | NULL | NULL | NULL | me |
+----+----------+----------+-----------+--------+--------------+----------+------+------+
| 7 | 5 | 3 | #text | NULL | NULL | NULL | NULL | demo |
+----+----------+----------+-----------+--------+--------------+----------+------+------+
| 8 | 4 | 1 | d | hello | test | NULL | 5 | NULL |
+----+----------+----------+-----------+--------+--------------+----------+------+------+
| 9 | 8 | 2 | hello | xmlns | NULL | NULL | NULL | NULL |
+----+----------+----------+-----------+--------+--------------+----------+------+------+
| 23 | 9 | 3 | #text | NULL | NULL | NULL | NULL | test |
+----+----------+----------+-----------+--------+--------------+----------+------+------+
| 10 | 8 | 3 | #text | NULL | NULL | NULL | NULL | demo |
+----+----------+----------+-----------+--------+--------------+----------+------+------+
| 11 | 4 | 1 | e | world | me | NULL | 8 | NULL |
+----+----------+----------+-----------+--------+--------------+----------+------+------+
| 12 | 11 | 2 | hello | xmlns | NULL | NULL | NULL | NULL |
+----+----------+----------+-----------+--------+--------------+----------+------+------+
| 24 | 12 | 3 | #text | NULL | NULL | NULL | NULL | test |
+----+----------+----------+-----------+--------+--------------+----------+------+------+
| 13 | 11 | 2 | world | xmlns | NULL | NULL | NULL | NULL |
+----+----------+----------+-----------+--------+--------------+----------+------+------+
| 25 | 13 | 3 | #text | NULL | NULL | NULL | NULL | me |
+----+----------+----------+-----------+--------+--------------+----------+------+------+
| 14 | 11 | 3 | #text | NULL | NULL | NULL | NULL | demo |
+----+----------+----------+-----------+--------+--------------+----------+------+------+
| 15 | 4 | 1 | f | hello | test | NULL | 11 | NULL |
+----+----------+----------+-----------+--------+--------------+----------+------+------+
| 16 | 15 | 2 | hello | xmlns | NULL | NULL | NULL | NULL |
+----+----------+----------+-----------+--------+--------------+----------+------+------+
| 26 | 16 | 3 | #text | NULL | NULL | NULL | NULL | test |
+----+----------+----------+-----------+--------+--------------+----------+------+------+
| 17 | 15 | 2 | world | xmlns | NULL | NULL | NULL | NULL |
+----+----------+----------+-----------+--------+--------------+----------+------+------+
| 27 | 17 | 3 | #text | NULL | NULL | NULL | NULL | me |
+----+----------+----------+-----------+--------+--------------+----------+------+------+
| 18 | 15 | 2 | demo | world | me | NULL | NULL | NULL |
+----+----------+----------+-----------+--------+--------------+----------+------+------+
| 28 | 18 | 3 | #text | NULL | NULL | NULL | NULL | x |
+----+----------+----------+-----------+--------+--------------+----------+------+------+
| 19 | 15 | 3 | #text | NULL | NULL | NULL | NULL | demo |
+----+----------+----------+-----------+--------+--------------+----------+------+------+
This table includes all the information you would need to create the statement above in a recursive CTE dynamically and use EXEC to create the XML from scratch.
with WHERE nodetype=1 you get the elements, with 2 the attributes...
But - to be honest - this is a huge effort...
If your XMLs are more complicated, nested, whatever, this will get really bad...
I am trying to write an XPath query to extract the URI of a specific namespace that will always use the same abbrevation.
Example:
<RequestPacket xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:a="urn:dont:really:care">
<a:Requests xmlns:queryNS="urn:uri:i:need">
<a:Request xsi:type="queryNS:GetImportantData">
<queryNS:Version>3</queryNS:Version>
...
</a:Request>
</a:Requests>
</RequestPacket>
I need to extract the URI which corresponds to the queryNS namespace, which in my example would be urn:uri:i:need.
Is this possible? If so, could someone please help?
I tried to get it using the namespace-uri() function, but to do so, I have to specify a node, which I can only do by specifying the namespaces at each node (which is what I'm trying to figure out). So it seems like a chicken and egg situation.
Thanks!
There are two approaches:
DECLARE #xml XML=
N'<RequestPacket xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:a="urn:dont:really:care">
<a:Requests xmlns:queryNS="urn:uri:i:need">
<a:Request xsi:type="queryNS:GetImportantData">
<queryNS:Version>3</queryNS:Version>
...
</a:Request>
</a:Requests>
</RequestPacket>';
--use namespace-uri() against a node, which is living within this namespace
SELECT #xml.value(N'namespace-uri((//*:Version)[1])',N'nvarchar(max)')
--This might be one of the rare situations, where FROM OPENXML is still helpful. Try this:
DECLARE #docHandle INT
EXEC sp_xml_preparedocument #docHandle OUTPUT, #xml;
SELECT * FROM OPENXML (#docHandle, '',1)
EXEC sp_xml_removedocument #docHandle
The result
+----+----------+----------+---------------+---------+-------------------------------------------+----------+------+-------------------------------------------+
| id | parentid | nodetype | localname | prefix | namespaceuri | datatype | prev | text |
+----+----------+----------+---------------+---------+-------------------------------------------+----------+------+-------------------------------------------+
| 0 | NULL | 1 | RequestPacket | NULL | NULL | NULL | NULL | NULL |
+----+----------+----------+---------------+---------+-------------------------------------------+----------+------+-------------------------------------------+
| 2 | 0 | 2 | xsi | xmlns | NULL | NULL | NULL | NULL |
+----+----------+----------+---------------+---------+-------------------------------------------+----------+------+-------------------------------------------+
| 10 | 2 | 3 | #text | NULL | NULL | NULL | NULL | http://www.w3.org/2001/XMLSchema-instance |
+----+----------+----------+---------------+---------+-------------------------------------------+----------+------+-------------------------------------------+
| 3 | 0 | 2 | a | xmlns | NULL | NULL | NULL | NULL |
+----+----------+----------+---------------+---------+-------------------------------------------+----------+------+-------------------------------------------+
| 11 | 3 | 3 | #text | NULL | NULL | NULL | NULL | urn:dont:really:care |
+----+----------+----------+---------------+---------+-------------------------------------------+----------+------+-------------------------------------------+
| 4 | 0 | 1 | Requests | a | urn:dont:really:care | NULL | NULL | NULL |
+----+----------+----------+---------------+---------+-------------------------------------------+----------+------+-------------------------------------------+
| 5 | 4 | 2 | queryNS | xmlns | NULL | NULL | NULL | NULL |
+----+----------+----------+---------------+---------+-------------------------------------------+----------+------+-------------------------------------------+
| 12 | 5 | 3 | #text | NULL | NULL | NULL | NULL | urn:uri:i:need |
+----+----------+----------+---------------+---------+-------------------------------------------+----------+------+-------------------------------------------+
| 6 | 4 | 1 | Request | a | urn:dont:really:care | NULL | NULL | NULL |
+----+----------+----------+---------------+---------+-------------------------------------------+----------+------+-------------------------------------------+
| 7 | 6 | 2 | type | xsi | http://www.w3.org/2001/XMLSchema-instance | NULL | NULL | NULL |
+----+----------+----------+---------------+---------+-------------------------------------------+----------+------+-------------------------------------------+
| 13 | 7 | 3 | #text | NULL | NULL | NULL | NULL | queryNS:GetImportantData |
+----+----------+----------+---------------+---------+-------------------------------------------+----------+------+-------------------------------------------+
| 8 | 6 | 1 | Version | queryNS | urn:uri:i:need | NULL | NULL | NULL |
+----+----------+----------+---------------+---------+-------------------------------------------+----------+------+-------------------------------------------+
| 14 | 8 | 3 | #text | NULL | NULL | NULL | NULL | 3 |
+----+----------+----------+---------------+---------+-------------------------------------------+----------+------+-------------------------------------------+
| 9 | 6 | 3 | #text | NULL | NULL | NULL | 8 | ... |
+----+----------+----------+---------------+---------+-------------------------------------------+----------+------+-------------------------------------------+
You can use the parentid and the nodetype to find your URI: The attribute queryNS has the id 5. This is the parentid of element 12, where the #text is the URI you are looking for. But this cannot be used within adhoc / inline queries.
UPDATE: You might use .query()
SELECT #xml.query(
N'
<root>
{
for $nd in //*
return
<nd ns="{namespace-uri($nd)}" name="{local-name($nd)}" value="{($nd/text())[1]}">
{
for $attr in $nd/#*
return
<attr ns="{namespace-uri($attr)}" name="{local-name($attr)}" value="{$attr}" />
}
</nd>
}
</root>
');
The result
<root>
<nd ns="" name="RequestPacket" value="" />
<nd ns="urn:dont:really:care" name="Requests" value="" />
<nd ns="urn:dont:really:care" name="Request" value="
...
">
<attr ns="http://www.w3.org/2001/XMLSchema-instance" name="type" value="queryNS:GetImportantData" />
</nd>
<nd ns="urn:uri:i:need" name="Version" value="3" />
</root>
Just use the namespace axis:
string(namespace::queryNS)
or if the namespace prefix is not known statically
string(namespace::*[name()=$prefix])
The namespace axis is not available in XQuery, and is deprecated in XPath 2.0+, so you can instead use
namespace-uri-for-prefix($prefix, $element)
CAVEAT: I just noticed the question was tagged sql-server. I know nothing of the XPath/XQuery implementation in SQL Server other than the fact that it's not always conformant.
I've imported data from an XML file by using SSIS to SQL Server.
The result what I got in the database is similar to this:
+-------+---------+---------+-------+
| ID | Name | Brand | Price |
+-------+---------+---------+-------+
| 2 | NULL | NULL | 100 |
| NULL | SLX | NULL | NULL |
| NULL | NULL | Blah | NULL |
| NULL | NULL | NULL | 100 |
+-------+---------+---------+-------+
My desired result would be:
+-------+---------+---------+-------+
| ID | Name | Brand | Price |
+-------+---------+---------+-------+
| 2 | SLX | Blah | 100 |
+-------+---------+---------+-------+
Is there a pretty solution to solve this in T-SQL?
I've already tried it with a SELECT MAX(ID) and then a GROUP BY ID, but I'm still stuck with the NULL values. Also I've tried it with MERGE, but also a failure.
Could someone give me a direction where to search further?
You can select MAX on all columns....
SELECT MAX(ID), MAX(NAME), MAX(BRAND), MAX(PRICE)
FROM [TABLE]
Click here for a fiddley fidd fiddle...
I want to query in SQL Server a column's name. I know it is possible to get a table's columns from the system table, but unfortunately that's not enough for me.
Example:
I have a table that contains an ID column and a string column. The table's name is test, and it has a testID and a test column.
This query:
select column_name
from information_schema.columns
where table_name = 'teszt'
return the names of the columns of my table. So it returns testID and Test.
What I want is when I use a query like this:
select count(*) as Amount from test
I want a query that can return the column names of my query. So in this specific case it returns the string 'Amount'. I don't know if that is possible.
Not sure if there is an easier way of getting the name of columns with aliases, but one way of doing it is via XML. This query will return one row per column in the inner query:
select T1.res.value('local-name(.)', 'varchar(50)')
from (select cast(
(
select count(*) as Amount from test
for xml raw) as xml
)) q(res)
CROSS APPLY q.res.nodes('/row/#*') as T1(res)
In SQL Server 2012 you have a stored procedure that you can use for exactly this purpose.
sp_describe_first_result_set (Transact-SQL)
SQL Fiddle
MS SQL Server 2012 Schema Setup:
create table test(id int);
Query 1:
exec sp_describe_first_result_set N'select count(*) as Amount from test'
Results:
| IS_HIDDEN | COLUMN_ORDINAL | NAME | IS_NULLABLE | SYSTEM_TYPE_ID | SYSTEM_TYPE_NAME | MAX_LENGTH | PRECISION | SCALE | COLLATION_NAME | USER_TYPE_ID | USER_TYPE_DATABASE | USER_TYPE_SCHEMA | USER_TYPE_NAME | ASSEMBLY_QUALIFIED_TYPE_NAME | XML_COLLECTION_ID | XML_COLLECTION_DATABASE | XML_COLLECTION_SCHEMA | XML_COLLECTION_NAME | IS_XML_DOCUMENT | IS_CASE_SENSITIVE | IS_FIXED_LENGTH_CLR_TYPE | SOURCE_SERVER | SOURCE_DATABASE | SOURCE_SCHEMA | SOURCE_TABLE | SOURCE_COLUMN | IS_IDENTITY_COLUMN | IS_PART_OF_UNIQUE_KEY | IS_UPDATEABLE | IS_COMPUTED_COLUMN | IS_SPARSE_COLUMN_SET | ORDINAL_IN_ORDER_BY_LIST | ORDER_BY_IS_DESCENDING | ORDER_BY_LIST_LENGTH | TDS_TYPE_ID | TDS_LENGTH | TDS_COLLATION_ID | TDS_COLLATION_SORT_ID |
|-----------|----------------|--------|-------------|----------------|------------------|------------|-----------|-------|----------------|--------------|--------------------|------------------|----------------|------------------------------|-------------------|-------------------------|-----------------------|---------------------|-----------------|-------------------|--------------------------|---------------|-----------------|---------------|--------------|---------------|--------------------|-----------------------|---------------|--------------------|----------------------|--------------------------|------------------------|----------------------|-------------|------------|------------------|-----------------------|
| 0 | 1 | Amount | 1 | 56 | int | 4 | 10 | 0 | (null) | (null) | (null) | (null) | (null) | (null) | (null) | (null) | (null) | (null) | 0 | 0 | 0 | (null) | (null) | (null) | (null) | (null) | 0 | (null) | 0 | 0 | 0 | (null) | (null) | (null) | 38 | 4 | (null) | (null) |
Maybe you want something like this? :-)
SELECT AMOUNT
FROM
(
SELECT COUNT(*) AS AMOUNT
FROM TEST
)X