I have a SQL Server XML column with data like this:
<History>
<Entry>
.....
</Entry>
<Entry>
.....
</Entry>
</History>
I need to add a unique identifier to each Entry element, giving this result:
<History>
<Entry entryID="AAA">
.....
</Entry>
<Entry entryID="BBB">
.....
</Entry>
</History>
I have it working for the first occurrence of the Entry element but don't know how to apply it to all occurrences. Also, this entryID is a GUID and I don't know how to generate a new one for each row.
Here is how I update the first element.
declare #eventId varchar(64)
set #eventId = CONVERT(varchar(64),NEWID())
update Histories
set XmlHistory.modify('
insert attribute EntryID {sql:variable("#eventId")}
into (History/Entry)[1]
')
where HistoryID=285162
I also have query that selects the elements that need this attribute and do not already have it. This gives the primary key and the element I need to update. I can't get a unique element identifier to use for the element array index.
select h.id rowPK, m.c.query('.') theElement
from TheTable h
cross apply h.XMLColumn.nodes('History/Entry[not(#EntryID)]') m(c)
where XMLColumn.exist('(History/Entry)')= 1
Manipulating XML in SQL Server can be very difficult, if you have the option, any other option! you should apply the unique id's before loading this as XML to Sql Server. The best I could do was to shred the XML to a table variable, add the keys and then extract as an XML stream. I hope this helps ...
Note, this does check for duplicate keys so you would need to handle that and you will need to include any additional nodes or elements that you did not reference in the question.
declare #Histories table
(
HistoryID int,
XmlHistory xml
)
insert into #Histories values (285162, '
<History>
<Entry>
Entry 1
</Entry>
<Entry>
Entry 2
</Entry>
<Entry>
Entry 3
</Entry>
<Entry>
Entry 4
</Entry>
</History>');
declare #tmp table
(
EntryVal varchar(max),
EntryGuid varchar(64)
)
insert into #tmp(EntryVal, EntryGuid)
SELECT p1.value(N'.[1]','nvarchar(max)') AS EntryValue, CONVERT(varchar(64),NEWID())
from #Histories H1
outer apply H1.XmlHistory.nodes(N'/History/Entry') AS A(p1)
select EntryGuid as '#ID', EntryVal as "data()"
from #tmp
for XML PATH ('ENTRY'), root ('HISTORY');
The output should look like this
<HISTORY>
<ENTRY ID="1C5C9492-36C8-4E4E-9AE3-DF7E2F1C1948">
Entry 1
</ENTRY>
<ENTRY ID="9AC4BB5D-C471-4C89-947B-8C17D2BD446C">
Entry 2
</ENTRY>
<ENTRY ID="10A81C91-A58B-4846-A857-A14BFB7F9CB7">
Entry 3
</ENTRY>
<ENTRY ID="0E65D134-37A2-489C-8C72-5BE52D08D7B1">
Entry 4
</ENTRY>
</HISTORY>
Related
On SQL Server, I have an XML field on a record and it contains multiple nodes of a similar type, but with different IDs.
I want to pull a few IDs from that XML and return on one row.
Here is a simple example of the XML:
...
<Items>
<Item>
<ItemID>1</ItemID>
<ItemValue>A</ItemValue>
</Item>
<Item>
<ItemID>2</ItemID>
<ItemValue>B</ItemValue>
</Item>
</Items>
I want to output the values of the specific Items I'm searching for on one row.
Something like
select XML_Values
from the_table
where conditions_met = true
I have used cross apply and can get just the nodes of the required values returning, but the output is all on separate rows.
Ideally the output Id like would be something along the lines of:
| Id=1 | Id=2 | (Column headers)
| A | B |
I'd be super grateful for any help.
Please try the following solution.
SQL
-- DDL and sample data population, start
DECLARE #tbl TABLE (ID INT IDENTITY PRIMARY KEY, xmldata XML);
INSERT INTO #tbl (xmldata) VALUES
(N'<Items>
<Item>
<ItemID>1</ItemID>
<ItemValue>A</ItemValue>
</Item>
<Item>
<ItemID>2</ItemID>
<ItemValue>B</ItemValue>
</Item>
</Items>');
-- DDL and sample data population, end
SELECT ID
, c.value('(Item[1]/ItemValue/text())[1]','VARCHAR(30)') AS Item1
, c.value('(Item[2]/ItemValue/text())[1]','VARCHAR(30)') AS Item2
FROM #tbl
CROSS APPLY xmldata.nodes('/Items') AS t(c);
Output
+----+-------+-------+
| ID | Item1 | Item2 |
+----+-------+-------+
| 1 | A | B |
+----+-------+-------+
Scenario: in SQL Server, I have a table [users] with columns [id] int and userdetails nvarchar(max) which contains data like:
<Attributes>
<Map>
<entry key="displayName" value="Administrator"/>
<entry key="email" value="joe.blow#google.com"/>
<entry key="firstname" value="Joe"/>
<entry key="lastname" value="Blow"/>
</Map>
</Attributes>
In a query I'd would like to extract as individual columns, like so:
displayName | email | firstname | lastname
What syntax could I use to accomplish this? (preferably in a performant manner as I have lots of queries to perform against potentially large tables....but even getting it working at all would be lovely)
What I've tried so far: 2+ hours of googling and trying various different syntax with no success (failure due to a variety of error messages, the resolution of each also being unsuccessful).
A secondary question might be whether there is a way to accommodate possibly missing key values in some rows (ie: email may not be specified, at all)
You can use SQL XML functions and XPath query. This is an example.
declare #tbl table(id int, userdetails nvarchar(max))
insert #tbl(id,userdetails)
values(1,'<Attributes>
<Map>
<entry key="displayName" value="Administrator"/>
<entry key="email" value="joe.blow#google.com"/>
<entry key="firstname" value="Joe"/>
<entry key="lastname" value="Blow"/>
</Map>
</Attributes>')
;with tbl as (
select id,cast(userdetails as xml) ud
from #tbl)
select id,
t.v.value('entry[#key="displayName"][1]/#value','nvarchar(100)') displayName,
t.v.value('entry[#key="email"][1]/#value','nvarchar(100)') email,
t.v.value('entry[#key="firstname"][1]/#value','nvarchar(100)') firstname,
t.v.value('entry[#key="lastname"][1]/#value','nvarchar(100)') lastname
from tbl
cross apply ud.nodes('Attributes/Map') t(v)
Here is my XML Format:
<ROOT>
<Orders>
<OrderID>423</OrderID>
<ProductID>54</ProductID>
<ProductID>23</ProductID>
</Orders>
<Orders>
<OrderID>523</OrderID>
<ProductID>5</ProductID>
<ProductID>26</ProductID>
</Orders>
I want to have my output in the below format
OrderID ProductID
423 54
423 23
523 5
523 26
I found ways that help me read attribute from XML and convert it to SQL table. But didn't find ways or solution for my XML input.
Any help is appreciated. Thanks.
Try it like this
DECLARE #xml XML=
'<ROOT>
<Orders>
<OrderID>423</OrderID>
<ProductID>54</ProductID>
<ProductID>23</ProductID>
</Orders>
<Orders>
<OrderID>523</OrderID>
<ProductID>5</ProductID>
<ProductID>26</ProductID>
</Orders>
</ROOT>';
SELECT Ord.value('OrderID[1]','int') AS OrderID
,Prod.value('.','int') AS ProductID
FROM #xml.nodes('/ROOT/Orders') AS A(Ord)
CROSS APPLY A.Ord.nodes('ProductID') AS B(Prod)
As your leading value is the ProductID you need a nodes()-call for this. It was possible to use only one .nodes('/ROOT/Orders/ProductID') and find the corresponding OrderID with a backward move:
SELECT Prod.value('(../OrderID)[1]','int') AS OrderID
,Prod.value('.','int') AS ProductID
FROM #xml.nodes('/ROOT/Orders/ProductID') AS B(Prod)
But it is cleaner and better performing to move down the tree step by step...
I am trying to insert data from XML string
<customer ID="1">
<Value HNAME="Holand" Revenue="12,7865"/>
<Value XNAME="Hemmer" Revenue="15,7865"/>
82 more "Value" child with different set of revenue and <AlphabetCombinations>NAME values.....
<customer>
If XML came with same ID in customer tag
<customer ID="1">
<Value HNAME="Holand" Revenue="12,7865"/>
<Value XNAME="Hemmer" Revenue="17,7425"/>
82 more "Value" child with different set of revenue and <AlphabetCombinations>NAME values.....
<customer>
Above XML value of Revenue is change for XNAME="Hemmer",I have to update value of only that column which is updated.there is 84 columns in my table,is there any query which update record for Customer ID =1 and update only modified column,I don't intended to update all the values again for customer.
Customer(ID,HNAME,XNAME,.....)
I'm quite new to FOR XML in SQL Server, I've searched considerable and I can't find an answer to this.
Can I have a variable element name using 'for xml' where the element name is not hard-coded and is instead take from a cell in each row? Take the following example...
Table ORDERS:
ID STATUS TIME AMOUNT
------------------------------------
1 COMPLETE 02:31 2355
2 ACCEPTED 02:39 6653
3 ACCEPTED 04:21 4102
4 RECEIVED 05:03 4225
FOR XML query:
select ID,
TIME as STATUS_TIME,
AMOUNT as CURRENT_AMOUNT
from ORDERS
for xml raw(' **STATUS NAME HERE** '),root('ORDERS'), elements
Required output:
<ORDERS>
<COMPLETE> <<<<--- Variable element name from STATUS in ORDERS
<ID>1</ID>
<STATUS_TIME>02:31</STATUS_TIME>
<CURRENT_AMOUNT>2355</CURRENT_AMOUNT>
</COMPLETE>
<ACCEPTED> <<<<--- Variable element name from STATUS in ORDERS
<ID>2</ID>
<STATUS_TIME>02:39</STATUS_TIME>
<CURRENT_AMOUNT>6653</CURRENT_AMOUNT>
</ACCEPTED>
<ACCEPTED> <<<<--- Variable element name from STATUS in ORDERS
<ID>3</ID>
<STATUS_TIME>04:21</STATUS_TIME>
<CURRENT_AMOUNT>4102</CURRENT_AMOUNT>
</ACCEPTED>
<RECEIVED> <<<<--- Variable element name from STATUS in ORDERS
<ID>4</ID>
<STATUS_TIME>05:03</STATUS_TIME>
<CURRENT_AMOUNT>4225</CURRENT_AMOUNT>
</RECEIVED>
</ORDERS>
I know I'm able to give attributes to the element names, and that I could give the individual ORDER in ORDERS and attribute of STATUS like below but unfortunately that's not what the people that will receive the XML document are looking for :(
select ID,
STATUS as '#STATUS'
TIME as STATUS_TIME,
AMOUNT as CURRENT_AMOUNT
from ORDERS
for xml raw('ORDER'),root('ORDERS'), elements
Output:
<ORDERS>
<ORDER STATUS='COMPLETE'> <<<<--- Attribute for STATUS but not what I want
<ID>1</ID>
<STATUS_TIME>02:31</STATUS_TIME>
<CURRENT_AMOUNT>2355</CURRENT_AMOUNT>
</ORDER>
<ORDER STATUS='ACCEPTED'> <<<<--- Attribute for STATUS but not what I want
<ID>2</ID>
<STATUS_TIME>02:39</STATUS_TIME>
<CURRENT_AMOUNT>6653</CURRENT_AMOUNT>
</ORDER>
....
I'd like to be able to do all this within SQL Server if possible. Many, many thanks if you can help me at all on this.
You can't specify column value in XML Raw(). So what you have to do is select required column from select query and cast result into XML, like this -
Schema
DECLARE #temp table (ID int, [STATUS] [varchar](100) NOT NULL, [TIME] [varchar](100), AMOUNT int);
INSERT #temp (ID, [STATUS], [TIME], AMOUNT) VALUES (1, 'COMPLETE', '02:31', 2355),(2, 'ACCEPTED', '02:41', 6653),(3, 'ACCEPTED', '02:31', 4102),(4, 'ACCEPTED', '02:31', 4225)
Query
SELECT
CAST('<' + STATUS + '>' +
'<ID>' + CAST(ID AS varchar) + '</ID>' +
'<TIME>' + TIME + '</TIME>' +
'<AMOUNT>' + CAST(AMOUNT AS varchar) + '</AMOUNT>' +
'</' + STATUS + '>' AS XML) from #temp
FOR XML PATH(''),root('ORDERS')
Output
<ORDERS>
<COMPLETE>
<ID>1</ID>
<TIME>02:31</TIME>
<AMOUNT>2355</AMOUNT>
</COMPLETE>
<ACCEPTED>
<ID>2</ID>
<TIME>02:41</TIME>
<AMOUNT>6653</AMOUNT>
</ACCEPTED>
<ACCEPTED>
<ID>3</ID>
<TIME>02:31</TIME>
<AMOUNT>4102</AMOUNT>
</ACCEPTED>
<ACCEPTED>
<ID>4</ID>
<TIME>02:31</TIME>
<AMOUNT>4225</AMOUNT>
</ACCEPTED>
</ORDERS>
In SQL Server, XML schema has to be static, so it is impossible to specify a variable element name (be it document or attribute).
If possible options for the STATUS field are limited and stable, you can mention them all explicitly, like in the example below:
select (
select t.ID, t.TIME as [STATUS_TIME], t.AMOUNT as [CURRENT_AMOUNT]
from #temp t
where t.STATUS = 'ACCEPTED'
for xml path('ACCEPTED'), type, elements
), (
select t.ID, t.TIME as [STATUS_TIME], t.AMOUNT as [CURRENT_AMOUNT]
from #temp t
where t.STATUS = 'COMPLETE'
for xml path('COMPLETE'), type, elements
)
for xml path('ORDERS'), type;
I think you have already noticed numerous possibilities for how this code can betray you, but frankly this approach is the only one available which does not include string manipulations (they will be detrimental to performance if the size of the XML output will be at least several Mb).
As a possible workaround, you can generate this query dynamically, including as many sections as there are distinct STATUS values in your table. Very ugly, but it will work.