How to add condition to COALESCE while reading xml in Sql - sql-server

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)

Related

t-sql to create XML file using FOR XML Path, multilevel issues

I am creating an XML file to Upload to a 3rd party product. The file must begin with specific file and source information level and then it is followed with the specific data requirements/levels of EVENT and CREW members for those events.
I can create the initial level with the file/source information, and I have the data requirements exactly as they should be, but I cannot get them together in the same file between the "ROOT" level without the initial level repeating between each EVENT level or the an extra EVENT level as if they're nested. I've also managed to get a result with a ROW level that I did not define and the "tags" modified to < and &gt: instead of < >. I've done a good bit of research and tried using a union method, sub-selects, nesting methods as well many combinations of FOR XML PATH, AUTO, EXPLICIT, with and without elements. I've learned a lot, but I'm just not finding the right combination for the results I need.
The first example is the layout that is required. The second is one of the examples that is most common for my efforts, followed by the SQL that created it.
what it should be (FILEINFO level only once, only one EVENT level for each EVENT)
<ROOT>
<FILEINFO>
<SOURCE_ID>P</SOURCE_ID>
</FILEINFO>
<EVENT>
<DATE>2019-09-24T08:00:00</DATE>
<NO>1</NO>
<DEL_FLAG>false</DEL_FLAG>
<DATE_TIME_STAMP>2019-09-24T14:14:00</DATE_TIME_STAMP>
<CREW>
<LAST_NAME>DOE</LAST_NAME>
<DEL_FLAG>false</DEL_FLAG>
<DATE_TIME_STAMP>2019-09-24T14:14:00</DATE_TIME_STAMP>
</CREW>
</EVENT>
<EVENT>
<DATE>2019-09-16T12:30:00</DATE>
<NO>1</NO>
<DEL_FLAG>false</DEL_FLAG>
<DATE_TIME_STAMP>2019-09-16T18:20:00</DATE_TIME_STAMP>
<CREW>
<LAST_NAME>DOE</LAST_NAME>
<DEL_FLAG>false</DEL_FLAG>
<DATE_TIME_STAMP>2019-09-16T18:20:00</DATE_TIME_STAMP>
</CREW>
</EVENT>
</ROOT>
what i'm getting:
<ROOT>
<EVENT>
<FILEINFO>
<SOURCE_ID>P</SOURCE_ID>
</FILEINFO>
<EVENT>
<DATE>2019-09-16T08:00:00</DATE>
<NO>1</NO>
<DEL_FLAG>false</DEL_FLAG>
<DATE_TIME_STAMP>2019-09-16T15:12:00</DATE_TIME_STAMP>
<CREW>
<LAST_NAME>DOE</LAST_NAME>
<DEL_FLAG>false</DEL_FLAG>
<DATE_TIME_STAMP>2019-09-16T15:12:00</DATE_TIME_STAMP>
</CREW>
</EVENT>
</EVENT>
<EVENT>
<FILEINFO>
<SOURCE_ID>P</SOURCE_ID>
</FILEINFO>
<EVENT>
<DATE>2019-09-16T08:00:00</DATE>
<NO>1</NO>
<DEL_FLAG>false</DEL_FLAG>
<DATE_TIME_STAMP>2019-09-16T15:12:00</DATE_TIME_STAMP>
<CREW>
<LAST_NAME>DOE</LAST_NAME>
<DEL_FLAG>false</DEL_FLAG>
<DATE_TIME_STAMP>2019-09-16T15:12:00</DATE_TIME_STAMP>
</CREW>
</EVENT>
</EVENT>
... ...
most recent/simplest attempt that creates the above:
SELECT
(SELECT SOURCE_ID FROM (select 'P' as SOURCE_ID) FILEINFO ) AS 'FILEINFO/SOURCE_ID'
,[DATE] AS 'EVENT/DATE'
,[NO] AS 'EVENT/NO'
,[DEL_FLAG] AS 'EVENT/DEL_FLAG'
,[DATE_TIME_STAMP] AS 'EVENT/DATE_TIME_STAMP'
,'DOE' as 'EVENT/CREW/LAST_NAME'
,[DEL_FLAG2] as 'EVENT/CREW/DEL_FLAG'
,[DATE_TIME_STAMP3] as 'EVENT/CREW/DATE_TIME_STAMP'
FROM [dbo].XMLForFILEExport x
FOR XML path('EVENT'), elements, ROOT('ROOT') ;
This is easy, just use a sub-select and deal with this like it was a *normal column:
This easy SELECT will return the single <FILEINFO>
SELECT 'P' AS [FILEINFO/SOURCE_ID]
FOR XML PATH(''),ROOT('ROOT');
You see, that I used an empty PATH(), but I set the ROOT().
This is the result
<ROOT>
<FILEINFO>
<SOURCE_ID>P</SOURCE_ID>
</FILEINFO>
</ROOT>
Now we can start to add your events. First I need a mockup table to simulate your issue
DECLARE #mockupEventTable TABLE(ID INT IDENTITY,[NO] INT, [DATE] DATETIME, EventText VARCHAR(100));
INSERT INTO #mockupEventTable VALUES(1,'20190916','Event 1')
,(2,'20190917','Event 2');
--The query
SELECT 'P' AS [FILEINFO/SOURCE_ID]
,(
SELECT e.[DATE]
,e.[NO]
,e.EventText
,'Doe' AS [CREW/LASTNAME]
FROM #mockupEventTable e
FOR XML PATH('EVENT'),TYPE
) AS [*]
FOR XML PATH(''),ROOT('ROOT');
The result
<ROOT>
<FILEINFO>
<SOURCE_ID>P</SOURCE_ID>
</FILEINFO>
<EVENT>
<DATE>2019-09-16T00:00:00</DATE>
<NO>1</NO>
<EventText>Event 1</EventText>
<CREW>
<LASTNAME>Doe</LASTNAME>
</CREW>
</EVENT>
<EVENT>
<DATE>2019-09-17T00:00:00</DATE>
<NO>2</NO>
<EventText>Event 2</EventText>
<CREW>
<LASTNAME>Doe</LASTNAME>
</CREW>
</EVENT>
</ROOT>
You can see, that the sub-select will create the inner XML just as you need it. We have to specify ,TYPE in order to get this as typed XML. Try the same without. You will get the XML escaped, as if it was simple text...
And I specify AS [*] (the same was AS [node()]) to indicate, that the XML "column" has no own name, but should be inserted as is. This is not mandatory (try it without), but it makes things more readable...
That's because you specified the PATH "EVENT" already. Also you can remove the EVENT in the field name, e.g. 'EVENT/CREW/DATE_TIME_STAMP' can just be 'CREW/DATE_TIME_STAMP'
TO achieve what required, you can generate the xml with EVENT elementsand then insert the FILEINFO.
DECLARE #x xml;
SET #x=(SELECT
[DATE] AS 'DATE'
,[NO] AS 'NO'
,[DEL_FLAG] AS 'DEL_FLAG'
,[DATE_TIME_STAMP] AS 'DATE_TIME_STAMP'
,'DOE' as 'CREW/LAST_NAME'
,[DEL_FLAG2] as 'CREW/DEL_FLAG'
,[DATE_TIME_STAMP3] as 'CREW/DATE_TIME_STAMP'
FROM [dbo].XMLForFILEExport x
FOR XML path('EVENT'), elements, ROOT('ROOT'))
SET #x.modify('
insert <FILEINFO><SOURCE_ID>P</SOURCE_ID></FILEINFO>
as first
into (/ROOT)[1]');

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

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)

If condition on Selecting XML node 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>
</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
currently i am selecting names from personal/subject as below:
Select
A.Search.value('(subject/name)[1]','varchar(max)')
FROM #xml.nodes('/report/personal/search') as A(Search)
Expecting result:
SearchName
HistoryName
How to add condition in between?
Is there any way we can add exists condition here
SELECT #xml.exist('//report//personal//search//subject//name')
Select coalesce(A.Search.value('(result/history/name)[1]', 'varchar(max)'), A.Search.value('(search/subject/name)[1]','varchar(max)'))
FROM #xml.nodes('/report/personal') as A(Search)
This:
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)
will return:
Name
------------
SearchName
HistoryName

Resources