I have a problem with XQuery. The logic:
Obtain the names of all teachers starting with "Manuel", eliminating repetitions and indicating all classrooms in each teacher's class. Order by name from the shortest to the longest.
I have done this:
for $x in (doc("LMSGI06")//course[starts-with(teacher, "Manuel")])
order by string-length($x/teacher)
return
<teacher>
<name>{data(distinct-values($x/teacher))}</name>
<classroom>{data($x/classroom)}</classroom>
</teacher>`
This is the result of my query:
<teacher>
<name>Manuel Gómez</name>
<classroom>2</classroom>
</teacher>
<teacher>
<name>Manuel Gómez</name>
<classroom>3</classroom>
</teacher>
<teacher>
<name>Manuela Berenguel</name>
<classroom>1</classroom>
</teacher>
<teacher>
<name>Manuel Antonio López</name>
<classroom>1</classroom>
</teacher>
The structure of the xml is this:
<shop>
<education>
<course id="1">
<teacher>Manuela Berenguel</teacher>
<classroom>1</classroom>
</course>
</education>
</shop>
I need Manuel Gómez to appear only once, but I don´t know how. I´m using distinct-values() but it´s not working. Can someone help me, please?
distinct-values() isn't helping in your current script, because you are applying it to each individual teacher name as you are generating the element. So, there will only be one value at a time and distinct-values() returns the current value being processed.
In order to de-duplicate the names, you would want to apply distinct-values() to all of the teacher element values. Then, you need to decide how to represent the multiple classroom values in your output. The following would produce multiple classroom elements if there are multiple courses for that teacher.
let $manuel-courses := doc("LMSGI06")//course[starts-with(teacher, "Manuel")]
for $teacher in distinct-values($manuel-courses/teacher)
order by string-length($teacher)
return
<teacher>
<name>{data($teacher))}</name>
{$manuel-courses[teacher = $teacher]/classroom)}
</teacher>`
Related
I have the following XML structure. I am trying to extract the attributes StartDate and EndDate of the relationship period, that is only if rr:PeriodType is RELATIONSHIP_PERIOD.
However, the nodes for "relationship" and "accounting" have exactly the same name and am not sure how to proceed.
<rr:RelationshipPeriods>
<rr:RelationshipPeriod>
<rr:StartDate>2018-01-01T00:00:00.000Z</rr:StartDate>
<rr:EndDate>2018-12-31T00:00:00.000Z</rr:EndDate>
<rr:PeriodType>ACCOUNTING_PERIOD</rr:PeriodType>
</rr:RelationshipPeriod>
<rr:RelationshipPeriod>
<rr:StartDate>2019-01-02T00:00:00.000Z</rr:StartDate>
<rr:PeriodType>RELATIONSHIP_PERIOD</rr:PeriodType>
</rr:RelationshipPeriod>
</rr:RelationshipPeriods>
I tried using this code
ldply(xpathApply(xmlData, '//rr:RelationshipPeriod/rr:StartDate', getChildrenStrings), rbind)
But doesn't work well as it's hard to understand if it is extracting accounting or relationship period.
Any help would be greatly appreciated!
For rr:StartDate use XPath:
//rr:RelationshipPeriod[rr:PeriodType='RELATIONSHIP_PERIOD']/rr:StartDate
But probably better to first find the correct rr:RelationshipPeriod using XPath:
//rr:RelationshipPeriod[rr:PeriodType='RELATIONSHIP_PERIOD']
See this answer on how to reuse the result of a XPath.
But don't use // in front of rr:StartDate and rr:EndDate
I want to specify my original question (linked below) on how to obtain a list of all elements and their attributes.
What I am looking for is this:
a list of all elements and their attributes
so that elements and attributes are associated/it is clear which elements and attributes belong together
without any duplicates (but if e.g. an attribute is used together with two elements, I want it to be listed with both elements)
in this manner:
Element1 #Attribute1, #Attribute2,...
Element2 #Attribute3, #Attribute4,...
So far, I have tried this:
for $x in collection("XYZ")
let $att := local-name(//#*)
let $ele := local-name(//*)
let $eleatt := string-join($ele, $att)
return $eleatt
which I modified, after reading a helpful comment by #michaelhkay, to this:
for $x in collection("XYZ")
return distinct-values(string-join(($x//*!local-name(), $x//#*!local-name()), ',
'))
However, so far, the elements and attributes are not associated and I am also not sure if all distinct values are gone since I still see some twice (however, they could be used with a variety of elements).
I appreciate any help!
Thanks in advance, Eleonore
Original question: How do I get a list of all elements and their attributes via XQuery
If you want to eliminate duplicate elements by element name I would use
for $el in collection("XYZ")//*
group by $name := node-name($el)
return ($name, $el[1]/#*!('#' || node-name())) => string-join(' ')
https://xqueryfiddle.liberty-development.net/6qVSgeZ/1
Note however, if you group elements that way by their name, you will not distinguish e.g. <foo att1="value"/> and <foo att2="value"/>, i.e. you would only get the output for one of the foo elements. So how/where you want to eliminate duplicates is not quite clear in that context.
this is about XQuery - I am using MarkLogic as Database.
I have data as in the following example:
<instrument name="myTest1" id="test1">
<daten>
<daily>
<day date="2016-02-05">
<screener>
<column name="i1">
<value>1</value>
<bg>red</bg>
</column>
<column name="i2">
<value>1</value>
<fg>lime</bg>
</column>
<column name="i4">
<fg>black</bg>
</column>
</screener>
</day>
</daily>
</daten>
</instrument>
I have many instruments, and each one has an entry for each day in the daily element, and inside screener, there can be manz columns, all with different names. Some screeners include more columns than others. Each column can include a value element, a bg element and a fg element.
I want to search for instruments that fullfill specific criteria about what kind of columns do have children with specific values. Example: I want a sequence of all instruments, that for a given day, have a value 1 for column i1 and that have a fg black for column i2
Since I have many different of those conditions, I would not like to hardcode them in XQuery where clauses. I did that for a few and it works, but the code gets a lot of duplications and is hard to maintain.
My question is, is it possible to build a where clause in a FLOWR statement programatically, meaning, based on another xml structure, which could look like this:
<searchpatterns>
<pattern name="test1">
<c>
<name>i1</name>
<element>value</element>
<value>1</value>
</c>
<c>
<name>i2</name>
<element>fg</element>
<value>red</value>
<modifier>not</modifier>
</c>
</pattern>
</searchpatterns>
which would find those instruments, where the screener has a column i1 which itself has a value of 1, and also it must not have column i2 with a fg of red.
When I do it the normal way I query my date like this:
for $res in doc()/instrument
where $res/daten/daily/day[#date="2016-02-05"]/screener/column[#name="i1"]/value/text()="1"
and res/daten/daily/day[#date="2016-02-05"]/screener/column[#name="i2"]/fg/text()!="red"
This kind of where clause I want to generate based on an XML structure.
I did some research of the MarkLogic inbuilt cts:search function and a lot of stuff around it but it seems to be for something else (more user interactive searching)
If you have a hint to point me in the right direction, if what I want is even possible, I would very much appreciate it.Thanks!
The doc()/instrument XPath asks for every document with an instrument element and then filters those documents.
Where possible, it's usually better in MarkLogic to model the documents so you can use the indexes to retrieve as few documents as possible. It's also usually better to use cts:search() instead of XPath to generate the sequence so you are working directly with the indexes.
In this case, you might consider using the values of the name attribute as elements instead of the generic "column." You could then generate a cts:element-query that matches the name containing a cts:element-value-query that matches the value within the name.
Hoping that helps,
Yes, this can be achieved programmatically. If you want to check whether an element satisifes a test for every item in a sequence, the every ... satisfies construct comes to mind. So in this case it could be:
for $res in doc()/instrument
where every $pattern in $searchpatterns/pattern/c satisfies (
let $equal := $res/daten/daily/day[#date="2016-02-05"]/screener/column[#name = $pattern/name]/*[name() = $pattern/element] = $pattern/value
return if ($pattern/modifier = "not") then not($equal) else $equal
)
return $res
So every $pattern will be checked. I assume the modifier element is supposed to modify the equal construct. So we first check if the element satisfies the equal condition and the we check whether the modifier element is equal to not. Of course, applying the same idea could also be used to implement other modifiers as well.
I'm getting the XDMP-NOTANODE error when I try to run an XQuery in MarkLogic. When I loaded my xml documents I loaded meta data files with them. I'm a student and I don't have experience in XQuery.
error:
[1.0-ml] XDMP-NOTANODE: (err:XPTY0019) $article/article/front/article-meta/title-group/article-title -- xs:untypedAtomic("
") is not a node
Stack Trace
At line 3 column 77:
In xdmp:eval("(for $article in fn:distinct-values(/article/text()) ...", (), <options xmlns="xdmp:eval"><database>4206169969988859108</database> <root>C:\mls-projects\pu...</options>)
$article := xs:untypedAtomic("
")
1. (for $article in fn:distinct-values(/article/text())
2.
3. return (fn:distinct-values($article/article/front/article-meta/title-group/article-title)
4.
5.
Code:
(
for $article in fn:distinct-values(/article/text())
return (
fn:distinct-values($article/article/front/article-meta/title-group/article-title/text())
)
)
Every $article is bound to an atomic value (fn:distinct-values() returns a sequence of atomic values). Then you try to apply a path expression (using the / operator) on $article. Which is forbidden, as the path operator requires its LHS operator to be nodes.
I am afraid your code does not make sense enough for me to suggest you an actual solution. I can only pinpoint where the error is.
Furthermore, using text() at the end of a path is most of the time a bad idea. And if /article is a complex document, it is certainly not what you want. One of the text nodes you select (most likely the first one) is simply one single newline character.
What do you want to achieve?
Your $article variable is bound to an atomic value, not a node() from the article document. You can only use an XPath axis on a node.
When you apply the function distinct-values() in the for statement, it returns simple string values, not the article document or nodes from it.
You can probably make things work by using the values in a predicate filter like this:
for $article-text in fn:distinct-values(/article/text())
return
fn:distinct-values(/article[text()=$article-text]/front/article-meta/title-group/article-title/text())
Note: The above XQuery should avoid the XDMP-NOTANODE error, but there are likely easier (and more efficient) solutions for achieving your goal. If you were to post a sample of your document and describe what you are trying to achieve, we could suggest alternatives.
Bit of a wild guess, but you have two distinct-values in your code. That makes me think you want a unique list of articles, and then finally a unique list of article-title's. I would hope you already have unique articles in your database, unless you are explicitly attempting to de-duplicate them.
In case you just want the overall unique list of article titles, I would do something like:
distinct-values(
for $article in collection()/article
return
$article/front/article-meta/title-group/article-title
)
HTH!
This is similar to Merge existing records in neo4j, remove duplicates, keep relationships, except that the nodes I want to merge have 0-2 relationships I want to keep.
Take the graph generated by:
create (:Person {name:"Bob"})-[:RELATED_TO]->(:Person {name:"Jane"})-[:FRIENDS_WITH]->(:Person {name:"Tim"})<-[:FRIENDS_WITH]-(:Person {name:"Jane"}),
(:Person {name:"Sally"})-[:RELATED_TO]->(:Person {name:"Jane"})
I want to merge the duplicate Jane nodes, preserving the RELATED_TO and FRIENDS_WITH relationships, removing the duplicates.
From the other question I can get as far as:
match (p:Person {name:"Jane"})
with p.name as name, collect(p) as ps, count(*) as pcount
where pcount > 1
with head(ps) as first, tail(ps) as rest
unwind rest as to_delete
return to_delete
But I can't seem to get the matches and/or optional matches correct for merging. I tried chaining optional matches and doing the merge in one statement and neo4j gives me a Statement.ExecutionFailure with no additional message. I tried breaking out the merges into each match and ended up with "other node is null". Thoughts?
The following query is working. On a side note, for this kind of refactoring I would love the day when it would be possible to set a relationship type with a dynamic variable :
MATCH (n:Person { name:"Jane" })
WITH collect(n) AS janes
WITH head(janes) AS superJane, tail(janes) AS badJanes
UNWIND badJanes AS badGirl
OPTIONAL MATCH (badGirl)-[r:FRIENDS_WITH]->(other)
OPTIONAL MATCH (badGirl)<-[r2:RELATED_TO]-(other2)
DELETE r, r2, badGirl
WITH superJane, collect(other) AS friends, collect(other2) AS related
FOREACH (x IN friends | MERGE (superJane)-[:FRIENDS_WITH]->(x))
FOREACH (x IN related | MERGE (x)-[:RELATED_TO]->(superJane))
Result :