Updating existing temp table with id created during set based insert - sql-server

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...

Related

SQL Server XML file with multiple nodes named the same

I have this inner XML which I am passing across to a SQL Server stored procedure.
As you can see, it contains multiple root nodes but additionally, it can also contain 1 to 'n' number of LotResults child nodes.
Is there a way I can manipulate this in the stored procedure so that I can retrieve all LotResults/Result nodes for each root node?
So far I have declared the cursor which can deal with the top-level nodes:
DECLARE cur CURSOR FOR
SELECT tab.col.value('ID[1]','NCHAR(10)') as INT_TransactionID,
tab.col.value('ResultDateTime[1]','INT') as DAT_ResultDateTime,
tab.col.value('StandardComment[1]/ID[1]','BIT') as INT_StandardCommentID,
tab.col.value('ReviewComment[1]/ID[1]','BIT') as INT_ReviewCommentID
FROM #XML_Results.nodes('/roots/root') AS tab(col)
OPEN cur
-- loop over nodes within xml document and populate declared variables
FETCH
NEXT
FROM cur
INTO #INT_TransactionID,
#DAT_ResultDateTime,
#INT_StandardCommentID,
#INT_ReviewCommentID
WHILE ##FETCH_STATUS = 0
BEGIN
BEGIN
-- use my values here
END
-- fetch next record
FETCH
NEXT
FROM cur
INTO #INT_TransactionID,
#DAT_ResultDateTime,
#INT_StandardCommentID,
#INT_ReviewCommentID
END
CLOSE cur;
Note: I found a post describing how to extract nodes with the same name and I feel like it is something that can be used to achieve what I want to do here but I need some guidance on how this can be applied to my scenario.
No cursors! Cursor are created by the devil to lead poor little db people away from the light of set-based thinking deep into the dark acres of procedural approaches...
Please (for future questions): Do not paste pictures! Had to type my example in...
And btw: Your use my values here makes it difficult, to advise the correct thing. Depending on what you are doing there, a cursor might be needed actually. But in this case you should create the cursor from a query like I show you...
Try it like this:
DECLARE #xml XML=
'<roots>
<root>
<ID>5</ID>
<LotResults>
<ID>13</ID>
<Result>
<ID>5</ID>
<Count>2</Count>
</Result>
</LotResults>
<LotResults>
<ID>13</ID>
<Result>
<ID>5</ID>
<Count>2</Count>
</Result>
</LotResults>
<StandardComment>
<ID>0</ID>
</StandardComment>
<ReviewComment>
<ID>0</ID>
</ReviewComment>
</root>
<root>
<ID>44</ID>
<LotResults>
<ID>444</ID>
<Result>
<ID>4444</ID>
<Count>2</Count>
</Result>
</LotResults>
<LotResults>
<ID>555</ID>
<Result>
<ID>55</ID>
<Count>2</Count>
</Result>
</LotResults>
<StandardComment>
<ID>5</ID>
</StandardComment>
<ReviewComment>
<ID>5</ID>
</ReviewComment>
</root>
</roots>';
--and here's the query
SELECT r.value('ID[1]','int') AS root_ID
,lr.value('ID[1]','int') AS LotResult_ID
,lr.value('(Result/ID)[1]','int') AS LotResult_Result_ID
,lr.value('(Result/Count)[1]','int') AS LotResult_Result_Count
,r.value('(StandardComment/ID)[1]','int') AS StandardComment_ID
,r.value('(ReviewComment/ID)[1]','int') AS ReviewComment_ID
FROM #xml.nodes('/roots/root') AS A(r)
CROSS APPLY r.nodes('LotResults') AS B(lr)

How to add condition to COALESCE while reading xml in Sql

