SQL Server XML file with multiple nodes named the same - sql-server

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)

Related

Insert XML child node to SQL table

I've got an XML file like this and I'm working with SQL 2014 SP2
<?xml version='1.0' encoding='UTF-8'?>
<gwl>
<version>123456789</version>
<entities>
<entity id="1" version="123456789">
<name>xxxxx</name>
<listId>0</listId>
<listCode>Oxxx</listCode>
<entityType>08</entityType>
<createdDate>03/03/1993</createdDate>
<lastUpdateDate>05/06/2011</lastUpdateDate>
<source>src</source>
<OriginalSource>o_src</OriginalSource>
<aliases>
<alias category="STRONG" type="Alias">USCJSC</alias>
<alias category="WEAK" type="Alias">'OSKOAO'</alias>
</aliases>
<programs>
<program type="21">prog</program>
</programs>
<sdfs>
<sdf name="OriginalID">9876</sdf>
</sdfs>
<addresses>
<address>
<address1>1141, SYA-KAYA STR.</address1>
<country>RU</country>
<postalCode>1234</postalCode>
</address>
<address>
<address1>90, MARATA UL.</address1>
<country>RU</country>
<postalCode>1919</postalCode>
</address>
</addresses>
<otherIds>
<childId>737606</childId>
<childId>737607</childId>
</otherIds>
</entity>
</entities>
</gwl>
I made a script to insert data from the XML to a SQL table. How can I insert child node into a table? I think I should replicate the row for each new child node but i don't know the best way to proceed.
Here is my SQL code
DECLARE #InputXML XML
SELECT #InputXML = CAST(x AS XML)
FROM OPENROWSET(BULK 'C:\MyFiles\sample.XML', SINGLE_BLOB) AS T(x)
SELECT
product.value('(#id)[1]', 'NVARCHAR(10)') id,
product.value('(#version)[1]', 'NVARCHAR(14)') ID
product.value('(name[1])', 'NVARCHAR(255)') name,
product.value('(listId[1])', 'NVARCHAR(9)')listId,
product.value('(listCode[1])', 'NVARCHAR(10)')listCode,
product.value('(entityType[1])', 'NVARCHAR(2)')entityType,
product.value('(createdDate[1])', 'NVARCHAR(10)')createdDate,
product.value('(lastUpdateDate[1])', 'NVARCHAR(10)')lastUpdateDate,
product.value('(source[1])', 'NVARCHAR(15)')source,
product.value('(OriginalSource[1])', 'NVARCHAR(50)')OriginalSource,
product.value('(aliases[1])', 'NVARCHAR(50)')aliases,
product.value('(programs[1])', 'NVARCHAR(50)')programs,
product.value('(sdfs[1])', 'NVARCHAR(500)')sdfs,
product.value('(addresses[1])', 'NVARCHAR(50)')addresses,
product.value('(otherIDs[1])', 'NVARCHAR(50)')otherIDs
FROM #InputXML.nodes('gwl/entities/entity') AS X(product)
You have a lot of different children here...
Just to show the principles:
DECLARE #xml XML=
N'<gwl>
<version>123456789</version>
<entities>
<entity id="1" version="123456789">
<name>xxxxx</name>
<listId>0</listId>
<listCode>Oxxx</listCode>
<entityType>08</entityType>
<createdDate>03/03/1993</createdDate>
<lastUpdateDate>05/06/2011</lastUpdateDate>
<source>src</source>
<OriginalSource>o_src</OriginalSource>
<aliases>
<alias category="STRONG" type="Alias">USCJSC</alias>
<alias category="WEAK" type="Alias">''OSKOAO''</alias>
</aliases>
<programs>
<program type="21">prog</program>
</programs>
<sdfs>
<sdf name="OriginalID">9876</sdf>
</sdfs>
<addresses>
<address>
<address1>1141, SYA-KAYA STR.</address1>
<country>RU</country>
<postalCode>1234</postalCode>
</address>
<address>
<address1>90, MARATA UL.</address1>
<country>RU</country>
<postalCode>1919</postalCode>
</address>
</addresses>
<otherIds>
<childId>737606</childId>
<childId>737607</childId>
</otherIds>
</entity>
</entities>
</gwl>';
-The query will fetch some values from several places.
--It should be easy to get the rest yourself...
SELECT #xml.value('(/gwl/version/text())[1]','bigint') AS [version]
,A.ent.value('(name/text())[1]','nvarchar(max)') AS [Entity_Name]
,A.ent.value('(listId/text())[1]','int') AS Entity_ListId
--more columns taken from A.ent
,B.als.value('#category','nvarchar(max)') AS Alias_Category
,B.als.value('text()[1]','nvarchar(max)') AS Alias_Content
--similar for programs and sdfs
,E.addr.value('(address1/text())[1]','nvarchar(max)') AS Address_Address1
,E.addr.value('(country/text())[1]','nvarchar(max)') AS Address_Country
--and so on
FROM #xml.nodes('/gwl/entities/entity') A(ent)
OUTER APPLY A.ent.nodes('aliases/alias') B(als)
OUTER APPLY A.ent.nodes('programs/program') C(prg)
OUTER APPLY A.ent.nodes('sdfs/sdf') D(sdfs)
OUTER APPLY A.ent.nodes('addresses/address') E(addr)
OUTER APPLY A.ent.nodes('otherIds/childId') F(ids);
The idea in short:
We read non-repeating values (e.g. version) from the xml variable directly
We use .nodes() to return repeating elements as derived sets.
We can use a cascade of .nodes() to dive deeper into repeating child elements by using a relativ Xpath (no / at the beginning).
You have two approaches:
Read the XML like above into a staging table (simply by adding INTO #tmpTable before FROM) and proceed from there (will need one SELECT ... GROUP BY for each type of child).
Create one SELECT per type of child, using only one of the APPLY lines and shift the data into specific child tables.
I would tend to the first one.
This allows to do some cleaning, generate IDs, check for business rules, before you shift this into the target tables.

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.

Microsoft SQL Server xml data

This site has a technique to pass xml data around in Microsoft SQL Server:
DECLARE #productIds xml
SET #productIds ='<Products><id>3</id><id>6</id><id>15</id></Products>'
SELECT
ParamValues.ID.value('.','VARCHAR(20)')
FROM #productIds.nodes('/Products/id') as ParamValues(ID)
But what is the syntax if I add another field?
The following does NOT work:
DECLARE #productIds xml
SET #productIds ='<Products><id>3</id><descr>Three</descr><id>6</id><descr>six</descr><id>15</id><descr>Fifteen</descr></Products>'
SELECT
ParamValues.ID.value('.','VARCHAR(20)')
,ParamValues.descr.value('.','VARCHAR(20)')
FROM #productIds.nodes('/Products/id') as ParamValues(ID)
Note: Maybe I've constructed my xml wrong.
You need to use something like:
SELECT
ParamValues.ID.value('(id)[1]','VARCHAR(20)'),
ParamValues.ID.value('(descr)[1]','VARCHAR(20)')
FROM
#productIds.nodes('/Products') as ParamValues(ID)
That FROM statement there defines something like a "virtual table" called ParamValues.ID - you need to select the <Products> node into that virtual table and then access the properties inside it.
Furthermore, your XML structure is very badly chosen:
<Products>
<id>3</id>
<descr>Three</descr>
<id>6</id>
<descr>six</descr>
<id>15</id>
<descr>Fifteen</descr>
</Products>
You won't be able to select the individual pairs of id/descr - you should use something more like:
<Products>
<Product>
<id>3</id>
<descr>Three</descr>
</Product>
<Product>
<id>6</id>
<descr>six</descr>
</Product>
<Product>
<id>15</id>
<descr>Fifteen</descr>
</Product>
</Products>
Then you could retrieve all items using this SQL XML query:
SELECT
ParamValues.ID.value('(id)[1]','VARCHAR(20)') AS 'ID',
ParamValues.ID.value('(descr)[1]','VARCHAR(20)') AS 'Description'
FROM
#productIds.nodes('/Products/Product') as ParamValues(ID)
ID Descrition
3 Three
6 six
15 Fifteen
You must wrap each set of id and descr into one parent node. Say Row. Now you can access each pair like this.
DECLARE #productIds xml
SET #productIds ='<Products><Row><id>3</id><descr>Three</descr></Row><Row><id>6</id><descr>six</descr></Row><Row><id>15</id><descr>Fifteen</descr></Row></Products>'
SELECT
ParamValues.Row.query('id').value('.','VARCHAR(20)'),
ParamValues.Row.query('descr').value('.','VARCHAR(20)')
FROM #productIds.nodes('/Products/Row') as ParamValues(Row)

Resources