Find rows not used by xml column - sql

I have a status table, and I must find the unused statuses.
The status code can be used in activity templates, which have an actionlist xml column with custom formulas.
So far I have written this, which works, but is incredibly slow (more than a minute to get 5000 lines), and I would need to speed it up a bit.
select *
from [status] s
where not exists (
select top 1 1
from Wf_ActivityTemplate at
where at.actionlist.value('.', 'nvarchar(max)') like '%#GetStatusId("' + s.code + '")%'
)
The actionlist column looks like this (irrelevant nodes removed).
As you can see, I need to search in the //ActionTriplet/Argument node, which is itself an xml node stored in text.
<ArrayOfActionTriplet xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<ActionTriplet>
<Priority>1</Priority>
<Argument><?xml version="1.0" encoding="UTF-8" standalone="yes"?><string>#SetProp("STATUS",#GetStatusId("INTERNAL DESIGN REVIEW"));True</string></Argument>
<ActionCode>APPLY_FORMULA</ActionCode>
<TriggerTaskCode />
<TriggerTaskIsSecondary>false</TriggerTaskIsSecondary>
<TriggerTaskConditionFormula />
</ActionTriplet>
<ActionTriplet>
<Priority>2</Priority>
<Argument><?xml version="1.0" encoding="UTF-8" standalone="yes"?><string>#SetProp("STATUS",#GetStatusId("VALID"));True</string></Argument>
<ActionCode>APPLY_FORMULA</ActionCode>
<TriggerTaskCode />
<TriggerTaskIsSecondary>false</TriggerTaskIsSecondary>
<TriggerTaskConditionFormula />
</ActionTriplet>
</ArrayOfActionTriplet>

