I have an xml string which I can read the value of the ID out with
;WITH XMLNAMESPACES
('http://collaborate.com/svn/capabilities/tdp/ManageNetworkAndServiceDiagnosticsV4/' AS P,
'https://collaborate.com/svn/edm/tdp/Test' as p2)
Select #id = feed.xx.value('.','VARCHAR(50)')
From #smxml.nodes('/p:initiateTest/p2:Test/p2:id') feed(xx)
This sets the #ID variable, which I amend, and then I want to place it back into the table.
I have tried
;WITH XMLNAMESPACES
('http://collaborate.com/svn/capabilities/tdp/ManageNetworkAndServiceDiagnosticsV4/' AS p,
'https://collaborate.com/svn/edm/tdp/Test' as p2)
UPDATE CT_GTCS_Temp_XML
SET xmlStartString.modify('replace value of (/p:initiateTest/p2:Test/p2:id with #New_Id)')
BUt I get an error of
XQuery [CT_GTCS_Temp_XML.xmlStartString.modify()]: ")" was expected.
As the error message suggested, you didn't put the closing bracket in correct place. Put it right after the XQuery, not after with clause :
.....
SET xmlStartString.modify('replace value of
(/p:initiateTest/p2:Test/p2:id/text())[1]
with sql:variable("#New_Id")')
Related
I've used a combination of CHARINDEX and SUBSTRING but can't get it working.
I get passed a variable in SQL that contains a lot of text but has an email in it. I need to extract the email value.
I have to use SQL 2008.
I'm trying to extract the value between "EmailAddress":" and ",
An example string is here:
{ "Type":test,
"Admin":test,
"User":{
"UserID":"16959191",
"FirstName":"Test",
"Surname":"Testa",
"EmailAddress":"Test.Test#test.com",
"Address":"Test"
}
}
Assuming you can't upgrade to 2016 or higher, you can use a combination of substring and charindex.
I've used a common table expression to make it less cumbersome, but you don't have to.
DECLARE #json varchar(4000) = '{ "Type":test,
"Admin":test,
"User":{
"UserID":"16959191",
"FirstName":"Test",
"Surname":"Testa",
"EmailAddress":"Test.Test#test.com",
"Address":"Test"
}
}';
WITH CTE AS
(
SELECT #Json as Json,
CHARINDEX('"EmailAddress":', #json) + LEN('"EmailAddress":') As StartIndex
)
SELECT SUBSTRING(Json, StartIndex, CHARINDEX(',', json, StartIndex) - StartIndex)
FROM CTE
Result: "Test.Test#test.com"
The first hint is: Move to v2016 if possible to use JSON support natively. v2008 is absolutely outdated...
The second hint is: Any string action (and all my approaches below will need some string actions too), will suffer from forbidden characters, unexpected blanks or any other surprise you might find within your data.
Try it like this:
First I create a mockup scenario to simulate your issue
DECLARE #tbl TABLE(ID INT IDENTITY,YourJson NVARCHAR(MAX));
INSERT INTO #tbl VALUES
(N'{ "Type":"test1",
"Admin":"test1",
"User":{
"UserID":"16959191",
"FirstName":"Test1",
"Surname":"Test1a",
"EmailAddress":"Test1.Test1#test.com",
"Address":"Test1"
}
}')
,(N'{ "Type":"test2",
"Admin":"test2",
"User":{
"UserID":"16959191",
"FirstName":"Test2",
"Surname":"Test2a",
"EmailAddress":"Test2.Test2#test.com",
"Address":"Test2"
}
}');
--Starting with v2016 there is JSON support
SELECT JSON_VALUE(t.YourJson, '$.User.EmailAddress')
FROM #tbl t
--String-methods
--use CHARINDEX AND SUBSTRING
DECLARE #FirstBorder NVARCHAR(MAX)='"EMailAddress":';
DECLARE #SecondBorder NVARCHAR(MAX)='",';
SELECT t.*
,A.Pos1
,B.Pos2
,SUBSTRING(t.YourJson,A.Pos1,B.Pos2 - A.Pos1) AS ExtractedEMail
FROM #tbl t
OUTER APPLY(SELECT CHARINDEX(#FirstBorder,t.YourJson)+LEN(#FirstBorder)) A(Pos1)
OUTER APPLY(SELECT CHARINDEX(#SecondBorder,t.YourJson,A.Pos1)) B(Pos2);
--use a XML trick
SELECT CAST('<x>' + REPLACE(REPLACE((SELECT t.YourJson AS [*] FOR XML PATH('')),'"EmailAddress":','<mailAddress value='),',',' />') + '</x>' AS XML)
.value('(/x/mailAddress/#value)[1]','nvarchar(max)')
FROM #tbl t
Some explanations:
JSON-support will parse the value directly from a JSON path.
For CHARINDEX AND SUBSTRING I use APPLY. The advantage is, that you can use the computed positions like a variable. No need to repeat the CHARINDEX statements over and over.
The XML approach will transform your JSON to a rather strange and ugly XML. The only sensefull element is <mailAddress> with an attribute value. We can use the native XML method .value() to retrieve the value you are asking for:
An intermediate XML looks like this:
<x>{ "Type":"test1" />
"Admin":"test1" />
"User":{
"UserID":"16959191" />
"FirstName":"Test1" />
"Surname":"Test1a" />
<mailAddress value="Test1.Test1#test.com" />
"Address":"Test1"
}
}</x>
I have following XML :
<ProductionSchedule xmlns:inp2="http://www.wbf.org/xml/B2MML-V0401" xmlns="http://www.wbf.org/xml/B2MML-V0401">
<inp2:ProductionRequest>
<inp2:ID>0916A</inp2:ID>
<inp2:Description>SUBH190916A</inp2:Description>
<inp2:Location>
<inp2:EquipmentID>MYEqupiment</inp2:EquipmentID>
</inp2:Location>
<inp2:SegmentRequirement>
<inp2:ID>000</inp2:ID>
<inp2:EarliestStartTime>2015-10-17T12:00:00</inp2:EarliestStartTime>
<inp2:LatestEndTime>2015-10-19T12:00:00</inp2:LatestEndTime>
<inp2:MaterialProducedRequirement>
<inp2:MaterialDefinitionID>GEEC3MA0025EMZI</inp2:MaterialDefinitionID>
<inp2:Quantity>
<inp2:QuantityString>2</inp2:QuantityString>
</inp2:Quantity>
<inp2:MaterialProducedRequirementProperty>
<inp2:ID>ERPWOStatus</inp2:ID>
<inp2:Value>
<inp2:ValueString>Released</inp2:ValueString>
</inp2:Value>
</inp2:MaterialProducedRequirementProperty>
<inp2:MaterialProducedRequirementProperty>
<inp2:ID>ROUTING</inp2:ID>
<inp2:Value>
<inp2:ValueString>SOmeMPRVaue</inp2:ValueString>
</inp2:Value>
</inp2:MaterialProducedRequirementProperty>
<inp2:MaterialProducedRequirementProperty>
<inp2:ID>MPValue2</inp2:ID>
<inp2:Value>
<inp2:ValueString>2016-01-21T12:00:00</inp2:ValueString>
</inp2:Value>
</inp2:MaterialProducedRequirementProperty>
</inp2:MaterialProducedRequirement>
</inp2:SegmentRequirement>
</inp2:ProductionRequest>
</ProductionSchedule>
I am trying to get the value MPValue2 , from the XML.
I tried with following:
Select `#xml.value('(/ProductionSchedule/inp2:ProductionRequest/inp2:SegmentRequirement/inp2:MaterialProducedRequirement/inp2:MaterialProducedRequirementProperty)[1]','nvarchar(255)')`
Your select is OK, but you must consider/declare the namespaces:
WITH XMLNAMESPACES(DEFAULT 'http://www.wbf.org/xml/B2MML-V0401'
,'http://www.wbf.org/xml/B2MML-V0401' AS inp2)
Select #xml.value('(/ProductionSchedule/inp2:ProductionRequest/inp2:SegmentRequirement/inp2:MaterialProducedRequirement/inp2:MaterialProducedRequirementProperty)[1]','nvarchar(255)')
This works too (wildcard) but it's better to be as specific as possible:
Select #xml.value('(/*:ProductionSchedule/*:ProductionRequest/*:SegmentRequirement/*:MaterialProducedRequirement/*:MaterialProducedRequirementProperty)[1]','nvarchar(255)')
The fast and lazy would work too :-) but not fast in terms of performance...
Select #xml.value('(//*:MaterialProducedRequirementProperty)[1]','nvarchar(255)')
UPDATE
This is the query to get all your Properties:
WITH XMLNAMESPACES(DEFAULT 'http://www.wbf.org/xml/B2MML-V0401'
,'http://www.wbf.org/xml/B2MML-V0401' AS inp2)
SELECT prop.value('(inp2:ID)[1]','nvarchar(100)') AS Property
FROM #xml.nodes('/ProductionSchedule/inp2:ProductionRequest/inp2:SegmentRequirement/inp2:MaterialProducedRequirement/inp2:MaterialProducedRequirementProperty') AS A(prop)
The result
Property
--------
ERPWOStatus
ROUTING
MPValue2
UPDATE 2: Use the ID as filter in XQuery
See how I added the filter at the end of the XPath in .nodes().
Nodes will return all sub-elements row-wise. The filter will reduce the resultset to one single row (if inp2:ID is unique!) and then read the Value/ValueString.
I let the namespace declaration for DEFAULT and inp2. But, as #Serf pointed out correctly, both URLs are equal. It would be enough to declare only the DEFAULT and query without any namespace-prefixes...
DECLARE #TheID NVARCHAR(100)='MPValue2';
WITH XMLNAMESPACES(DEFAULT 'http://www.wbf.org/xml/B2MML-V0401'
,'http://www.wbf.org/xml/B2MML-V0401' AS inp2)
SELECT prop.value('(inp2:Value/inp2:ValueString)[1]','nvarchar(100)') AS Property
FROM #xml.nodes('/ProductionSchedule/inp2:ProductionRequest/inp2:SegmentRequirement/inp2:MaterialProducedRequirement/inp2:MaterialProducedRequirementProperty[inp2:ID=sql:variable("#TheID")]') AS A(prop)
I have a SQL Server table with an XML column, and it contains data something like this:
<Query>
<QueryGroup>
<QueryRule>
<Attribute>Integration</Attribute>
<RuleOperator>8</RuleOperator>
<Value />
<Grouping>OrOperator</Grouping>
</QueryRule>
<QueryRule>
<Attribute>Integration</Attribute>
<RuleOperator>5</RuleOperator>
<Value>None</Value>
<Grouping>AndOperator</Grouping>
</QueryRule>
</QueryGroup>
</Query>
Each QueryRule will only have one Attribute, but each QueryGroup can have many QueryRules. Each Query can also have many QueryGroups.
I need to be able to pull all records that have one or more QueryRule with a certain attribute and value.
SELECT *
FROM QueryBuilderQueries
WHERE [the xml contains any value=X where the attribute is either Y or Z]
I've worked out how to check a specific QueryRule, but not "any".
SELECT
Query
FROM
QueryBuilderQueries
WHERE
Query.value('(/Query/QueryGroup/QueryRule/Value)[1]', 'varchar(max)') like 'UserToFind'
AND Query.value('(/Query/QueryGroup/QueryRule/Attribute)[1]', 'varchar(max)') in ('FirstName', 'LastName')
You can use two exist(). One to check the value and one to check Attribute.
select Q.Query
from dbo.QueryBuilderQueries as Q
where Q.Query.exist('/Query/QueryGroup/QueryRule/Value/text()[. = "UserToFind"]') = 1 and
Q.Query.exist('/Query/QueryGroup/QueryRule/Attribute/text()[. = ("FirstName", "LastName")]') = 1
If you really want the like equivalence when you search for a Value you can use contains().
select Q.Query
from dbo.QueryBuilderQueries as Q
where Q.Query.exist('/Query/QueryGroup/QueryRule/Value/text()[contains(., "UserToFind")]') = 1 and
Q.Query.exist('/Query/QueryGroup/QueryRule/Attribute/text()[. = ("FirstName", "LastName")]') = 1
According to http://technet.microsoft.com/pl-pl/library/ms178030%28v=sql.110%29.aspx
"The XQuery must return at most one value"
If you are quite certain that for example your XML has let's say maximum 10 QueryRules you could maybe use WHILE to loop everything while droping your results into temporary table?
maybe below can help you anyway
CREATE TABLE #temp(
Query type)
DECLARE #i INT
SET #i = 1
WHILE #i >= 10
BEGIN
INSERT INTO #temp
SELECT
Query
FROM QueryBuilderQueries
WHERE Query.value('(/Query/QueryGroup/QueryRule/Value)[#i]', 'varchar(max)') LIKE 'UserToFind'
AND Query.value('(/Query/QueryGroup/QueryRule/Attribute)[#i]', 'varchar(max)') IN ('FirstName', 'LastName')
#i = #i + 1
END
SELECT
*
FROM #temp
It's a pity that the SQL Server (I'm using 2008) does not support some XQuery functions related to string such as fn:matches, ... If it supported such functions, we could query right inside XQuery expression to determine if there is any. However we still have another approach. That is by turning all the possible values into the corresponding SQL row to use the WHERE and LIKE features of SQL for searching/filtering. After some experiementing with the nodes() method (used on an XML data), I think it's the best choice to go:
select *
from QueryBuilderQueries
where exists( select *
from Query.nodes('//QueryRule') as v(x)
where LOWER(v.x.value('(Attribute)[1]','varchar(max)'))
in ('firstname','lastname')
and v.x.value('(Value)[1]','varchar(max)') like 'UserToFind')
This is a unique problem..I think. So my goal is to input a variable and get a row from my column. Let me explain a little with the code im doing.
SELECT
pref.query('Database/text()') as PersonSkills,
pref.query('FillQuery/text()') as PersonSkills,
pref.query('TabText/text()') as PersonSkills,
pref.query('TooltipText/text()') as PersonSkills
FROM table CROSS APPLY
Tag.nodes('/Root/Configuration/TaskSelectorControl/QueueSelector') AS People(pref)
this works fine. However what I need to do is pass in the last part, the queue selector as a variables.
DECLARE #Xml XML
DECLARE #AttributeName VARCHAR(MAX) = 'QueueSelector'
SELECT
pref.query('Database/text()') as PersonSkills,
pref.query('FillQuery/text()') as PersonSkills,
pref.query('TabText/text()') as PersonSkills,
pref.query('TooltipText/text()') as PersonSkills
FROM table CROSS APPLY
Tag.nodes('/Root/Configuration/TaskSelectorControl[#Name=sql:variable("#AttributeName")]
') AS People(pref)
this doesnt work, any ideas why?
Well, I kinda lied. the bottom works, however it returns an empty dataset
/Root/Configuration/TaskSelectorControl/QueueSelector
is not equivalent to:
/Root/Configuration/TaskSelectorControl[#Name='QueueSelector']
The above XPath selects <TaskSelectorControl Name="QueueSelector">, not <QueueSelector> children of <TaskSelectorControl>.
You could either do this in XPath:
/Root/Configuration/TaskSelectorControl/*[local-name(.)=sql:variable("#AttributeName")]
Or it might be simpler to concat prior to evaluating:
'/Root/Configuration/TaskSelectorControl/' + #AttributeName
I am performing a query against an XML data type column in SQL Server 2012. An example of the data is:
<ns:Resume xmlns:ns="http://schemas.microsoft.com/sqlserver/2004/07/adventure-works/Resume">
<ns:Name>
<ns:Name.Prefix></ns:Name.Prefix>
<ns:Name.First>Shai</ns:Name.First>
<ns:Name.Middle></ns:Name.Middle>
<ns:Name.Last>Bassli</ns:Name.Last>
<ns:Name.Suffix></ns:Name.Suffix>
</ns:Name>
...
</ns:Resume>
I am trying to write a query to return the first names.
This query returns a list of first names as expected:
WITH XMLNAMESPACES ('http://schemas.microsoft.com/sqlserver/2004/07/adventure-works/Resume' AS ns)
SELECT [Resume].query('(//ns:Name.First)').value('.[1]', 'nvarchar(100)')
FROM HumanResources.JobCandidate;
However, this query returns an error:
WITH XMLNAMESPACES ('http://schemas.microsoft.com/sqlserver/2004/07/adventure-works/Resume' AS ns)
SELECT [Resume].value('(//ns:Name.First)[1]', 'nvarchar(100)')
FROM HumanResources.JobCandidate;
Error:
Msg 9314, Level 16, State 1, Line 2
XQuery [HumanResources.JobCandidate.Resume.value()]: Cannot implicitly atomize or apply 'fn:data()' to complex content elements, found type 'xs:anyType' within inferred type '(element(ns{http://schemas.microsoft.com/sqlserver/2004/07/adventure-works/Resume}:Name.First,xs:string) | element(ns{http://schemas.microsoft.com/sqlserver/2004/07/adventure-works/Resume}:Name.First,xs:anyType)) ?'.
There's some basic understanding that I'm missing here but I'm not sure what it is. Can someone enlighten me? Why does the second query return an error?
Your XPath expression could lead to multiple rows being returned, for each row in the SQL Server table. You'll need to use a CROSS APPLY and a call to .nodes() to get that information you're after:
WITH XMLNAMESPACES ('http://schemas.microsoft.com/sqlserver/2004/07/adventure-works/Resume' AS ns)
SELECT
JobCandidateID,
ResNames.value('(ns:Name.First)[1]', 'nvarchar(100)')
FROM
HumanResources.JobCandidate
CROSS APPLY
[Resume].nodes('/ns:Resume/ns:Name') AS XTbl(ResNames)
That should return all JobCandidateID values and all first names defined in the Resume XML column for each row in the table.
If you can be sure that there's only ever going to be a single <name> tag in your XML column, then you could also shorten this to:
WITH XMLNAMESPACES ('http://schemas.microsoft.com/sqlserver/2004/07/adventure-works/Resume' AS ns)
SELECT
JobCandidateID,
[Resume].value('(/ns:Resume/ns:Name/ns:Name.First)[1]', 'nvarchar(100)')
FROM
HumanResources.JobCandidate