The target of 'replace' must be at most one node - sql

I'm trying to modify an XML value but keep getting the message
The target of the replace must be at most one node, found attribute(prefType, xdt:untypedAtomic)
First here is my XML
<preferences>
<categories>
<category id="1" prefType="2">
<subcat id="1" prefType="2" />
<subcat id="2" prefType="2" />
<subcat id="3" prefType="2" />
<subcat id="77" prefType="2" />
</category>
<category id="2" prefType="2">
<subcat id="9" prefType="2" />
<subcat id="10" prefType="2" />
<subcat id="11" prefType="2" />
<subcat id="12" prefType="2" />
<subcat id="13" prefType="2" />
<subcat id="14" prefType="2" />
<subcat id="17" prefType="2" />
<subcat id="78" prefType="2" />
<subcat id="101" prefType="2" />
</category>
<category id="3" prefType="2">
<subcat id="18" prefType="2" />
<subcat id="19" prefType="2" />
<subcat id="20" prefType="2" />
</category>
</categories>
</preferences>
And my code
declare #XMLinput as XML;
declare #custXML as XML;
declare #subcatid as nvarchar(3);
declare #interest as nvarchar(8);
declare #newValue as varchar(1);
declare #cnt as int;
set #XMLinput = '<preferences><categoryId>73</categoryId><interestLevel>POSITIVE</interestLevel></preferences>';
-- get the subcatid and interest level
SET #subcatid = #XMLinput.value('(//preferences/categoryId)[1]','nvarchar(3)');
SET #interest = #XMLinput.value('(//preferences/interestLevel)[1]','nvarchar(20)');
SET #newValue =
CASE #interest
WHEN 'POSITIVE' THEN '1'
WHEN 'NEGATIVE' THEN '3'
ELSE '2'
END;
set #custXML = (select Preferences from Customer_Preferences where custID=11584);
select #custXML.exist('//preferences/categories/category/subcat[#id=sql:variable("#subcatid")]');
if (##ROWCOUNT > 0)
BEGIN TRY
BEGIN TRAN;
set #cnt = CAST(CAST(#custXML.query('count(//preferences/categories/category/subcat[#id=(sql:variable("#subcatid"))])') AS VARCHAR) AS INT);
-- replace the value
UPDATE Customer_Preferences
SET preferences.modify('
replace value of
(//*/subcat[#id=sql:variable("#subcatid")]/#prefType[1])
with sql:variable("#newValue")
')
where CustID = 11584;
COMMIT TRAN;
END TRY
BEGIN CATCH
select XACT_STATE() as 'XACT_STATE', ##TRANCOUNT as '##TRANCOUNT';
if ##TRANCOUNT > 0 ROLLBACK TRANSACTION;
END CATCH
select preferences from Customer_Preferences where custid=11584
SELECT XACT_STATE() as 'XACT_STATE', ##TRANCOUNT AS '##TRANCOUNT'
I've tried removing the sql variables and replacing them with fixed values but still get the same issue. I've also tried removing all the XML subcats except for one and the same error occurs.
After 3 hours of working through this and getting no where, I'd really appreciate your help.

Just as further reference (although you solved your problem already on your own) for others with similar problems: Here is what actually went wrong: The error message already indicates that the replace target can be at most one value (meaning you can replace only one value at a time).
However, (//*/subcat[#id=sql:variable("#subcatid")]/#prefType[1]) yields a sequence of results What is means literally is to take each subcat element and select the first attribute with the name prefType. This actually does not make much sense as an XML element can not have multiple attributes with the same name, so the query would be the same without the [1] predicate.
What you probably wanted to write is: Give me each prefType attribute of each subcat element and return only the first one of the whole result set. That is exactly what your working query is doing: (//*/subcat[#id=sql:variable("#subcatid")]/#prefType)[1]

Related

Extracting data from XML Array using SQL

I have the following XML and would like to extract the PrimaryTeams, SecondaryTeams and OverflowTeams arrays from this and either have them comma separated or one per row.
I have the following xml:
declare #xml xml
set #xml = '<SimpleStrategy xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.datacontract.org/2004/07/Synthesys.Switch.ACD">
<Id>00000000-0000-0000-0000-000000000000</Id>
<Name>Default</Name>
<AcceptedCLIs xmlns:d2p1="http://schemas.microsoft.com/2003/10/Serialization/Arrays">
<d2p1:string>07811353995</d2p1:string>
</AcceptedCLIs>
<ActiveHours>
<FridayEnd />
<FridayStart />
<MondayEnd />
<MondayStart />
<SaturdayEnd />
<SaturdayStart />
<SundayEnd />
<SundayStart />
<ThursdayEnd />
<ThursdayStart />
<TuesdayEnd />
<TuesdayStart />
<UseIndividualWeekDays>false</UseIndividualWeekDays>
<WednesdayEnd />
<WednesdayStart />
<WeekdayEnd />
<WeekdayStart />
</ActiveHours>
<AgentUserName />
<AllowRouteDuringFinalMessage>false</AllowRouteDuringFinalMessage>
<CRMPrefix />
<DirectDDIMessage />
<DirectDDIPassThrough>false</DirectDDIPassThrough>
<EmergencyBusyBack>false</EmergencyBusyBack>
<EmergencyDivertNumber />
<EmergencyWavFile />
<FinallyDivertNumber />
<FinallyDrop>true</FinallyDrop>
<FinallyMessageFile />
<MaximumQueueLength>0</MaximumQueueLength>
<MaximumQueueWait>0</MaximumQueueWait>
<MinimumRingTime>4000</MinimumRingTime>
<MusicOnHold />
<MusicWhileWaiting />
<NumberOfRings>2</NumberOfRings>
<OutOfHoursDivertNumber />
<OutOfHoursDrop>true</OutOfHoursDrop>
<OutOfHoursMessage />
<OverflowMessage />
<OverflowTeams xmlns:d2p1="http://schemas.microsoft.com/2003/10/Serialization/Arrays" />
<PrimaryTeams xmlns:d2p1="http://schemas.microsoft.com/2003/10/Serialization/Arrays">
<d2p1:int>3</d2p1:int>
<d2p1:int>1</d2p1:int>
</PrimaryTeams>
<Priority>1</Priority>
<RecordAgent>false</RecordAgent>
<RecordCall>true</RecordCall>
<RecordCustomer>false</RecordCustomer>
<RegulatoryMessage>Default.wav</RegulatoryMessage>
<SecondaryOverflowMessage />
<SecondaryTeams xmlns:d2p1="http://schemas.microsoft.com/2003/10/Serialization/Arrays" />
<SendBusyIfQueueTooLong>false</SendBusyIfQueueTooLong>
<SendBusyIfWaitTooLong>false</SendBusyIfWaitTooLong>
<TimeInOverflow>-1</TimeInOverflow>
<TimeWithDirectDDI>20000</TimeWithDirectDDI>
<TimeWithPrimaryTeams>-1</TimeWithPrimaryTeams>
<TimeWithSecondaryTeams>20000</TimeWithSecondaryTeams>
<UseDirectDDI>false</UseDirectDDI>
<UsePAM>false</UsePAM>
<UseSecondaryTeams>false</UseSecondaryTeams>
<WrapTime>40000</WrapTime>
</SimpleStrategy>'
I then created the following SQL Statement to try and extract the Teams
;WITH XMLNAMESPACES ('http://www.w3.org/2001/XMLSchema-instance' as i, 'http://schemas.microsoft.com/2003/10/Serialization/Arrays' as d2p1,
DEFAULT 'http://schemas.datacontract.org/2004/07/Synthesys.Switch.ACD')
SELECT #xml,
#xml.value('(/SimpleStrategy/Name)[1]', 'varchar(255)'),
#xml.value('(/SimpleStrategy/PrimaryTeams)[1]', 'int') as PrimaryTeams,
#xml.value('(/SimpleStrategy/SecondaryTeams)[1]', 'int') as SecondaryTeams,
#xml.value('(/SimpleStrategy/OverflowTeams)[1]', 'int') as OverflowTeams
But all I get is the TeamID's concatenated together.
,PrimaryTeams,SecondaryTeams,OverflowTeams
Default,31,0,0
Any ideas?
Thanks
Matt
Your XML shows two team IDs in <PrimaryTeams>, while both other team nodes are empty... You did not tell us anything about the expected counts in there. However, the following approach will return a kind of entity-value-pairs with all IDs for all Teams. Hope this is what you need:
;WITH XMLNAMESPACES ('http://www.w3.org/2001/XMLSchema-instance' as i, 'http://schemas.microsoft.com/2003/10/Serialization/Arrays' as d2p1,
DEFAULT 'http://schemas.datacontract.org/2004/07/Synthesys.Switch.ACD')
SELECT 'Name' AS Caption
,1 AS RowInx
,#xml.value('(/SimpleStrategy/Name)[1]', 'varchar(255)') AS Content
UNION ALL
SELECT 'Primary Team'
,ROW_NUMBER() OVER(ORDER BY (SELECT NULL))
,t.value('.','varchar(255)')
FROM #xml.nodes('/SimpleStrategy/PrimaryTeams/d2p1:int') A(t)
UNION ALL
SELECT 'Secondary Team'
,ROW_NUMBER() OVER(ORDER BY (SELECT NULL))
,t.value('.','varchar(255)')
FROM #xml.nodes('/SimpleStrategy/SecondaryTeams/d2p1:int') A(t)
UNION ALL
SELECT 'Overflow-Team'
,ROW_NUMBER() OVER(ORDER BY (SELECT NULL))
,t.value('.','varchar(255)')
FROM #xml.nodes('/SimpleStrategy/OverflowTeams/d2p1:int') A(t);

FORXML SQL Group By Element

I am trying to group some elements together under one node. This is my current SQL;
declare #xml xml
set #xml = (
select (
select
'DERIVED' '#type',
m.NuixDerivedFieldName '#name', (
SELECT
NuixFieldType as 'metadata/#type',
NuixFieldName as 'metadata/#name'
from eddsdbo.MetadataMapping m1
where m1.NuixDerivedFieldName = m.NuixDerivedFieldName
for xml path ('first-non-blank'), type
)
from (select distinct NuixDerivedFieldName from eddsdbo.MetadataMapping) m
for xml path ('metadata'))
)
;WITH XMLNAMESPACES(DEFAULT 'http://nuix.com/fbi/metadata-profile')
select #xml for XML PATH ('metadata-list'), ROOT ('metadata-profile')
Which gives me the following output;
<metadata-profile xmlns="http://nuix.com/fbi/metadata-profile">
<metadata-list>
<metadata type="DERIVED" name="Barcode" xmlns="">
<first-non-blank>
<metadata type="CUSTOM" name="Barcode" />
</first-non-blank>
<first-non-blank>
<metadata type="EVIDENCE" name="Barcode" />
</first-non-blank>
</metadata>
I want to group together elements together which have the same 'name' attribute of the metadata element under the <first-non-blank> element.
The desired output should be;
<metadata-profile xmlns="http://nuix.com/fbi/metadata-profile">
<metadata-list>
<metadata type="DERIVED" name="Barcode" xmlns="">
<first-non-blank>
<metadata type="CUSTOM" name="Barcode" />
<metadata type="EVIDENCE" name="Barcode" />
</first-non-blank>
</metadata>
...
My database looks something like this;
NuixFieldName NuixFieldType NuixDerivedFieldName
------------------------------ ------------------------------ ------------------------------
_EmailEntryID PROPERTY EmailEntryID
Audited Audited Audited
Author PROPERTY Author
Barcode CUSTOM Barcode
Barcode EVIDENCE Barcode
I would also like to remove the xlmns namespace identifier from the metadata elements.
Thanks in advance!
You could try this
DECLARE #SampleData AS TABLE
(
NuixFieldName varchar(20),
NuixFieldType varchar(20),
NuixDerivedFieldName varchar(20)
)
INSERT INTO #SampleData
VALUES
('_EmailEntryID','PROPERTY','EmailEntryID'),
('Audited', 'Audited ','Audited'),
('Author ', 'PROPERTY','Author '),
('Barcode', 'CUSTOM ','Barcode'),
('Barcode', 'EVIDENCE','Barcode')
DECLARE #xml XML
SET #xml = (
SELECT
-- sd.NuixDerivedFieldName AS [#name],
'DERIVED' AS [#type],
sd.NuixDerivedFieldName AS [#name],
(
SELECT
sd2.NuixFieldType as '#type',
sd2.NuixFieldName as '#name'
FROM #SampleData sd2 WHERE sd2.NuixDerivedFieldName = sd.NuixDerivedFieldName
FOR XML PATH ('metadata'),ROOT('first-non-blank'), TYPE
)
FROM (select DISTINCT sd.NuixDerivedFieldName from #SampleData sd ) sd
FOR XML PATH('metadata'), ROOT('metadata-list'),TYPE
)
;WITH XMLNAMESPACES(DEFAULT 'http://nuix.com/fbi/metadata-profile')
SELECT #xml FOR XML PATH (''),ROOT('metadata-profile')
return:
<metadata-profile xmlns="http://nuix.com/fbi/metadata-profile">
<metadata-list>
<metadata type="DERIVED" name="Audited">
<first-non-blank>
<metadata type="Audited " name="Audited" />
</first-non-blank>
</metadata>
<metadata type="DERIVED" name="Author ">
<first-non-blank>
<metadata type="PROPERTY" name="Author " />
</first-non-blank>
</metadata>
<metadata type="DERIVED" name="Barcode">
<first-non-blank>
<metadata type="CUSTOM " name="Barcode" />
<metadata type="EVIDENCE" name="Barcode" />
</first-non-blank>
</metadata>
<metadata type="DERIVED" name="EmailEntryID">
<first-non-blank>
<metadata type="PROPERTY" name="_EmailEntryID" />
</first-non-blank>
</metadata>
</metadata-list>
</metadata-profile>

update xml attribute in xml returns error SQL

Im trying to update an attribute of an xml in SQL.
My XML is stored on a variable #tmpRespXML:
<Responses>
<x id="3" name="Good" val="0" seq="0" createsr="0" />
<x id="4" name="Fair" val="0" seq="0" createsr="0" />
<x id="5" name="Needs Repair" val="1" seq="0" createsr="0" />
<x id="6" name="Not Inspected" val="1" seq="0" createsr="0" />
<x id="7" name="N/A" val="1" seq="0" createsr="0" />
</Responses>
So what I did is to put the xml in a temp table.
DECLARE #tmpRespTBL TABLE(Responses XML)
INSERT #tmpRespTBL VALUES(#tmpRespXML)
and then update the table. I'm trying to set the attribute #createsr to 1 where my attribute #id is equal to #items
UPDATE #tmpRespTBL
SET Responses.modify('replace value of(/Responses/x[#id=("'+#items+'")]/#createsr)[1] with "1"')
This returns the ff error:
Msg 8172, Level 16, State 1, Line 30 The argument 1 of the xml data
type method "modify" must be a string literal.
What am I missing here?
Try with
SET Responses.modify('replace value of(/Responses/x[#id=("''+#items+''")]/#createsr)[1] with "1"')
That should fix your issue. What I did here is escape the '

tsql code to sync two xml documents

xml 1
<Team>
<Players>
<Player id="1" timestamp="11/03/2012 08:10:12">30</Player>
<Player id="2" timestamp="11/02/2012 09:11:12">40</Player>
<Players>
</Team>
xml 2
<Team>
<Players>
<Player id="1" timestamp="11/02/2012 09:10:12">10</Player>
<Player id="2" timestamp="11/03/2012 10:11:12">20</Player>
<Player id="3" timestamp="11/03/2012 13:00:00">50</Player>
<Players>
</Team>
OUTPUT when we merge the above two XMLs based on timestamp element:
<Team>
<Players>
<Player id="1" timestamp="11/02/2012 09:10:12">30</Player>
<Player id="2" timestamp="11/03/2012 10:11:12">20</Player>
<Player id="3" timestamp="11/03/2012 13:00:00">50</Player>
<Players>
</Team>
Could anyone please help me with T-SQL code to do this in SQL Server 2005/2008
Using this approach with two nested CTE's, you can get close - but not all the way:
DECLARE #XML1 XML = '<Team>
<Players>
<Player id="1" timestamp="11/03/2012 08:10:12">30</Player>
<Player id="2" timestamp="11/02/2012 09:11:12">40</Player>
</Players>
</Team>'
DECLARE #XML2 XML = '<Team>
<Players>
<Player id="1" timestamp="11/02/2012 09:10:12">10</Player>
<Player id="2" timestamp="11/03/2012 10:11:12">20</Player>
<Player id="3" timestamp="11/03/2012 13:00:00">50</Player>
</Players>
</Team>'
-- extract the ID, Timestamp and node values from both XML variables
;WITH CTE AS
(
SELECT
ID = P1.value('#id', 'int'),
TS = P1.value('#timestamp', 'datetime2'),
NodeValue = P1.value('(.)[1]', 'int')
FROM #XML1.nodes('/Team/Players/Player') AS XTbl1(P1)
UNION
SELECT
ID = P2.value('#id', 'int'),
TS = P2.value('#timestamp', 'datetime2'),
NodeValue = P2.value('(.)[1]', 'int')
FROM #XML2.nodes('/Team/Players/Player') AS XTbl2(P2)
),
-- partition and sequentially number the result, so that the newest
-- (most recent) item can be extracted
CTE2 AS
(
SELECT ID, TS, NodeValue,
RowNum = ROW_NUMBER() OVER(PARTITION BY ID ORDER BY TS DESC)
FROM CTE
)
SELECT
ID AS '#id',
TS AS '#timestamp',
NodeValue AS 'text()'
FROM CTE2
WHERE RowNum = 1
FOR XML PATH('Player'), ROOT('Players')
Results in output like this:
<Players>
<Player id="1" timestamp="2012-11-03T08:10:12">30</Player>
<Player id="2" timestamp="2012-11-03T10:11:12">20</Player>
<Player id="3" timestamp="2012-11-03T13:00:00">50</Player>
</Players>

SQL Server XML add attribute if non-existent

I am trying to add an attribute if it does not exist. It should be simple, but I am new to XML XPath/XQuery/etc., so excuse my ignorance.
I want to be able to pass XML data and modify it...
ALTER FUNCTION [dbo].[ConvertXmlData](#xmlData XML)
RETURNS XML
AS
BEGIN
RETURN #xmlData.<something here>
END
If I pass data like:
<something>
this is sample data <xxx id="1"/> and <xxx id="2" runat="server" />. More <yyy id="3" />
</something>
I would like
<something>
this is sample data <xxx id="1" runat="server" /> and <xxx id="2" runat="server" />. More <yyy id="3" />
</something>
And not :
<something>
this is sample data <xxx id="1" runat="server" /> and <xxx id="2" runat="server" runat="server"/>. More <yyy id="3" />
</something>
You can do
SET #xmlData.modify('insert attribute runat { "server" } into descendant::xxx[not(#runat)][1]');
This will however only change the first xxx descendant not having a runat attribute, at least with SQL Server 2005 you can only modify one node at a time.
Maybe combining the above with a WHILE helps e.g.
WHILE #xmlData.exist('descendant::xxx[not(#runat)]') = 1
BEGIN
SET #xmlData.modify('insert attribute runat { "server" } into descendant::xxx[not(#runat)][1]');
END
I don't have access to SQL Server 2008 R2 but I think the modify is still limited to one node at a time so you could try
ALTER FUNCTION [dbo].[ConvertXmlData](#xmlData XML)
RETURNS XML
AS
BEGIN
WHILE #xmlData.exist('descendant::xxx[not(#runat)]') = 1
BEGIN
SET #xmlData.modify('insert attribute runat { "server" } into descendant::xxx[not(#runat)][1]');
END
RETURN #xmlData
END