I am trying to read the xml and storing it in SQL server.
DECLARE #xml XML
SET #xml =
'<report>
<personal>
<search>
<subject>
<name>SearchName</name>
</subject>
</search>
</personal>
<personal>
<search>
<subject>
<name>SearchName</name>
</subject>
</search>
<result>
<history>
<name>HistoryName</name>
</history>
</result>
</personal>
<personal>
<search>
<subject>
<name>SearchName</name>
</subject>
</search>
<result>
<history>
<dob>HistoryDOB</dob>
</history>
</result>
</personal>
</report>
'
What i am trying here is - selecting the name but condition here is
if <personal> contains <result> then select the name under history/name
if <personal> doesn't contain <result> select the name under subject/name
if <personal> contain <result>BUT name is not there then enter null
I am using below query
SELECT
COALESCE(
A.Search.value('(result/history/name)[1]','varchar(max)'),
A.Search.value('(search/subject/name)[1]','varchar(max)')
) AS Name
FROM #xml.nodes('/report/personal') as A(Search)
It is returning
SearchName
HistoryName
SearchName
But it is failing in 3rd condition.
Just tweak the second values call to specifically request what you've specified - that you'll only take a search/subject for nodes with no result:
SELECT
COALESCE(
A.Search.value('(result/history/name)[1]','varchar(max)'),
A.Search.value('(search[not(../result)]/subject/name)[1]','varchar(max)')
) AS Name
FROM #xml.nodes('/report/personal') as A(Search)
Result:
Name
------------------
HistoryName
SearchName
NULL
You can use exist method to chech if <personal> contains <result> or not.
Using it your algorithm can be straightforward translated to query as:
select
case
when A.Search.exist('result') = 1
then A.Search.value('(result/history/name)[1]','varchar(max)')
else A.Search.value('(search/subject/name)[1]','varchar(max)')
end as Name
FROM #xml.nodes('/report/personal') as A(Search)

Import 'xml' into Sql Server

