If I have some xml:
<Users>
<User>
<property1>sdfd</property1>
...
<User>
...
</Users>
And my sql is:
SELECT
*
FROM
OpenXML(#idoc, '/Users/User')
WITH (
[property1] varchar(50) 'property1',
...
)
How can I get the name of the parent element and return that in the dataset?
This page from MSDN docs on OpenXML seems to indicate you should be able to use the ".." notation for the parent:
declare #idoc int
declare #doc varchar(1000)
set #doc ='<ROOT>
<Customer CustomerID="VINET" ContactName="Paul Henriot">
<Order OrderID="10248" CustomerID="VINET" EmployeeID="5"
OrderDate="1996-07-04T00:00:00">
<OrderDetail ProductID="11" Quantity="12"/>
<OrderDetail ProductID="42" Quantity="10"/>
</Order>
</Customer>
<Customer CustomerID="LILAS" ContactName="Carlos Gonzlez">
<Order OrderID="10283" CustomerID="LILAS" EmployeeID="3"
OrderDate="1996-08-16T00:00:00">
<OrderDetail ProductID="72" Quantity="3"/>
</Order>
</Customer>
</ROOT>'
--Create an internal representation of the XML document.
exec sp_xml_preparedocument #idoc OUTPUT, #doc
-- SELECT stmt using OPENXML rowset provider
SELECT *
FROM OPENXML (#idoc, '/ROOT/Customer/Order/OrderDetail',2)
WITH (OrderID int '../#OrderID',
CustomerID varchar(10) '../#CustomerID',
OrderDate datetime '../#OrderDate',
ProdID int '#ProductID',
Qty int '#Quantity')
Does that work in your case, too?
UPDATE:
Try this (the "pseudo-attribute" #mp:parentlocalname) :
SELECT *
FROM OPENXML (#idoc, '/ROOT/Customer/Order/OrderDetail',2)
WITH (OrderID int '../#OrderID',
CustomerID varchar(10) '../#CustomerID',
OrderDate datetime '../#OrderDate',
ProdID int '#ProductID',
Qty int '#Quantity',
ParentNodeName varchar(50) '#mp:parentlocalname' )
Does this now do what you want? :-)
See a whole list of these "pseudo-attributes" in this article at ExtremeExperts.
Marc
Related
Can we use with inside any apply in the SQL server?
For traversing all nodes in the XML file I am trying to use outer apply so I can traverse in one go.
DECLARE #XML AS XML, #hDoc AS INT, #SQL NVARCHAR (MAX)
SELECT #XML = XMLData FROM XMLwithOpenXML
EXEC sp_xml_preparedocument #hDoc OUTPUT, #XML
SELECT CustomerID, CustomerName, Address
FROM OPENXML(#hDoc, 'ROOT/Customers/Customer')
WITH
(
CustomerID [varchar](50) '#CustomerID',
CustomerName [varchar](100) '#CustomerName',
Address [varchar](100) 'Address'
)
OUTER APPLY
(SELECT OrderDate, OrderID--, Address
FROM OPENXML(#hDoc, 'ROOT/Customers/Customer/Orders/Order')
WITH
(
OrderDate [varchar](100) '#OrderDate',
OrderID [varchar](100) '#OrderID'
Address [varchar](100) 'Address'
) as Orders
OUTER APPLY
(SELECT Quantity, ProductID--, Address
FROM OPENXML(#hDoc, 'ROOT/Customers/Customer/Orders/Order/OrderDetail')
WITH
(
Quantity [varchar](100) '#Quantity',
ProductID [varchar](100) '#ProductID'
Address [varchar](100) 'Address'
) as OrderDetail
this is the XML file
<ROOT>
<Customers>
--root/customers/cusomer/orders/order/OrderDetail
<Customer CustomerName="Arshad Ali" CustomerID="C001">
<Orders>
<Order OrderDate="2012-07-04T00:00:00" OrderID="10248">
<OrderDetail Quantity="5" ProductID="10"/>
<OrderDetail Quantity="12" ProductID="11"/>
<OrderDetail Quantity="10" ProductID="42"/>
</Order>
</Orders>
<Address> Address line 1, 2, 3</Address>
</Customer>
</Customers>
</ROOT>
and this is how i want my result.
CustomerID
CustomerName
Address
OrderID
OrderDate
ProductID
Quantity
C001
Arshad Ali
Address line 1, 2, 3
10248
2012-07-04 00:00:00.000
10
5
C001
Arshad Ali
Address line 1, 2, 3
10248
2012-07-04 00:00:00.000
11
12
C001
Arshad Ali
Address line 1, 2, 3
10248
2012-07-04 00:00:00.000
42
10
I know another solution without apply. but can we do with outer apply? if 'yes' then how?
I am new with this so please help me.
To do this, you can choose to go the deepest level and then "crawl up" from it.
DECLARE #XML AS XML, #hDoc AS INT, #SQL NVARCHAR (MAX)
SELECT #XML = N'<ROOT>
<Customers>
--root/customers/cusomer/orders/order/OrderDetail
<Customer CustomerName="Arshad Ali" CustomerID="C001">
<Orders>
<Order OrderDate="2012-07-04T00:00:00" OrderID="10248">
<OrderDetail Quantity="5" ProductID="10"/>
<OrderDetail Quantity="12" ProductID="11"/>
<OrderDetail Quantity="10" ProductID="42"/>
</Order>
</Orders>
<Address> Address line 1, 2, 3</Address>
</Customer>
</Customers>
</ROOT>'
EXEC sp_xml_preparedocument #hDoc OUTPUT, #XML
select *
FROM OPENXML(#hDoc, 'ROOT/Customers/Customer/Orders/Order/OrderDetail')
WITH
(
CustomerID NVARCHAR(30) '../../../../Customer/#CustomerID'
, CustomerName NVARCHAR(100) '../../../../Customer/#CustomerName'
, Address NVARCHAR(100) '../../../Address'
, OrderID NVARCHAR(30) '../#OrderID'
, OrderDate DATETIME '../#OrderDate'
, ProductID INT '#ProductID'
, Quantity INT '#Quantity'
)
exec sp_xml_removedocument #hDoc OUTPUT -- Always clean up your XMLs!
Edit #2: "real" apply version:
DECLARE #XML AS XML
SELECT #XML = N'<ROOT>
<Customers>
--root/customers/cusomer/orders/order/OrderDetail
<Customer CustomerName="Arshad Ali" CustomerID="C001">
<Orders>
<Order OrderDate="2012-07-04T00:00:00" OrderID="10248">
<OrderDetail Quantity="5" ProductID="10"/>
<OrderDetail Quantity="12" ProductID="11"/>
<OrderDetail Quantity="10" ProductID="42"/>
</Order>
</Orders>
<Address> Address line 1, 2, 3</Address>
</Customer>
</Customers>
</ROOT>'
SELECT cu.value('#CustomerName', 'nvarchar(1000)') name
, cu.value('#CustomerID', 'nvarchar(100)') id
, cu.value('Address[1]', 'NVARCHAR(1000)') address
, oo.value('#OrderDate', 'datetime') orderdate
, oo.value('#OrderID', 'varchar(30)') orderid
, ood.value('#Quantity', 'int') qty
, ood.value('#ProductID', 'varchar(10)') productId
FROM (
SELECT #xml AS x
) xml
OUTER APPLY x.nodes('ROOT/Customers/Customer') c(cu)
OUTER APPLY cu.nodes('Orders/Order') o(oo)
OUTER APPLY oo.nodes('OrderDetail') od(ood)
In the following code, I want to check if column is a Number. If it is - fill it with leading zeros.
Is there anyway using XML XQuery to check the original data type (int)
of the column?
declare #T table (string nchar(10), id int)
insert #T
select 'test1', 1
insert #T
select 'test2', 2
declare #X xml
SET ANSI_WARNINGS ON
set #X = (select * from #T order by id for xml path('row'), root('root'))
SELECT (
STUFF(
(
SELECT ';' + v.value('.','nvarchar(max)')
FROM r.nodes('*') AS B(v)
FOR XML PATH('')
),1,1,'')
) as [OUTPUT]
FROM #x.nodes('/root/row') AS A(r)
As you know your table you should not use a generical approach, if you do not have a good reason to do so.
What you probably should do is this
Include the formatted value into your XML. This allows you, to carry both information within the structure: typed and formatted.
declare #T table (string nvarchar(10), id int)
insert #T values
('test1', 1)
,('test2', 22)
declare #x xml = (select string
,REPLACE(STR(id,8),' ','0') AS [id/#formatted]
,id
from #T
order by id for xml path('row'), root('root'),TYPE)
SELECT (
STUFF(
(
SELECT ';' + r.value('(string/text())[1]','nvarchar(max)')
+ ';' + r.value('(id/#formatted)[1]','nvarchar(max)')
FOR XML PATH('')
),1,1,'')
) as [OUTPUT]
FROM #x.nodes('/root/row') AS A(r);
If you need the dynamic approach - look at this
declare #T table (string nchar(10), id int)
insert #T values
('test1', 1)
,('test2', 2)
declare #X xml = (select * from #T order by id for xml path('row'), root('root'))
select #x;
The first thing you see, that you - probably want to use NVARCHAR(10) instead of NCHAR(10). You might use LTRIM() too:
<root>
<row>
<string>test1 </string>
<id>1</id>
</row>
<row>
<string>test2 </string>
<id>2</id>
</row>
</root>
Now I start from scratch with NVARCHAR(10)
declare #T2 table (string nvarchar(10), id int);
insert #T2 values
('test1', 1)
,('test2', 22);
declare #X2 xml = (select * from #T2 order by id for xml path('row'), root('root'));
--First try is with ISNUMERIC and CASE WHEN
SELECT (
STUFF(
(
SELECT ';' + CASE WHEN ISNUMERIC(v.value('.','nvarchar(max)'))=1
THEN REPLACE(STR(v.value('.','int'),8),' ','0')
ELSE v.value('.','nvarchar(max)') END
FROM r.nodes('*') AS B(v)
FOR XML PATH('')
),1,1,'')
) as [OUTPUT]
FROM #x2.nodes('/root/row') AS A(r);
But: There are some character formats (scientific notations), which can be taken as numeric incidentically.
Add this to your table and try again
,('3d2',333) --breaks, because SELECT ISNUMERIC('3d2'),ISNUMERIC('1e1') returns 1 (for both)
Other/better approaches
If you are using SQL-Server 2012 or higher, you can use TRY_CAST, which will return NULL instead of an error
SELECT (
STUFF(
(
SELECT ';' + CASE WHEN TRY_CAST(v.value('.','nvarchar(max)') AS INT) IS NOT NULL
THEN REPLACE(STR(v.value('.','int'),8),' ','0')
ELSE v.value('.','nvarchar(max)') END
FROM r.nodes('*') AS B(v)
FOR XML PATH('')
),1,1,'')
) as [OUTPUT]
FROM #x2.nodes('/root/row') AS A(r);
Another option might the the explicit XQuery cast (Find possible XQuery functions here)
SELECT (
STUFF(
(
SELECT ';' + v.query('let $nd:=string(./text()[1])
,$nr:=concat("00000000",string($nd cast as xs:int?))
return if(string-length($nr)=8)
then $nd
else substring($nr,string-length($nr)-7)').value('.','nvarchar(max)')
FROM r.nodes('*') AS B(v)
FOR XML PATH('')
),1,1,'')
) as [OUTPUT]
FROM #x2.nodes('/root/row') AS A(r);
UPDATE
XML is not aware of an underlying type unless you specify a schema. Look at this:
declare #T2 table (string nvarchar(10), id int)
insert #T2 values
('test1', 1)
,('test2', 22)
,('1',333)
declare #x2 xml = (select * from #T2 order by id for xml raw('row'), root('root'),xmlschema)
select #x2;
<root>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:sqltypes="http://schemas.microsoft.com/sqlserver/2004/sqltypes" targetNamespace="urn:schemas-microsoft-com:sql:SqlRowSet1" elementFormDefault="qualified">
<xsd:import namespace="http://schemas.microsoft.com/sqlserver/2004/sqltypes" schemaLocation="http://schemas.microsoft.com/sqlserver/2004/sqltypes/sqltypes.xsd" />
<xsd:element name="row">
<xsd:complexType>
<xsd:attribute name="string">
<xsd:simpleType>
<xsd:restriction base="sqltypes:nvarchar" sqltypes:localeId="1033" sqltypes:sqlCompareOptions="IgnoreCase IgnoreKanaType IgnoreWidth">
<xsd:maxLength value="10" />
</xsd:restriction>
</xsd:simpleType>
</xsd:attribute>
<xsd:attribute name="id" type="sqltypes:int" />
</xsd:complexType>
</xsd:element>
</xsd:schema>
<row xmlns="urn:schemas-microsoft-com:sql:SqlRowSet1" string="test1" id="1" />
<row xmlns="urn:schemas-microsoft-com:sql:SqlRowSet1" string="test2" id="22" />
<row xmlns="urn:schemas-microsoft-com:sql:SqlRowSet1" string="1" id="333" />
</root>
In this case you might ask the schema for the underlying type. But - if I get you correctly - you want to look at any value if it might be a number. This works as shown, but is extremely dangerous...
Good afternoon all.
Currently, I have make a small demo with XML in SQl Server.
I have table with name: tb_xml_demo(ID, Name, Descr)
And each time when I insert to this table. ID column like this:
001, 002, 003 ....
This is my procedure
alter proc sp_xml_demo_cud
#p_xml xml
as
begin
declare #dochandle int;
exec sp_xml_preparedocument #dochandle output,#p_xml;
insert into tb_xml_demo(id, name, descr)
select
(select
format(isnull(convert(int, max(id)), 0) + 1, '000')
from tb_xml_demo) id,
name,
descr
from
OPENXML(#dochandle,'/root/item',2)
with
(name nvarchar(50),
descr nvarchar(50),
crud varchar(1)
)
end;
And this is my xml:
exec sp_xml_demo_cud
'<root>
<item>
<name>9876543</name>
<descr>1sdfsd</descr>
</item>
<item>
<name>333</name>
<descr>333</descr>
</item>
</root>';
And this is result after executing the procedure:
id Name Descr
001 9876543 1sdfsd
001 333 333
Please help me.
Thanks a lot.
I would recommend doing this:
create your table with a ID INT IDENTITY(1,1) column to let SQL Server handle the generation of unique ID values
add a computed column (I called it PaddedID) that uses the system-generated, valid ID to display with leading zeroes
parse the XML with the built-in, native XQuery functionality (instead of the legacy OPENXML stuff which is notorious for memory leaks)
This gives me this code:
-- create your table
CREATE TABLE tb_xml_demo
(ID INT IDENTITY(1,1) PRIMARY KEY CLUSTERED,
PaddedID AS RIGHT('0000' + CAST(ID AS VARCHAR(4)), 4) PERSISTED,
Name VARCHAR(50),
Descr VARCHAR(100)
)
-- declare your INPUT XML document
DECLARE #input XML = '<root>
<item>
<name>9876543</name>
<descr>1sdfsd</descr>
</item>
<item>
<name>333</name>
<descr>333</descr>
</item>
</root>'
-- parse the XML using XQuery and insert the results into that newly created table
INSERT INTO dbo.tb_xml_demo
(Name, Descr)
SELECT
ItemName = xc.value('(name)[1]', 'varchar(50)'),
ItemDescr = xc.value('(descr)[1]', 'varchar(100)')
FROM
#input.nodes('/root/item') AS XT(XC)
-- select the values from the table
SELECT * FROM dbo.tb_xml_demo
and this results in an output of:
I have table that contains three columns:
[ID],[Name],[Value]
My select for xml result:
DECLARE #xml XML
SET #xml = (
SELECT
ID
,[Name]
,[Value]
FROM [CustomerDetails]
WHERE ID = 1
FOR XML PATH(''), ROOT('Customer')
)
SELECT #xml
My select return xml with multiple ID properties:
<Customer>
<ID>1</ID>
<Name>FirstName</Name>
<Value>firstName</Value>
<ID>1</ID>
<Name>LastName</Name>
<Value>lastName</Value>
<ID>1</ID>
<Name>Age</Name>
<Value>20</Value>
<ID>1</ID>
<Name>City</Name>
<Value>London</Value>
</Customer>
I need next xml:
<Customer>
<ID>1</ID>
<Name>FirstName</Name>
<Value>firstName</Value>
<Name>LastName</Name>
<Value>lastName</Value>
<Name>Age</Name>
<Value>20</Value>
<Name>City</Name>
<Value>London</Value>
</Customer>
How to return this kind of XML?
I have shortened name of columns:
declare #id int = 1
select id, n, v from
(select #id id, null n, null v, 1 as rn from t
union
select null, n, v, 2 as rn from t
where id = #id
) t order by rn
for xml path(''), root('customer')
Output:
<customer><id>1</id><n>n1</n><v>v1</v><n>n2</n><v>v2</v><n>n3</n><v>v3</v></customer>
Fiddle http://sqlfiddle.com/#!3/70ea0/4
I can't get the value for the XML attribute 'Country' into my table.
What am I doing wrong?
Here is my XML:
<?xml version="1.0" encoding="utf-8"?>
<CustomerDetails>
<PersonalInfo Country="USA">
<CustID>1001</CustID>
<CustLastName>Smith</CustLastName>
<DOB>2011-05-05T09:25:48.253</DOB>
<Address>
<Addr1>100 Smith St.</Addr1>
<City>New York</City>
</Address>
</PersonalInfo>
</CustomerDetails>
Here is my SQL:
Drop table #Cust
CREATE TABLE #Cust
(CustID INT, CustLastName VARCHAR(10)
, DOB DATETIME, Addr1 VARCHAR(100), City VARCHAR(10), Country VARCHAR(20))
insert into #Cust
select
c3.value('CustID[1]','int'),
c3.value('CustLastName[1]','varchar(10)'),
c3.value('DOB[1]','DATETIME'),
c3.value('(Address/Addr1)[1]','VARCHAR(100)'),
c3.value('(Address/City)[1]','VARCHAR(10)'),
c3.value('Country[1]','VARCHAR(20)')
from
(
select
cast(c1 as xml)
from
OPENROWSET (BULK 'C:\Users\wattronts\Documents\XMLImportTest.xml',SINGLE_BLOB) as T1(c1)
)as T2(c2)
cross apply c2.nodes('/CustomerDetails/PersonalInfo') T3(c3)
Select * from #Cust
Thanks for your help.
Use # to specify that you want an attribute.
T3.c3.value('#Country', 'varchar(50)')