Your code is taking the whole content of your XML, converts it to nvarchar and performs a %x%-like-search, which is always slow. The bigger the text, the slower...
To show another approach I declare a table variable, which mocks up your table, with two rows (see the ID). I search for the code given in #YourCode:
DECLARE #YourCode NVARCHAR(100)=N'INTERNAL DESIGN REVIEW';
DECLARE #YourTable TABLE(ID INT IDENTITY, actionList XML);
INSERT INTO #YourTable(actionList) VALUES(
'<ArrayOfActionTriplet xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<ActionTriplet>
<Priority>1</Priority>
<Argument><?xml version="1.0" encoding="UTF-8" standalone="yes"?><string>#SetProp("STATUS",#GetStatusId("INTERNAL DESIGN REVIEW"));True</string></Argument>
<ActionCode>APPLY_FORMULA</ActionCode>
<TriggerTaskCode />
<TriggerTaskIsSecondary>false</TriggerTaskIsSecondary>
<TriggerTaskConditionFormula />
</ActionTriplet>
<ActionTriplet>
<Priority>2</Priority>
<Argument><?xml version="1.0" encoding="UTF-8" standalone="yes"?><string>#SetProp("STATUS",#GetStatusId("VALID"));True</string></Argument>
<ActionCode>APPLY_FORMULA</ActionCode>
<TriggerTaskCode />
<TriggerTaskIsSecondary>false</TriggerTaskIsSecondary>
<TriggerTaskConditionFormula />
</ActionTriplet>
</ArrayOfActionTriplet>')
,(
'<ArrayOfActionTriplet xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<ActionTriplet>
<Priority>1</Priority>
<Argument><?xml version="1.0" encoding="UTF-8" standalone="yes"?><string>#SetProp("STATUS",#GetStatusId("INTERNAL DESIGN REVIEW"));True</string></Argument>
<ActionCode>APPLY_FORMULA</ActionCode>
<TriggerTaskCode />
<TriggerTaskIsSecondary>false</TriggerTaskIsSecondary>
<TriggerTaskConditionFormula />
</ActionTriplet>
<ActionTriplet>
<Priority>2</Priority>
<Argument><?xml version="1.0" encoding="UTF-8" standalone="yes"?><string>#SetProp("STATUS",#GetStatusId("VALID"));True</string></Argument>
<ActionCode>APPLY_FORMULA</ActionCode>
<TriggerTaskCode />
<TriggerTaskIsSecondary>false</TriggerTaskIsSecondary>
<TriggerTaskConditionFormula />
</ActionTriplet>
</ArrayOfActionTriplet>');
--This is the query: You'll get back all Argument-elements, where the text within this element contains GetStatusId("TheCode").
SELECT s.ID
,arg.query('.')
FROM #YourTable AS s
CROSS APPLY actionList.nodes('/*:ArrayOfActionTriplet/*:ActionTriplet/*:Argument[fn:contains(.,fn:concat("GetStatusId("",sql:variable("#YourCode"),"")"))]') AS A(arg)
UPDATE
With this query you can cast the inner XML from the encoded form to real XML and get the full string readable:
SELECT s.ID
,arg.query('.')
,InnerXml.value('string[1]','nvarchar(max)')
FROM #YourTable AS s
CROSS APPLY actionList.nodes('/*:ArrayOfActionTriplet/*:ActionTriplet/*:Argument[fn:contains(.,fn:concat("GetStatusId("",sql:variable("#YourCode"),"")"))]') AS A(arg)
CROSS APPLY (SELECT CAST(arg.value('.','varchar(max)') AS XML)) AS Casted(InnerXml)
UPDATE 2: Faster, checking the existance only
If you want nothing more, than to check, wether there is an <Argument> containing your code or not, you might do this:
SELECT s.ID
FROM #YourTable AS s
WHERE actionList.exist('/*:ArrayOfActionTriplet/*:ActionTriplet/*:Argument[fn:contains(.,fn:concat("GetStatusId("",sql:variable("#YourCode"),"")"))]') =1
The final =1 means: The string is inlcuded. With =0 you'd get all rows, where this string is not included

Related

Import field in XML using SQL not working

i've been trying to import the field GivenName in my example XML but for some reason it's not working, i've been using the following SQL query, i think i'm using the correct field and the correct nodes but i'm not 100% sure about the XMLNameSpaces
Thank you very much in advance for your help
This is the example SQL Query i'm using:
DECLARE #xml XML = (SELECT [Xml] FROM ExampleTable)
;WITH XMLNAMESPACES (DEFAULT 'http://www.opentravel.org/OTA/2003/05','http://www.w3.org/2003/05/soap-envelope' )
select FirstName = ProfileInfo.value('Profiles[1]/ProfileInfo[1]/Profile[1]/Customer[1]/PersonName[1]/#GivenName', 'nvarchar(255)')
FROM #xml.nodes('Envelope/Body/OTA_Example/Info/Infos/ResUser') as T1(Profiles)
outer apply T1.Profiles.nodes('ResUser2') as T2(ProfileInfo)
This is the example XML i'm using for the import:
<Envelope xmlns="http://www.w3.org/2003/05/soap-envelope">
<soap2:Header xmlns:htng="http://htng.org/1.3/Header/" xmlns:wsa="http://www.w3.org/2005/08/addressing"
xmlns:wss="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"
xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"
xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:htnga="http://htng.org/PWSWG/2007/02/AsyncHeaders"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:soap2="http://www.w3.org/2003/05/soap-envelope">
<wsa:Action>Example</wsa:Action>
<wsa:ReplyTo>
<wsa:Address>Example2</wsa:Address>
</wsa:ReplyTo>
<htnga:ReplyTo>
<wsa:Address>Example3</wsa:Address>
</htnga:ReplyTo>
<wsa:MessageID>123</wsa:MessageID>
</soap2:Header>
<Body>
<OTA_Example xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.opentravel.org/OTA/2003/05" Version="5.000" >
<Info>
<Infos CreateDateTime="2021-09-20T06:52:40" CreatorID="User">
<UniqueID Type="1" ID="12341251" />
<ResUser>
<ResUser2 ResGuestRPH="1" PrimaryIndicator="true">
<Profiles>
<ProfileInfo>
<Profile ProfileType="1">
<Customer>
<PersonName>
<NamePrefix>Mr.</NamePrefix>
<GivenName>FirstnameTest</GivenName>
<Surname>LastnameTest</Surname>
</PersonName>
</Customer>
</Profile>
</ProfileInfo>
</Profiles>
</ResUser2>
</ResUser>
</Infos>
</Info>
</OTA_Example>
</Body>
</Envelope>
GivenName is not an attribute, so you shouldn't use # for it.
It's unclear why you needed .nodes, it is only needed if there were multiple nodes that needed breaking out into separate rows
You can also select straight out of ExampleTable, you do not need to store it in a variable.
;WITH XMLNAMESPACES (
'http://www.w3.org/2003/05/soap-envelope' AS soap,
DEFAULT 'http://www.opentravel.org/OTA/2003/05')
select FirstName = [XML].value('(soap:Envelope/soap:Body/OTA_Example/Info/Infos/ResUser/ResUser2/Profiles/ProfileInfo/Profile/Customer/PersonName/GivenName/text())[1]', 'nvarchar(255)')
FROM ExampleTable
db<>fiddle

Save XML with attribute to Table in SQL Server

Hi I have XML data with attribute as input for SQL, i need this to be inserted in my table.
XML Data is
<?xml version="1.0" encoding="ISO-8859-1"?>
<MESSAGEACK>
<GUID GUID="kfafb30" SUBMITDATE="2015-10-15 11:30:29" ID="1">
<ERROR SEQ="1" CODE="28681" />
</GUID>
<GUID GUID="kfafb3" SUBMITDATE="2015-10-15 11:30:29" ID="1">
<ERROR SEQ="2" CODE="286381" />
</GUID>
</MESSAGEACK>
I want this to be inserted in below Format
GUID SUBMIT DATE ID ERROR SEQ CODE
kfafb3 2015-10-15 11:30:29 1 1 28681
kfafb3 2015-10-15 11:30:29 1 1 2868
please help.
Look into XPath and xml Data Type Methods in MSDN. This is one possible way :
declare #xml As XML = '...you XML string here...'
INSERT INTO YourTable
SELECT
guid.value('#GUID', 'varchar(100)') as 'GUID'
,guid.value('#SUBMITDATE', 'datetime') as 'SUBMIT DATE'
,guid.value('#ID', 'int') as 'ID'
,guid.value('ERROR[1]/#SEQ', 'int') as 'SEQ'
,guid.value('ERROR[1]/#CODE', 'int') as 'CODE'
FROM #xml.nodes('/MESSAGEACK/GUID') as x(guid)
Result :
just paste this into an empty query window and execute. Adapt to your needs:
DECLARE #xml XML=
'<?xml version="1.0" encoding="ISO-8859-1"?>
<MESSAGEACK>
<GUID GUID="kfafb30" SUBMITDATE="2015-10-15 11:30:29" ID="1">
<ERROR SEQ="1" CODE="28681" />
</GUID>
<GUID GUID="kfafb3" SUBMITDATE="2015-10-15 11:30:29" ID="1">
<ERROR SEQ="2" CODE="286381" />
</GUID>
</MESSAGEACK>';
SELECT Msg.Node.value('#GUID','varchar(max)') AS [GUID] --The value is no GUID, if the original values are, you could use uniqueidentifier instead of varchar(max)
,Msg.Node.value('#SUBMITDATE','datetime') AS SUBMITDATE
,Msg.Node.value('#ID','int') AS ID
,Msg.Node.value('(ERROR/#SEQ)[1]','int') AS [ERROR SEQ]
,Msg.Node.value('(ERROR/#CODE)[1]','int') AS CODE
FROM #xml.nodes('/MESSAGEACK/GUID') AS Msg(Node)

SQL Server column containing XML needs to be concatentated

I have a sql server 2008 r2 database that contains a table with raw XML in one of the columns. I have written a query to extract data from this table using group by and stuff to concatenate all of the columns that meet a 2 column group by clause. The issue is that when I stuff the columns I get duplicate XML declarations and the XML is not valid.
<?xml version="1.0" encoding="UTF-8"?>
<Fruits>
<Fruit Type="Lemons" Price="0.50" />
<Fruit Type="Apples" Price="0.75" />
</Fruits>
<?xml version="1.0" encoding="UTF-8"?>
<Fruits>
<Fruit Type="Cherries" Price="0.10" />
<Fruit Type="Dates" Price="0.25" />
</Fruits>
I looked at adding a substring call to remove this declaration but the query is getting quite complex and slow. I am working with an XSLT transformer that creates XML output. In the end I have many rows that can be delivered to a common destination. I basically need a way to concatenate the XML while removing the duplicate XML declarations and handling the XML hierarchy
This would be converted to
<?xml version="1.0" encoding="UTF-8"?>
<Fruits>
<Fruit Type="Lemons" Price="0.50" />
<Fruit Type="Apples" Price="0.75" />
<Fruit Type="Cherries" Price="0.10" />
<Fruit Type="Dates" Price="0.25" />
</Fruits>
Does anyone know how to do this in a systematic way? I am not actually working with fruit but used it as a simplified example.
Here is the actual query
SELECT tt.ProductCodeID, tt.ProviderID, tt.ContentXML, LEN(tt.ContentXML) AS xmllength from
(SELECT p.ProductCodeID, l.ProviderID,
STUFF((
SELECT pl1.Content FROM dbo.Payload pl1
INNER JOIN Log l1 ON pl1.LogID=l1.LogID
INNER JOIN Provider p1 ON l1.ProviderID=p1.ProviderID
WHERE pl1.ProductCodeID=p.ProductCodeID AND
pl1.PayloadTypeID=pt.PayloadTypeID
AND p1.ProviderID=l.ProviderID
FOR XML PATH(''), TYPE).value('.','varchar(max)'),1,1,'') AS ContentXML
FROM dbo.Queue q
INNER JOIN log l ON q.LogID=l.LogID
INNER JOIN payload pl ON l.LogID=pl.LogID
INNER JOIN dbo.PayloadType pt ON pl.PayloadTypeID = pt.PayloadTypeID
INNER JOIN dbo.ProductCode p ON pl.ProductCodeID= p.ProductCodeID
INNER JOIN dbo.Status s ON q.StatusID=s.StatusID
WHERE s.Name = 'processed' AND pt.Name='MIF'
GROUP BY p.ProductCodeID, l.ProviderID, pt.PayloadTypeID) AS tt
Thank you
It's ugly, but if you always know what the objects are going to be you could strip out the xml you don't want.
SELECT ..., '<?xml version="1.0" encoding="UTF-8"?><Fruits>' +
REPLACE( REPLACE(xml_column,
'<?xml version="1.0" encoding="UTF-8"?><Fruits>',
''),
'</Fruits',
'') +
'</Fruits>
Or, use a similar step to preprocess the data, and use the replace function to add a column missing the xml headers.
This one is the quick solution that I can think of right now.
CREATE TABLE Test2(ID INT,Col1 XML, Col2 XML)
INSERT INTO Test2 VALUES(1,'<?xml version="1.0" encoding="UTF-8"?><Fruits><Fruit Type="Lemons" Price="0.10" /><Fruit Type="Apples" Price="0.25" /></Fruits>','<?xml version="1.0" encoding="UTF-8"?><Fruits><Fruit Type="Cherries" Price="0.10" /><Fruit Type="Dates" Price="0.25" /></Fruits>')
INSERT INTO Test2 VALUES(2,'<?xml version="1.0" encoding="UTF-8"?><Fruits><Fruit Type="Lemons2" Price="0.10" /><Fruit Type="Apples2" Price="0.25" /></Fruits>','<?xml version="1.0" encoding="UTF-8"?><Fruits><Fruit Type="Cherries2" Price="0.10" /><Fruit Type="Dates3" Price="0.25" /></Fruits>')
SELECT ID, (
SELECT Fruit,Price FROM(
SELECT
Tbl.Col.value('#Type', 'varchar(100)') AS Fruit,
Tbl.Col.value('#Price', 'varchar(10)') AS Price
FROM Col1.nodes('//Fruit') Tbl(Col)
UNION
SELECT
Tbl.Col.value('#Type', 'varchar(100)'),
Tbl.Col.value('#Price', 'varchar(10)')
FROM Col2.nodes('//Fruit') Tbl(Col)) Fruits FOR XML AUTO
) FROM Test2

SqlServer XQuery always concat two fields

I want to get somedata from one column only from an xml field:
<CDirData xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://Fnet.ESB.Schemas.CentroDirectivo.CDirData">
<ProcedureData xmlns="">
<ProcedureId>7001</ProcedureId>
<CentroDirectivo>Subsecretaria</CentroDirectivo>
</ProcedureData>
<SolicitudData xmlns="">
...
with my query
SELECT top 1
[Message].query('//*[local-name()="ProcedureId"]').value('.','nvarchar(max)') as R
from
ManagementCenterQueueHistorical
but allways returns the fields concat
I would need just only the first one
Thanks in advance!
Put your XPath expressin in parenthessis and add [1] to get only first element from the group
DECLARE #x XML = '<CDirData xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://Fnet.ESB.Schemas.CentroDirectivo.CDirData">
<ProcedureData xmlns="">
<ProcedureId>7001</ProcedureId>
<CentroDirectivo>Subsecretaria1</CentroDirectivo>
</ProcedureData>
<ProcedureData xmlns="">
<ProcedureId>7002</ProcedureId>
<CentroDirectivo>Subsecretaria2</CentroDirectivo>
</ProcedureData>
<ProcedureData xmlns="">
<ProcedureId>7003</ProcedureId>
<CentroDirectivo>Subsecretaria3</CentroDirectivo>
</ProcedureData>
<ProcedureData xmlns="">
<ProcedureId>7004</ProcedureId>
<CentroDirectivo>Subsecretaria4</CentroDirectivo>
</ProcedureData>
</CDirData>'
SELECT
#x.query('(//*[local-name()="ProcedureId"])[1]').value('.','nvarchar(max)') as R

SQL server XML type

I have an XML type column in SQL server:
<LogMessage xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="LogMessageBusinessServiceRequest">
<Application>Services.Business.myapp</Application>
<Time>2013-05-30T15:01:38.932Z</Time>
<Level>Info</Level>
<Message>MultiQuery Biz Request</Message>
<MachineName>Machine1</MachineName>
<ThreadId>16084</ThreadId>
<Callsite>BLAH</Callsite>
<CreatedBy>Machine1\svc_biz_myapp</CreatedBy>
<Context>
<myappExtraInfo xmlns="http://services.somedomain.com/myapp/logging/extraInfo" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<CallType>MultiProcessQuery</CallType>
<RequestId>r163505508498822282742</RequestId>
<FirmId>160</FirmId>
<PMFirmId>203</PMFirmId>
<SubscriptionId>0</SubscriptionId>
<Token />
<LastClientSvcTimeMs>0</LastClientSvcTimeMs>
<ResultCode>0</ResultCode>
<ResultCodeDescription>OK</ResultCodeDescription>
<ElapsedTimeTotalMs>110</ElapsedTimeTotalMs>
<ElapsedTimeMtMs>110</ElapsedTimeMtMs>
<ElapsedTimeDacMs>109</ElapsedTimeDacMs>
<ElapsedTimePMSSMs>-1</ElapsedTimePMSSMs>
</myappExtraInfo>
</Context>
I want to get the FirmID from there. But I am not able with the following section of my select statement:
select
[Body].value('(/LogMessage/Context/myappExtraInfo/FirmId)[1]', 'varchar(max)') FirmID
What am I doing wrong?
Thanks for your time.
Shiyam
Try this - you need to respect the XML namespace that's defined on your <myappExtraInfo> node (and thus applies to all subnodes, too):
DECLARE #Test TABLE (ID INT NOT NULL, XmlContent XML)
INSERT INTO #Test VALUES(1,
'<LogMessage xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="LogMessageBusinessServiceRequest">
<Application>Services.Business.myapp</Application>
<Time>2013-05-30T15:01:38.932Z</Time>
<Level>Info</Level>
<Message>MultiQuery Biz Request</Message>
<MachineName>Machine1</MachineName>
<ThreadId>16084</ThreadId>
<Callsite>BLAH</Callsite>
<CreatedBy>Machine1\svc_biz_myapp</CreatedBy>
<Context>
<myappExtraInfo xmlns="http://services.somedomain.com/myapp/logging/extraInfo" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<CallType>MultiProcessQuery</CallType>
<RequestId>r163505508498822282742</RequestId>
<FirmId>160</FirmId>
<PMFirmId>203</PMFirmId>
<SubscriptionId>0</SubscriptionId>
<Token />
<LastClientSvcTimeMs>0</LastClientSvcTimeMs>
<ResultCode>0</ResultCode>
<ResultCodeDescription>OK</ResultCodeDescription>
<ElapsedTimeTotalMs>110</ElapsedTimeTotalMs>
<ElapsedTimeMtMs>110</ElapsedTimeMtMs>
<ElapsedTimeDacMs>109</ElapsedTimeDacMs>
<ElapsedTimePMSSMs>-1</ElapsedTimePMSSMs>
</myappExtraInfo>
</Context>
</LogMessage>')
;WITH XMLNAMESPACES('http://services.somedomain.com/myapp/logging/extraInfo' AS ns)
SELECT
XmlContent.value('(/LogMessage/Context/ns:myappExtraInfo/ns:FirmId)[1]', 'int')
FROM #Test
WHERE ID = 1
This returns the value 160 to me.