I have a file that is structured like so:
<?xml version="1.0" encoding="UTF-8"?>
<EventSchedule>
<Event Uid="2" Type="Main Event">
<IsFixed>True</IsFixed>
<EventKind>MainEvent</EventKind>
<Fields>
<Parameter Name="Type" Value="TV_Show"/>
<Parameter Name="Name" Value="The Muppets"/>
<Parameter Name="Duration" Value="00:30:00"/>
</Fields>
</Event>
<Event>
...and so on
</Event>
</EventSchedule>
I'm not entirely sure if it is valid XML, however I need to import it into SQL Server but everything I try doesn't seem to work.
Please could anyone point me in the right direction either with some example code or a recommendation on which method to use?
I'd ideally like to get the raw data into a flat table, along the lines of:
Name | Type | Duration | EventKind
The Muppets | TV_Show | 00:30:00 | MainEvent
Finally this is coming from fairly large files and I will need to import the regularly.
Thanks, pugu
Try this:
DECLARE #XML XML = '<EventSchedule>
<Event Uid="2" Type="Main Event">
<IsFixed>True</IsFixed>
<EventKind>MainEvent</EventKind>
<Fields>
<Parameter Name="Type" Value="TV_Show"/>
<Parameter Name="Name" Value="The Muppets"/>
<Parameter Name="Duration" Value="00:30:00"/>
</Fields>
</Event>
<Event Uid="3" Type="Secondary Event">
<IsFixed>True</IsFixed>
<EventKind>SecondaryEvent</EventKind>
<Fields>
<Parameter Name="Type" Value="TV_Show"/>
<Parameter Name="Name" Value="The Muppets II"/>
<Parameter Name="Duration" Value="00:30:00"/>
</Fields>
</Event>
</EventSchedule>'
SELECT
EventUID = Events.value('#Uid', 'int'),
EventType = Events.value('#Type', 'varchar(20)'),
EventIsFixed =Events.value('(IsFixed)[1]', 'varchar(20)'),
EventKind =Events.value('(EventKind)[1]', 'varchar(20)')
FROM
#XML.nodes('/EventSchedule/Event') AS XTbl(Events)
Gives me an output of:
And of course, you can easily do an
INSERT INTO dbo.YourTable(EventUID, EventType, EventIsFixed, EventKind)
SELECT
......
to insert that data into a relational table.
Update: assuming you have your XML in files - you can use this code to load the XML file into an XML variable in SQL Server:
DECLARE #XmlFile XML
SELECT #XmlFile = BulkColumn
FROM OPENROWSET(BULK 'path-to-your-XML-file', SINGLE_BLOB) x;
and then use the above code snippet to parse the XML.
Update #2: if you need the parameters, too - use this XQuery statement:
SELECT
EventUID = Events.value('#Uid', 'int'),
EventType = Events.value('#Type', 'varchar(20)'),
EventIsFixed = Events.value('(IsFixed)[1]', 'varchar(20)'),
EventKind = Events.value('(EventKind)[1]', 'varchar(20)'),
ParameterType = Events.value('(Fields/Parameter[#Name="Type"]/#Value)[1]', 'varchar(20)'),
ParameterName = Events.value('(Fields/Parameter[#Name="Name"]/#Value)[1]', 'varchar(20)'),
ParameterDuration = Events.value('(Fields/Parameter[#Name="Duration"]/#Value)[1]', 'varchar(20)')
FROM
#XML.nodes('/EventSchedule/Event') AS XTbl(Events)
Results in:
You do it by creating a destination table, then a schema mapping file that maps the xml elements to table columns.
Yours might look a bit like this:
create table event (
Type nvarchar(50),
Name nvarchar(50),
Duration nvarchar(50))
and this:
<?xml version="1.0" ?>
<Schema xmlns="urn:schemas-microsoft-com:xml-data"
xmlns:dt="urn:schemas-microsoft-com:xml:datatypes"
xmlns:sql="urn:schemas-microsoft-com:xml-sql" >
<ElementType name="Type" dt:type="string" />
<ElementType name="Name" dt:type="string" />
<ElementType name="Duration" dt:type="string" />
<ElementType name="EventSchedule" sql:is-constant="1">
<element type="Event" />
</ElementType>
<ElementType name="Event" sql:relation="Event">
<element type="Type" sql:field="Type" />
<element type="Name" sql:field="Name" />
<element type="Duration" sql:field="Duration" />
</ElementType>
</Schema>
Then you can load your XML into your table using the XML bulk loader.
http://support.microsoft.com/kb/316005
If you need to do it without XML variable (from string in table-valued function)
SELECT
--myTempTable.XmlCol.value('.', 'varchar(36)') AS val
myTempTable.XmlCol.query('./ID').value('.', 'varchar(36)') AS ID
,myTempTable.XmlCol.query('./Name').value('.', 'nvarchar(MAX)') AS Name
,myTempTable.XmlCol.query('./RFC').value('.', 'nvarchar(MAX)') AS RFC
,myTempTable.XmlCol.query('./Text').value('.', 'nvarchar(MAX)') AS Text
,myTempTable.XmlCol.query('./Desc').value('.', 'nvarchar(MAX)') AS Description
--,myTempTable.XmlCol.value('(Desc)[1]', 'nvarchar(MAX)') AS DescMeth2
FROM
(
SELECT
CAST('<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<data-set>
<record>
<ID>1</ID>
<Name>A</Name>
<RFC>RFC 1035[1]</RFC>
<Text>Address record</Text>
<Desc>Returns a 32-bit IPv4 address, most commonly used to map hostnames to an IP address of the host, but it is also used for DNSBLs, storing subnet masks in RFC 1101, etc.</Desc>
</record>
<record>
<ID>2</ID>
<Name>NS</Name>
<RFC>RFC 1035[1]</RFC>
<Text>Name server record</Text>
<Desc>Delegates a DNS zone to use the given authoritative name servers</Desc>
</record>
</data-set>
' AS xml) AS RawXml
) AS b
--CROSS APPLY b.RawXml.nodes('//record/ID') myTempTable(XmlCol);
CROSS APPLY b.RawXml.nodes('//record') myTempTable(XmlCol);
Or from file:
IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[tfu_RPT_SEL_XmlData]') AND type in (N'FN', N'IF', N'TF', N'FS', N'FT'))
DROP FUNCTION [dbo].[tfu_RPT_SEL_XmlData]
GO
CREATE FUNCTION [dbo].[tfu_RPT_SEL_XmlData]
(
#in_language varchar(10)
,#in_reportingDate datetime
)
RETURNS TABLE
AS
RETURN
(
SELECT
--myTempTable.XmlCol.value('.', 'varchar(36)') AS val
myTempTable.XmlCol.query('./ID').value('.', 'varchar(36)') AS ID
,myTempTable.XmlCol.query('./Name').value('.', 'nvarchar(MAX)') AS Name
,myTempTable.XmlCol.query('./RFC').value('.', 'nvarchar(MAX)') AS RFC
,myTempTable.XmlCol.query('./Text').value('.', 'nvarchar(MAX)') AS Text
,myTempTable.XmlCol.query('./Desc').value('.', 'nvarchar(MAX)') AS Description
FROM
(
SELECT CONVERT(XML, BulkColumn) AS RawXml
FROM OPENROWSET(BULK 'D:\username\Desktop\MyData.xml', SINGLE_BLOB) AS MandatoryRowSetName
) AS b
CROSS APPLY b.RawXml.nodes('//record') myTempTable(XmlCol)
)
GO
SELECT * FROM tfu_RPT_SEL_XmlData('DE', CURRENT_TIMESTAMP);
e.g.
DECLARE #bla varchar(MAX)
SET #bla = 'BED40DFC-F468-46DD-8017-00EF2FA3E4A4,64B59FC5-3F4D-4B0E-9A48-01F3D4F220B0,A611A108-97CA-42F3-A2E1-057165339719,E72D95EA-578F-45FC-88E5-075F66FD726C'
-- http://stackoverflow.com/questions/14712864/how-to-query-values-from-xml-nodes
SELECT
x.XmlCol.value('.', 'varchar(36)') AS val
FROM
(
SELECT
CAST('<e>' + REPLACE(#bla, ',', '</e><e>') + '</e>' AS xml) AS RawXml
) AS b
CROSS APPLY b.RawXml.nodes('e') x(XmlCol);
So you can have a function like
SELECT * FROM MyTable
WHERE UID IN
(
SELECT
x.XmlCol.value('.', 'varchar(36)') AS val
FROM
(
SELECT
CAST('<e>' + REPLACE(#bla, ',', '</e><e>') + '</e>' AS xml) AS RawXml
) AS b
CROSS APPLY b.RawXml.nodes('e') x(XmlCol)
)
If you're trying to import your XML as a "pure" XML field you should create a table like this (obviously with many other fields as you want):
CREATE TABLE [dbo].[TableXML](
[ID] [int] IDENTITY(1,1) NOT NULL,
[XmlContent] [xml] NOT NULL -- specify [xml] type
)
Then you can easily insert your XML as a string:
INSERT INTO [dbo].[TableXML]
([XmlContent])
VALUES
('<?xml version="1.0" encoding="UTF-8"?>
<EventSchedule>
<Event Uid="2" Type="Main Event">
<IsFixed>True</IsFixed>
<EventKind>MainEvent</EventKind>
<Fields>
<Parameter Name="Type" Value="TV_Show"/>
<Parameter Name="Name" Value="The Muppets"/>
<Parameter Name="Duration" Value="00:30:00"/>
</Fields>
</Event>
</EventSchedule>')
Then to query start from MSDN t-SQL XML
If you prefer store it as string use a varchar(max) in place of [XML] column type and the same insert. But if you like to query easily I suggest [XML] type. With the flat string approach you need a lot of work unless you will implement some application code to parse it and store in a flat table.
A good approach could be an XML storage in a "compress" TABLE and a VIEW for data retrieve with the flat field disposition.
How to load the below XML data into the SQL
<?xml version="1.0" encoding="utf-8"?>
<DataTable xmlns="SmarttraceWS">
<xs:schema id="NewDataSet" xmlns="" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xs:element name="NewDataSet" msdata:IsDataSet="true" msdata:MainDataTable="ActivityRecords" msdata:UseCurrentLocale="true">
<xs:complexType>
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:element name="ActivityRecords">
<xs:complexType>
<xs:sequence>
<xs:element name="ReferenceID" type="xs:long" minOccurs="0" />
<xs:element name="IMEI" type="xs:string" minOccurs="0" />
<xs:element name="Asset" type="xs:string" minOccurs="0" />
<xs:element name="Driver" type="xs:string" minOccurs="0" />
<xs:element name="DateTime" type="xs:string" minOccurs="0" />
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:choice>
</xs:complexType>
</xs:element>
</xs:schema>
<diffgr:diffgram xmlns:msdata="urn:schemas-microsoft-com:xml-msdata" xmlns:diffgr="urn:schemas-microsoft-com:xml-diffgram-v1">
<DocumentElement xmlns="">
<ActivityRecords diffgr:id="ActivityRecords1" msdata:rowOrder="0">
<ReferenceID>2620443016</ReferenceID>
<IMEI>013795001360346</IMEI>
<Asset>L-93745</Asset>
<Driver>N/A</Driver>
<DateTime>2019-10-14 12:00:35</DateTime>
</ActivityRecords>
</DocumentElement>
</diffgr:diffgram>
</DataTable>

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.

XML question in 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;

Resources