XML question in SQL Server - sql-server

In one of my sql scripts, I need to execute a stored procedure with the following xml string
<Collection xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Field>
<Attributes>
<Attribute Name="CODE1" IsRequired="true" Order="1" IsVisible="true"/>
<Attribute Name="CODE2" IsRequired="true" Order="2" IsVisible="true"/>
</Attributes>
<Rows>
<Row ProductState="5">
<Items>
<Item Name="PROD1" SendCustomer="false"/>
<Item Name="PROD2" SendCustomer="false"/>
</Items>
</Row>
</Rows>
</Field>
</Collection>
I get the Attribute and the Item information from different tables. I am writing a generic function in which you pass an ID and returns this XML string that is used by the SQL script to execute the stored procedure
Sometimes, I need to override the attribute values of some elements like SendCustomer. My initial thought was to deserialize this to a temp table, update the temp table with the override value and then serialize it back to XML.
So, essentially, the entire process boils down to:
Query tables, serialize to XML in the function
Deserialze XML, store in temp table
Override values if necessary
Serialze from table to XML again
Is there a more elegant way in sql server 2005 to do this entire process?

The XML datatype actually can be modified using XQuery. See the modify() method.
declare #x XML;
select #x = N'<Collection xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Field>
<Attributes>
<Attribute Name="CODE1" IsRequired="true" Order="1" IsVisible="true"/>
<Attribute Name="CODE2" IsRequired="true" Order="2" IsVisible="true"/>
</Attributes>
<Rows>
<Row ProductState="5">
<Items>
<Item Name="PROD1" SendCustomer="false"/>
<Item Name="PROD2" SendCustomer="false"/>
</Items>
</Row>
</Rows>
</Field>
</Collection>';
set #x.modify(N'replace value of
(/Collection/Field/Rows/Row/Items/Item[#Name="PROD2"]/#SendCustomer)[1]
with "true"');
select #x;

Related

Convert XML from one format to another

I have this below xml data which is stored in a table.
The XML Structure I have
<Response>
<Question ID="1">
<Value ID="1">I want a completely natural childbirth - no medical interventions for me</Value>
<Value ID="2">no medical interventions for me</Value>
</Question>
</Response>
I need to convert this XML to a slightly different format, like the below one.
The XML Structure I need
<Response>
<Question ID="1">
<SelectedChoices>
<Choice>
<ID>1</ID>
</Choice>
<Choice>
<ID>2</ID>
</Choice>
</SelectedChoices>
</Question>
</Response>
Here the "Value" is changed to "Choice" and "ID" attribute of "Value" element is changed to an element.
I know this can be done in other ways, like using an XSLT. But it will be much more helpful if can accomplish with SQL itself.
Can someone help me to convert this using SQL?
Use this variable to test the statements
DECLARE #xml XML=
N'<Response>
<Question ID="1">
<Value ID="1">I want a completely natural childbirth - no medical interventions for me</Value>
<Value ID="2">no medical interventions for me</Value>
</Question>
</Response>';
This can be done with FLWOR-XQuery:
The query will re-build the XML out of itself... Very similar to XSLT...
SELECT #xml.query(
N'
<Response>
{
for $q in /Response/Question
return
<Question ID="{$q/#ID}">
<SelectedChoices>
{
for $v in $q/Value
return <Choice><ID>{string($v/#ID)}</ID></Choice>
}
</SelectedChoices>
</Question>
}
</Response>
'
);
Another approach: Shredding and re-build
You'd reach the same with this, but I'd prefere the first...
WITH Shredded AS
(
SELECT q.value('#ID','int') AS qID
,v.value('#ID','int') AS vID
FROM #xml.nodes('/Response/Question') AS A(q)
OUTER APPLY q.nodes('Value') AS B(v)
)
SELECT t1.qID AS [#ID]
,(
SELECT t2.vID AS ID
FROM Shredded AS t2
WHERE t1.qID=t2.qID
FOR XML PATH('Choice'),ROOT('SelectedChoices'),TYPE
) AS [node()]
FROM Shredded AS t1
GROUP BY t1.qID
FOR XML PATH('Question'),ROOT('Response')

Updating existing temp table with id created during set based insert

I have extracted some XML into a temporary table as follows:
declare #INT_ParticipantID INT = 1
declare #XML_Results XML = '
<roots>
<root>
<ID />
<ResultDateTime>2016-08-16T13:58:21.484Z</ResultDateTime>
<Test>
<ID>5</ID>
<ParticipantID>0</ParticipantID>
<Instrument />
<ControlSet />
<Assay />
<CreationDate>0001-01-01T00:00:00Z</CreationDate>
<StartDate>0001-01-01T00:00:00Z</StartDate>
<EndDate>0001-01-01T00:00:00Z</EndDate>
<Closed>false</Closed>
<SlideGenNumber>0</SlideGenNumber>
</Test>
<EnteredByInitials />
<ControlSetLots />
<LotResult1 />
<LotResult2 />
<LotResult3 />
<LotResults>
<ID>13</ID>
<LotNumber />
<LotName />
<ExpiryDate>0001-01-01T00:00:00Z</ExpiryDate>
<Result>
<ID />
<Count>1</Count>
<Mean>2</Mean>
<SD>3</SD>
</Result>
<ParticipantID>0</ParticipantID>
<ApprovalStatus>false</ApprovalStatus>
<LotAnalytes />
<LotInstruments />
<TestDetails />
</LotResults>
<LotResults>
<ID>14</ID>
<LotNumber />
<LotName />
<ExpiryDate>0001-01-01T00:00:00Z</ExpiryDate>
<Result>
<ID />
<Count>4</Count>
<Mean>5</Mean>
<SD>6</SD>
</Result>
<ParticipantID>0</ParticipantID>
<ApprovalStatus>false</ApprovalStatus>
<LotAnalytes />
<LotInstruments />
<TestDetails />
</LotResults>
<LotResults>
<ID>0</ID>
<LotNumber />
<LotName />
<ExpiryDate>0001-01-01T00:00:00Z</ExpiryDate>
<Result>
<ID />
<Count>1</Count>
<Mean>0</Mean>
<SD>0</SD>
</Result>
<ParticipantID>0</ParticipantID>
<ApprovalStatus>false</ApprovalStatus>
<LotAnalytes />
<LotInstruments />
<TestDetails />
</LotResults>
<StandardComment>
<ID>1</ID>
<EnteredBy />
<Description />
</StandardComment>
<ReviewComment>
<ID />
<EnteredBy />
<Description />
</ReviewComment>
</root>
</roots>
'
SELECT r.value('ID[1]','int') AS Transaction_ID
,r.value('ResultDateTime[1]', 'datetime') AS Transaction_DateTime
,r.value('(Test/ID)[1]', 'int') AS QCTest_ID
,lr.value('ID[1]','int') AS Lot_ID
,lr.value('(Result/Count)[1]','int') AS Result_Count
,lr.value('(Result/Mean)[1]','decimal(18, 8)') AS Result_Mean
,lr.value('(Result/SD)[1]','decimal(18, 8)') AS Result_SD
,r.value('(StandardComment/ID)[1]','int') AS StandardComment_ID
,r.value('(ReviewComment/ID)[1]','int') AS ReviewComment_ID
INTO #tempRawXML
FROM #XML_Results.nodes('/roots/root') AS A(r)
CROSS
APPLY r.nodes('LotResults') AS B(lr)
This brings me back the result set below:
I need to insert the results extracted into two tables - one is a mapping table and the other is determined by the Lot_ID field sent through the XML.
What I need to achieve is an INSERT into the mapping table, then extract the newly generated primary key field (which is an IDENTITY) and INSERT it into the relevant table(s) along with the remaining result data.
The most efficient way I can think to do this would be to UPDATE the existing Transaction_ID column in the #tempRawXML table with the OUTPUT of the first INSERT operation. Is there a way I can achieve this? So far I have the following - which creates a new row in the #tempRawXML table with the relevant Transaction_ID:
INSERT
INTO dbo.Result_Transaction_Mapping
(
fk_participant_id,
fk_test_id,
result_date_time,
fk_comment_id,
fk_review_comment_id
)
OUTPUT INSERTED.pk_id
INTO #tempRawXML(Transaction_ID)
SELECT #INT_ParticipantID,
QCTest_ID,
Transaction_DateTime,
StandardComment_ID,
ReviewComment_ID
FROM #tempRawXML
Is there a way I can modify the above so that instead of inserting new rows containing only the generated Transaction_ID, it updates the existing row in #tempRawXML?
After researching for a way to UPDATE the original tempRawXML table - to no avail - I have a solution for the initial problem using a combination of:
XML used:
declare #XML_Results XML = '
<roots>
<root>
<ID>-2</ID>
<ResultDateTime>2016-08-24T10:44:22.829Z</ResultDateTime>
<Test>
<ID>5</ID>
<ParticipantID>0</ParticipantID>
<Instrument />
<ControlSet />
<Assay />
<CreationDate>0001-01-01T00:00:00Z</CreationDate>
<StartDate>0001-01-01T00:00:00Z</StartDate>
<EndDate>0001-01-01T00:00:00Z</EndDate>
<Closed>false</Closed>
<SlideGenNumber>0</SlideGenNumber>
</Test>
<EnteredByInitials />
<ControlSetLots />
<LotResults>
<ID>13</ID>
<LotNumber />
<LotName />
<ExpiryDate>0001-01-01T00:00:00Z</ExpiryDate>
<Result>
<ID />
<Count>5</Count>
<Mean>6</Mean>
<SD>7</SD>
</Result>
<ParticipantID>0</ParticipantID>
<ApprovalStatus>false</ApprovalStatus>
<LotAnalytes />
<LotInstruments />
<TestDetails />
</LotResults>
<LotResults>
<ID>14</ID>
<LotNumber />
<LotName />
<ExpiryDate>0001-01-01T00:00:00Z</ExpiryDate>
<Result>
<ID />
<Count>1</Count>
<Mean>0</Mean>
<SD>0</SD>
</Result>
<ParticipantID>0</ParticipantID>
<ApprovalStatus>false</ApprovalStatus>
<LotAnalytes />
<LotInstruments />
<TestDetails />
</LotResults>
<LotResults>
<ID>0</ID>
<LotNumber />
<LotName />
<ExpiryDate>0001-01-01T00:00:00Z</ExpiryDate>
<Result>
<ID />
<Count>1</Count>
<Mean>0</Mean>
<SD>0</SD>
</Result>
<ParticipantID>0</ParticipantID>
<ApprovalStatus>false</ApprovalStatus>
<LotAnalytes />
<LotInstruments />
<TestDetails />
</LotResults>
<StandardComment>
<ID />
<EnteredBy />
<Description />
</StandardComment>
<ReviewComment>
<ID />
<EnteredBy />
<Description />
</ReviewComment>
</root>
<root>
<ID>-1</ID>
<ResultDateTime>2016-08-24T10:44:22.829Z</ResultDateTime>
<Test>
<ID>5</ID>
<ParticipantID>0</ParticipantID>
<Instrument />
<ControlSet />
<Assay />
<CreationDate>0001-01-01T00:00:00Z</CreationDate>
<StartDate>0001-01-01T00:00:00Z</StartDate>
<EndDate>0001-01-01T00:00:00Z</EndDate>
<Closed>false</Closed>
<SlideGenNumber>0</SlideGenNumber>
</Test>
<EnteredByInitials />
<ControlSetLots />
<LotResults>
<ID>13</ID>
<LotNumber />
<LotName />
<ExpiryDate>0001-01-01T00:00:00Z</ExpiryDate>
<Result>
<ID />
<Count>1</Count>
<Mean>0</Mean>
<SD>0</SD>
</Result>
<ParticipantID>0</ParticipantID>
<ApprovalStatus>false</ApprovalStatus>
<LotAnalytes />
<LotInstruments />
<TestDetails />
</LotResults>
<LotResults>
<ID>14</ID>
<LotNumber />
<LotName />
<ExpiryDate>0001-01-01T00:00:00Z</ExpiryDate>
<Result>
<ID />
<Count>1</Count>
<Mean>2</Mean>
<SD>3</SD>
</Result>
<ParticipantID>0</ParticipantID>
<ApprovalStatus>false</ApprovalStatus>
<LotAnalytes />
<LotInstruments />
<TestDetails />
</LotResults>
<LotResults>
<ID>0</ID>
<LotNumber />
<LotName />
<ExpiryDate>0001-01-01T00:00:00Z</ExpiryDate>
<Result>
<ID />
<Count>1</Count>
<Mean>0</Mean>
<SD>0</SD>
</Result>
<ParticipantID>0</ParticipantID>
<ApprovalStatus>false</ApprovalStatus>
<LotAnalytes />
<LotInstruments />
<TestDetails />
</LotResults>
<StandardComment>
<ID />
<EnteredBy />
<Description />
</StandardComment>
<ReviewComment>
<ID />
<EnteredBy />
<Description />
</ReviewComment>
</root>
</roots>
'
1) An additional temporary table for 'mapping' UI to IDENTITY generated IDs (thanks to #Pawel for the suggestion to get me on the right track).
NOTE: I am sending an incremental negative value from the UI for the Old_ID field to ensure that these values can never match up with an existing IDENTITY.
-- Hold mappings between old and processed IDs
-- Used when inserting into relevant lot tables following initial top level transaction insert
CREATE
TABLE #Processed_Transactions
(
Old_ID INT, -- ID supplied by UI (using a negative number to ensure no conflict with IDs from Result_Transaction_Mapping table)
ProcessedTransaction_ID INT -- ID generated during initial insert into Result_Transaction_Mapping table
)
2) MERGE combined with OUTPUT to insert the initial transaction into the and track the Old_ID / ProcessedTransaction_ID fields in the mapping temporary table.
A 1=0 scenario is raised at this point to ensure the INSERT is always triggered. This seems a little iffy but seems to be widely used.
Example from another question using MERGE instead of INSERT
-- Function to insert the top level Result Transaction
-- Required to populate OUTPUT variable in Processed_Transactions temporary table
MERGE dbo.Result_Transaction_Mapping AS RTM
USING
(
-- Extracts distinct UI assigned IDs and column information
SELECT DISTINCT Assigned_ID,
MAX(Transaction_DateTime) AS Transaction_DateTime,
MAX(QCTest_ID) as QCTest_ID,
MAX(StandardComment_ID) AS StandardComment_ID,
MAX(ReviewComment_ID) AS ReviewComment_ID,
MAX(Result_Count) AS Result_Count,
MAX(Result_Mean) AS Result_Mean,
MAX(Result_SD) AS Result_SD
FROM #tempRawXML
GROUP
BY Assigned_ID
) AS TR
-- Create 1 = 0 scenario to ensure the IDs never match up to what currently exists in the Result_Transaction_Mapping table
ON TR.Assigned_ID = RTM.pk_id
WHEN NOT MATCHED
-- Ensure at least one of the transaction result columns contain a value
-- This will also be verified on the UI
AND TR.Result_Count > 0
AND TR.Result_Mean > 0.0
AND TR.Result_SD > 0.0
THEN
INSERT
(
fk_participant_id,
fk_test_id,
result_date_time,
fk_comment_id,
fk_review_comment_id
)
VALUES
(
#INT_ParticipantID,
TR.QCTest_ID,
TR.Transaction_DateTime,
TR.StandardComment_ID,
TR.ReviewComment_ID
)
-- Following insert of a result, populate the INSERTED primary key field into the mappings table
OUTPUT TR.Assigned_ID,
INSERTED.pk_id
INTO #Processed_Transactions
(
Old_ID,
ProcessedTransaction_ID
);
Following this, I now have a combination of datasets which can be used to insert into the relevant Lot tables.
#tempRawXML table
ID mappings with UI negative mappings and IDENTITY IDs generated by the table
Which leads me to another predicament - the use of CURSORS and thus venturing back into the "dark acres of procedural approaches" (strongly advised against by #Shnugo in a previous question who I would imagine is 'curs'ing my name right about now.
Following a successful top-level result transaction INSERT and using the raw XML and the generated IDs above, I need to insert the remainder of the 'result specific' information to their own respective tables, the names of which have yet to be determined based on the result LotID. I have therefore setup the following combination of procedural, set based, dynamic SQL (if there is such a thing) to accomplish this:
-- recursively access each associated Lot table based on associated Lot ID's
DECLARE #LotNumber NVARCHAR(20), #LotID INT
-- Querystring to hold all set update calls
DECLARE #ResultQueryString NVARCHAR(MAX) = ''
DECLARE Lot_Cursor
CURSOR FAST_FORWARD
FOR
-- Select the lot numbers based on the available IDs
SELECT
DISTINCT L.pk_id AS LotID,
L.number AS LotNumber
FROM dbo.Lot L
LEFT
JOIN #tempRawXML TR
ON TR.Lot_ID = L.pk_id
WHERE L.pk_id IN (TR.Lot_ID)
OPEN Lot_Cursor
FETCH
NEXT
FROM Lot_Cursor
INTO #LotID, #LotNumber
WHILE ##fetch_status = 0
BEGIN
SET #ResultQueryString +=
N' MERGE dbo.[' + #LotNumber + '] AS L
USING
(
SELECT PT.ProcessedTransaction_ID,
TR.Result_ID,
TR.Result_Count,
TR.Result_Mean,
TR.Result_SD
FROM #tempRawXML TR
JOIN #Processed_Transactions PT
ON PT.Old_ID = TR.Assigned_ID
WHERE TR.Lot_ID = '+ CAST(#LotID AS NVARCHAR(20)) +'
) R
ON R.Result_ID = L.pk_id
WHEN NOT MATCHED
AND R.Result_Count > 0
AND R.Result_Mean > 0.0
AND R.Result_SD > 0.0
THEN
INSERT
(
fk_result_transaction_mapping_id,
count,
mean,
standard_deviation,
result_status
)
VALUES
(
R.ProcessedTransaction_ID,
R.Result_Count,
R.Result_Mean,
R.Result_SD,
1
); '
FETCH
NEXT
FROM Lot_Cursor
INTO #LotID, #LotNumber
END
CLOSE Lot_Cursor
DEALLOCATE Lot_Cursor
-- #Processed_Transactions temp table variable must be declared when executing dynamic sql
--EXEC sp_executesql #ResultQueryString, N'#Processed_Transactions MyTable READONLY', #Processed_Transactions=#Processed_Transactions
EXEC (#ResultQueryString)
My follow-up question here - is this an acceptable use of CURSORS (bearing in mind that there can only be a maximum of 6 iterations)? Additionally, is there a way I can avoid the use of CURSORs in this scenario?
Your question and your answer is quite a lot to read...
I want to offer you a very reduced MCVE (minimal, complete, verifiable example) to boild down your needs to the actual problem - as far as I understand it...
The following solution has one tiny need: The table with the IDENTITY ID must have a column for temporary storage of an external ID. If this is possible, you could use this much simpler approach:
--This table must have a column for temporary storage of the external ID
DECLARE #TableWithExistingData TABLE(ID INT IDENTITY,SomeData VARCHAR(100),ExternalID INT);
INSERT INTO #TableWithExistingData(SomeData) VALUES
('Data for ID=1'),('Data for ID=2');
--This is the existing data
SELECT * FROM #TableWithExistingData
--This is the derived table from your XML.
--You can use ROW_NUMBER() to create a running number on the fly.
--Use this as the rows temporary ID
--These new rows should be inserted in the table with existing data
--DataForOtherTable should be inserted in another table but with the newly created ID as FK
DECLARE #NewRows TABLE(ID INT,SomeNewData VARCHAR(100),DataForOtherTable VARCHAR(100));
INSERT INTO #NewRows(ID,SomeNewData,DataForOtherTable) VALUES
(1,'New value 1','More data 1'),(2,'New value 2','More data 2');
--This table will hold the newly created ID and the external ID
DECLARE #Mapping TABLE(nwID INT,extID INT);
--OUTPUT is great but can only return columns of the target table,
--hence the need to have the external ID within your table
INSERT INTO #TableWithExistingData(SomeData,ExternalID)
OUTPUT inserted.ID,inserted.ExternalID INTO #Mapping
SELECT nr.SomeNewData,nr.ID
FROM #NewRows AS nr;
--This is your other existing table, where you want to store values with the new ID as FK
DECLARE #SideTable TABLE(NewlyCreatedID INT,AndMoreDataForOtherTable VARCHAR(100));
--use the mapping table to get the ID into the table
INSERT INTO #SideTable
SELECT nwID,nr.DataForOtherTable
FROM #Mapping AS m
INNER JOIN #NewRows AS nr ON m.extID=nr.ID
--And this is the result in all tables
SELECT * FROM #NewRows
SELECT * FROM #TableWithExistingData
SELECT * FROM #SideTable;
One point to consider: If you use ROW_NUMBER and there is the same process happening in the same seconds, you might mix your external ID with the other process... You could use GUIDs or concatenate the ROW_NUMBER with a unique sessionID or whatever you can use there...

Call a procedure or function in Oracle DB with return = array of UDT from WSO2 DSS

I follow this post[1] as a guide to build an example of query an array of UDT in WSO2 DSS. In the post just query an UDT, my config try to query an UDT array.
I created this in my DB, a dummy PROCEDURE to try this:
create or replace
TYPE "LIST_CUSTOMERS" IS TABLE OF customer_t
CREATE OR REPLACE
PROCEDURE getCustomer2(listcust OUT list_customers) IS
cust customer_t;
cust2 customer_t;
BEGIN
listcust := list_customers();
cust := customer_t(1, 'prabath');
cust2 := customer_t(2, 'jorge');
listcust.extend;
listcust(1) := cust;
listcust.extend;
listcust(2) := cust2;
END;
My DS is this:
<?xml version="1.0" encoding="UTF-8"?>
<data name="UDTSample2">
<config id="default">
<property name="org.wso2.ws.dataservice.driver">oracle.jdbc.driver.OracleDriver</property>
<property name="org.wso2.ws.dataservice.protocol">jdbc:oracle:thin:#localhost:1521:DBMB</property>
<property name="org.wso2.ws.dataservice.user">****</property>
<property name="org.wso2.ws.dataservice.password">****</property>
</config>
<query id="q3" useConfig="default">
<sql>call getCustomer2(?)</sql>
<result element="customers">
<element name="customer" arrayName="custArray" column="cust" optional="true"/>
</result>
<param name="cust" paramType="ARRAY" sqlType="ARRAY" type="OUT" structType="LIST_CUSTOMERS" />
</query>
<operation name="op3">
<call-query href="q3" />
</operation>
</data>
ant return:
<customers xmlns="http://ws.wso2.org/dataservice">
<customer>{1,prabath}</customer>
<customer>{2,jorge}</customer>
</customers>
but I want something like this:
<customers xmlns="http://ws.wso2.org/dataservice">
<customer>
<id>1</id>
<name>prabath<name>
</customer>
<customer>
<id>2</id>
<name>Jorge<name>
</customer>
</customers>
How can I accomplish this?
[1] http://prabathabey.blogspot.com/2012/05/query-udtsuser-defined-types-with-wso2.html
Not sure whether this kind of transformation can be done at DSS level because DSS gives back what it recieves from database. Better use WSO2 esb for this kind of transformation.
By the moment it's not possible to accomplish this scenario using just DSS. the DS response must be send to the WSO2 ESB to do the corresponding transformation before send the response to the client. A JIRA was created to do this in the future https://wso2.org/jira/browse/DS-1104
As a workaround, you can use a procedure returning a sys_refcursor.
It would look like this:
PROCEDURE getCustomer_CUR(cur_cust OUT SYS_REFCURSOR)
l_cust LIST_CUSTOMERS;
IS
-- Retrieve cust list:
getCustomer2(l_cust);
OPEN cur_cust for
select cast(multiset(select * from TABLE(l_cust)) as customer_t) from dual;
...
END;
Then you can do your DSS mapping something like:
<sql>call getCustomer_CUR(?)</sql>
<result element="customers">
<element arrayName="custArray" name="Customers">
<element column="custArray[0]" name="col0" xsdType=.../>
...
</element>
</result>
<param name="cust" sqlType="ORACLE_REF_CURSOR" type="OUT"/>
It is tedious but it works.

Query ELMAH's XML field

The stock ELMAH_Error table uses an nText field to store an Error entry. I found that by adding a field of type XML; then adding this new field to the INSERT statement of the SPROC that populates the field; i could make better use of ELMAH's output.
Now I'd like to learn how to query specific element values within that XML field. The document is structured as:
<error [...]>
<serverVariables>
<item name="ALL_HTTP">
<value string="..." />
</item>
<item name="ALL_RAW">
<value string="..." />
</item>
.
.
.
</serverVariables>
</error>
I need to be able to query the value of specific items beneath .
So I'm looking at an example from the 15seconds.com article:
SELECT MyXml.value('(/root/product[#id="304"]/name)[1]', 'nvarchar(30)')
and am trying to maps those values to my field's structure - but can't. E.g.
select top 10 RealXML.value('(/error/serverVariables[#id="REMOTE_HOST"]/name)[0]', 'nvarchar(30)')
where REMOTE_HOST is formatted:
<item name="REMOTE_HOST">
<value string="55.55.55.55" />
</item>
much appreciated
This should work:
select top 10 RealXML.value('(/error/serverVariables/item[#name="REMOTE_HOST"]/value/#string)[1]', 'nvarchar(30)')
Tested using the following:
DECLARE #xml XML = '
<error>
<serverVariables>
<item name="ALL_HTTP">
<value string="..." />
</item>
<item name="ALL_RAW">
<value string="..." />
</item>
<item name="REMOTE_HOST">
<value string="55.55.55.55" />
</item>
</serverVariables>
</error>
'
SELECT #xml.value('(/error/serverVariables/item[#name="REMOTE_HOST"]/value/#string)[1]','nvarchar(30)')

XPath to fetch SQL XML value

Here is my problem: from the following XML that is within a column, I want to know if the value of a variable with the name 'Enabled' is equal to 'Yes' given a step Id and a component Id.
'<xml>
<box stepId="1">
<components>
<component id="2">
<variables>
<variable id="3" nom="Server" valeur="DEV1" />
<variable id="4" nom="Enabled" valeur="Yes" />
</variables>
</component>
<component id="3">
<variables>
<variable id="3" nom="Server" valeur="DEV1" />
<variable id="4" nom="Enabled" valeur="No" />
</variables>
</component>
</components>
</box>
<box stepId="2">
<components>
<component id="2">
<variables>
<variable id="3" nom="Server" valeur="DEV2" />
<variable id="4" nom="Enabled" valeur="Yes" />
</variables>
</component>
<component id="3">
<variables>
<variable id="3" nom="Server" valeur="DEV2" />
<variable id="4" nom="Enabled" valeur="No" />
</variables>
</component>
</components>
</box>
</xml>'
XQuery Against the xml Data Type
General XQuery Use Cases
XQueries Involving Hierarchy
XQueries Involving Order
Anything in Michael Rys blog
Update
My recomendation would be to shred the XML into relations and do searches and joins on the resulted relation, in a set oriented fashion, rather than the procedural fashion of searching specific nodes in the XML. Here is a simple XML query that shreds out the nodes and attributes of interest:
select x.value(N'../../../../#stepId', N'int') as StepID
, x.value(N'../../#id', N'int') as ComponentID
, x.value(N'#nom',N'nvarchar(100)') as Nom
, x.value(N'#valeur', N'nvarchar(100)') as Valeur
from #x.nodes(N'/xml/box/components/component/variables/variable') t(x)
However, if you must use an XPath that retrieves exactly the value of interest:
select x.value(N'#valeur', N'nvarchar(100)') as Valeur
from #x.nodes(N'/xml/box[#stepId=sql:variable("#stepID")]/
components/component[#id = sql:variable("#componentID")]/
variables/variable[#nom="Enabled"]') t(x)
If the stepID and component ID are columns, not variables, the you should use sql:column() instead of sql:variable in the XPath filters. See Binding Relational Data Inside XML Data.
And finaly if all you need is to check for existance you can use the exist() XML method:
select #x.exist(
N'/xml/box[#stepId=sql:variable("#stepID")]/
components/component[#id = sql:variable("#componentID")]/
variables/variable[#nom="Enabled" and #valeur="Yes"]')
I always go back to this article SQL Server 2005 XQuery and XML-DML - Part 1 to know how to use the XML features in SQL Server 2005.
For basic XPath know-how, I'd recommend the W3Schools tutorial.
I think the xpath query you want goes something like this:
/xml/box[#stepId="$stepId"]/components/component[#id="$componentId"]/variables/variable[#nom="Enabled" and #valeur="Yes"]
This should get you the variables that are named "Enabled" with a value of "Yes" for the specified $stepId and $componentId. This is assuming that your xml starts with an tag like you show, and not
If the SQL Server 2005 XPath stuff is pretty straightforward (I've never used it), then the above query should work. Otherwise, someone else may have to help you with that.

Resources