Oracle 10.2.0.4.0 query on partial xpath - sql

I need to change the below query to be able to query any kind of tender item.
/Basket/CardTenderItem/Description
/Basket/CashTenderItem/Description
So
/Basket/WildcardTenderItem/Description
I have looked at various examples on but cannot them to bring back any results when running (happily admit to user error if can get working!)
SELECT
RETURN_ID
,SALE_ID,
,extractValue(xmltype(RETURNxml),'/Basket/CashTenderItem/NetValue')
,extractValue(xmltype(RETURNxml),'/Basket/CashTenderItem/Description')
FROM SPR361
WHERE return_id = '9999.0303|20170327224954|2063'

If you only want to match anything the ends with TenderItem, but doesn't have anything after that, you could be specific with substring checks:
SELECT
RETURN_ID
,SALE_ID
,extractValue(xmltype(RETURNxml),
'/Basket/*[substring(name(), string-length(name()) - 9) = "TenderItem"]/NetValue')
,extractValue(xmltype(RETURNxml),
'/Basket/*[substring(name(), string-length(name()) - 9) = "TenderItem"]/Description')
FROM SPR361
WHERE return_id = '9999.0303|20170327224954|2063'
If you never have any nodes with anything after that fixed string then #Shnugo's contains approach is easier, and in Oracle would be very similar:
...
,extractValue(xmltype(RETURNxml),
'/Basket/*[contains(name(), "TenderItem")]/NetValue')
,extractValue(xmltype(RETURNxml),
'/Basket/*[contains(name(), "TenderItem")]/Description')
I'm not sure there's any real difference between name() and local-name() here.
If a basket can have multiple child nodes (card and cash, or more than one of each) you could also switch to XMLTable syntax:
SELECT
s.RETURN_ID
,s.SALE_ID
,x.netvalue
,x.description
FROM SPR361 s
CROSS JOIN XMLTable(
'/Basket/*[contains(name(), "TenderItem")]'
PASSING XMLType(s.RETURNxml)
COLUMNS netvalue NUMBER PATH './NetValue'
, description VARCHAR(80) PATh './Description'
) x
WHERE s.return_id = '9999.0303|20170327224954|2063'
And it's overkill here maybe, but for more complicated tests you can use other XPath syntax, like:
CROSS JOIN XMLTable(
'for $i in /Basket/*
where contains($i/name(), "TenderItem") return $i'
PASSING XMLType(s.RETURNxml)
...

This is SQL-Server syntax and I cannot test, if this works with Oracle too, but I think it will. You can use XQuery function contains():
DECLARE #xml XML=
N'<root>
<abcTenderItem>test1</abcTenderItem>
<SomeOther>should not show up</SomeOther>
<xyzTenderItem>test2</xyzTenderItem>
</root>';
SELECT #xml.query(N'/root/*[contains(local-name(),"TenderItem")]')
only the elements with "TenderItem" in their names show up:
<abcTenderItem>test1</abcTenderItem>
<xyzTenderItem>test2</xyzTenderItem>

Related

Parse XML value with Name Space in SQl

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)

Querying XML colum for values

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')

Using xml with SQL to evaluate a dynamic variable

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

Liferay DynamicQuery - xPath and Array comparison

My final goal is I want to get plid and portletId that can be display my article(or entry with any type if it is possible).
I have sql query that return me any portlet availble for display my article.
But when I have to use dynamicQuery to get the same results, I get problem with xPath and array comparison, please help!
SELECT * FROM portletpreferences pr
WHERE pr.preferences != '<portlet-preferences />' AND pr.ownerid = 0 AND pr.portletid ilike '%_INSTANCE_%' AND pr.plid IN(
SELECT layout.plid FROM layout
WHERE layout.type_ = 'portlet' AND layout.groupid = 19 AND layout.hidden_ is false)
AND pr.portletpreferencesid IN (
SELECT pr.portletpreferencesid FROM portletpreferences pr
WHERE 'true' = ANY(xpath('//preference[name="anyAssetType"]/value/text()', XMLPARSE(DOCUMENT pr.preferences))::text[])
OR (SELECT (array(SELECT id_ FROM journalstructure))::text[]) && xpath('//preference[name="classTypeIds"]/value/text()', XMLPARSE(DOCUMENT pr.preferences))::text[] )
If you are bent upon using this same query, then use this query directly with Custom-SQL in liferay by creating custom-finders instead of using DynamicQuery. That would give you a lot of flexibility in using any type of SQL query directly.
I don't think this query can be converted to DynamicQuery, but if you do manage to convert it then please do post it here :-)
DynamicQuery is very powerful, see e.g. my answer how to find layouts with specific JournalArticles. I think your requirement is similar to this one:
Liferay: How to find all Layouts with the specific JournalArticle in AssetPublisher portlets?

Better way in TSQL to search xml for a node that doesn't exist

We have a source XML file that has an address node, and each node is supposed to have a zip_code node beneath in order to validate. We received a file that failed the schema validation because at least one node was missing it's zip_code (there were several thousand addresses in the file).
We need to find the elements that do not have a zip code, so we can repair the file and send an audit report to the source.
--declare #x xml = bulkcolumn from openrowset(bulk 'x:\file.xml',single_blob) as s
declare #x xml = N'<addresses>
<address><external_address_id>1</external_address_id><zip_code>53207</zip_code></address>
<address><external_address_id>2</external_address_id></address>
</addresses>'
declare #t xml = (
select #x.query('for $a in .//address
return
if ($a/zip_code)
then <external_address_id />
else $a/external_address_id')
)
select x.AddressID.value('.', 'int') AddressID
from #t.nodes('./external_address_id') x(AddressID)
where x.AddressID.value('.', 'int') > 0
GO
Really, it's the where clause that bugs me. I feel like I'm depending on a cast for a null value to 0, and it works, but I'm not really sure that it should. I tried a few variations with the .exist function, but I couldn't get the correct result.
If you just want to ensure that you are selecting address elements that have a zip_code element, then adjust your XPATH to include that criteria in a predicate filter:
/addresses/address[zip_code]
If you also want to ensure that the zip_code element also has a value, use a predicate filter for the zip_node to select those that have text() nodes:
/addresses/address[zip_code[text()]]
EDIT:
Actually, I'm looking for the
opposite. I need to identify the nodes
that don't have a zip, so we can
manually correct the source data.
So, if you want to identify all of the address elements that do not have a zip_code, you can specify it in the XPATH like this:
/addresses/address[not(zip_code)]
If you just want to locate those nodes that are missing their <zip_code> element, you could use something like this:
SELECT
ADRS.ADR.value('(external_address_id)[1]', 'int') as 'ExtAdrID'
FROM
#x.nodes('/addresses/address') as ADRS(ADR)
WHERE
ADRS.ADR.exist('zip_code') = 0
It uses the built-in .exist() method in XQuery to check the existence of a subnode inside an XML node.