I got some dataset with ads of diffrent companys.
for example
<jobs>
<job>
<company>A</company>
<value>Testvalue</value>
</job>
<job>
<company>A</company>
<value>Testvalue</value>
</job>
<job>
<company>B</company>
<value>Testvalue</value>
</job>
<job>
<company>C</company>
<value>Testvalue</value>
</job>
</jobs>
What I´m trying to do is generate a custom output. I want to have only 1 record for each company
Example output wanted:
<jobs>
<job>
<company>A</company>
<value>Testvalue</value>
</job>
<job>
<company>B</company>
<value>Testvalue</value>
</job>
<job>
<company>C</company>
<value>Testvalue</value>
</job>
</jobs>
What I try'ed is the following:
If company is not within array, append it to an array & append the item to another array.
(: loop through job in jobs :)
for $ad in //jobs/job
(: firmenarray, "unique" ads :)
let $companys := ()
let $ads := ()
(: declare company of ad:)
let $company := $ad//company[1]
(: if ad/company not within companyarray > add & concat to ads :)
let $test := if(not(fn:index-of($companys, $company))) then(
(: add ad/company to companys :)
$companys = fn:insert-before($companys, 0, $company),
(: add jobs/job to ads :)
$ads = fn:insert-before($ads, 0, $ad)
)
return $ads
somehow It does not work and im kind of stuck finding out why...
The group by solution by Martin Honnen is the obvious and best one. But if you want to iteratively populate a sequence or array in XQuery, then it is important to understand that your approach cannot work in a functional language like XQuery, since all variables are immutable. Understanding the basics of functional programming is really important if you want to go beyond simple XPath and FLWOR expressions.
The "equivalent" to iteration in functional languages is recursion, so here is a recursive solution to your task using a useer-defined function:
declare function local:unique($companies, $unique) {
if(empty($companies)) then $unique
else if($companies[1]/company = $unique/company)
then local:unique(tail($companies), $unique)
else local:unique(tail($companies), ($unique, $companies[1]))
};
<jobs>{
local:unique(/jobs/job, ())
}</jobs>
This specific pattern of iterating through a sequence and aggregating a result is so common that it is even abstracted out into its own standard function, namely fn:fold-left($sequence, $start-value, $aggregation-function). With its help the solution becomes pretty short:
<jobs>{
fn:fold-left(/jobs/job, (), function($companies, $company) {
if($company/company = $companies/company) then $companies
else ($companies, $company)
})
}</jobs>
But since you compare each new entry with all previously found unique company entries, this soution is still rather inefficient. A well implemented group by will probably always beat it.
Text book grouping example:
<jobs>
{
for $job in jobs/job
group by $company := $job/company
return $job[1]
}
</jobs>
https://xqueryfiddle.liberty-development.net/b4GWVb
Related
How can i convert string into XPATH, below is the code
let $ti := "item/title"
let $tiValue := "Welcome to America"
return db:open('test')/*[ $tiValue = $ti]/base-uri()
Here is one way to solve it:
let $ti := "item/title"
let $tiValue := "Welcome to America"
let $input := db:open('test')
let $steps := tokenize($ti, '/')
let $process-step := function($input, $step) { $input/*[name() = $step] }
let $output := fold-left($input, $steps, $process-step)
let $test := $output[. = $tiValue]
return $test/base-uri()
The path string is split into single steps (item, title). With fold-left, all child nodes of the current input (initially db:open('test')) will be matched against the current step (initially, item). The result will be used as new input and matched against the next step (title), and so on. Finally, only those nodes with $tiValue as text value will be returned.
Your question is very unclear - the basic problem is that you've shown us some code that doesn't do what you want, and you're asking us to work out what you want by guessing what was going on in your head when you wrote the incorrect code.
I suspect -- I may be wrong -- that you were hoping this might somehow give you the result of
db:open('test')/*[item/title = $ti]/base-uri()
and presumably $ti might hold different path expressions on different occasions.
XQuery 3.0/3.1 doesn't have any standard way to evaluate an XPath expression supplied dynamically as a string (unless you count the rather devious approach of using fn:transform() to invoke an XSLT transformation that uses the xsl:evaluate instruction).
BaseX however has an query:eval() function that will do the job for you. See https://docs.basex.org/wiki/XQuery_Module
How to insert the node in XML.
let $a := <a><b>bbb</b></a>)
return
xdmp:node-insert-after(doc("/example.xml")/a/b, <c>ccc</c>);
Expected Output:
<a><c>ccc</c><b>bbb</b></a>
Please help to get the output.
You should be using xdmp:node-insert-before I believe in the following way:
xdmp:document-insert('/example.xml', <a><b>bbb</b></a>);
xdmp:node-insert-before(fn:doc('/example.xml')/a/b, <c>ccc</c>);
fn:doc('/example.xml');
(: returns <a><c>ccc</c><b>bbb</b></a> :)
Nodes are immutable, so in-memory mutation can only be done by creating a new copy.
The copy can use the unmodified contained nodes from the original:
declare function local:insert-after(
$prior as node(),
$inserted as node()+
) as element()
{
let $container := $prior/parent::element()
return element {fn:node-name($container)} {
$container/namespace::*,
$container/attribute(),
$prior/preceding-sibling::node(),
$prior,
$inserted,
$prior/following-sibling::node()
}
};
let $a := <a><b>bbb</b></a>
return local:insert-after($a//b, <c>ccc</c>)
Creating a copy in memory and then inserting the copy is faster than inserting and modifying a document in the database.
Depending on how many documents are inserted, the difference could be significant.
There are community libraries for copying with changes, but sometimes it's as easy to write a quick function (recursive where necessary).
You can use below code to insert the element into the XML:
xdmp:node-insert-child(fn:doc('directory URI'),element {fn:QName('http://yournamesapce','elementName') }{$elementValue})
Here we use fn:QName to remove addition of xmlns="" in added node.
Very new to XQuery and MarkLogic, what is the XQuery version of the following statement?
update all_the_records
set B_field = A_field
where B_field is null and A_field is not null
Something like this might get you started. But remember that you're working with trees, not tables. Things are generally more complicated because of that extra dimension.
for $doc in collection()/doc[not(b)][a]
let $a as element() := $doc/a
return xdmp:node-insert-child($doc, element b { $a/#*, $a/node() })
I have a XML file containing Employees Name and the Job done by them.
The structure of the XML file is -
<Employee>AAA#A#B#C#D</Employee>
<Employee>BBB#A#B#C#D</Employee>
<Employee>CCC#A#B#C#D</Employee>
<Employee>DDD#A#B#C#D</Employee>
There are thousands of records and I have to change structure to -
<Employee>
<Name>AAA</Name>
<Jobs>
<Job>A</Job>
<Job>B</Job>
<Job>C</Job>
<Job>D</Job>
</Jobs>
</Employee>
How to get this done using XQuery in BaseX ?
3 XQuery functions, substring-before, substring-after and tokenize are used to get
the required output.
substring-before is used to get the Name.
Similarly, the substring-after is used to get the Job portion.
Then the tokenize function, is used to split the Jobs.
let $data :=
<E>
<Employee>AAA#A#B#C#D</Employee>
<Employee>BBB#A#B#C#D</Employee>
<Employee>CCC#A#B#C#D</Employee>
<Employee>DDD#A#B#C#D</Employee>
</E>
for $x in $data/Employee
return
<Employee>
{<Name>{substring-before($x,"#")}</Name>}
{<Jobs>{
for $tag in tokenize(substring-after($x,"#"),'#')
return
<Job>{$tag}</Job>
}</Jobs>
}</Employee>
HTH...
Tokenizing the string is probably easier and faster. tokenize($string, $pattern) splits $string using the regular expression $pattern, head($seq) returns the first value of a sequence and tail($seq) all but the first. You could also use positional predicates of course, but these functions are easier to read.
for $employee in //Employee
let $tokens := tokenize($employee, '[##]')
return element Employee {
element Name { head($tokens) },
element Jobs {
for $job in tail($tokens)
return element Job { $job }
}
}
I would like the node replace to act on the $person variable. What do I need to change?
The following code should change the name of the people in the sequence to X.
declare function local:ChangeName($person)
{
xdmp:node-replace($person//Name/text, text { "X" } )
<p>{$person}</p>
};
let $b := <Person>
<Name>B</Name>
<IsAnnoying>No</IsAnnoying>
</Person>
let $j := <Person>
<Name>J</Name>
<IsAnnoying>No</IsAnnoying>
</Person>
let $people := ($b, $j)
return $people ! local:ChangeName(.)
xdmp:node-replace() only operates on persisted documents, not on in-memory documents.
Your local:ChangeName() function could construct the Person and Name elements but copying the IsAnnoying element, as in:
declare function local:ChangeName($person)
{
<p>
<Person>
<Name>X</Name>
{$person//IsAnnoying}
</Person>
</p>
};
For more complex transformations, consider a recursive typeswitch or XSLT transform.
As Erik noted above, you can't apply the xdmp update functions to in-memory nodes. If you have a strong need to to do in-memory node updates, Ryan Grimm's memupdate library can come in handy. It basically does the grunt work of the recursive traversal for you. But beware, in-memory updates do not scale to large documents because it requires making a copy to do an update.
https://github.com/marklogic/commons/tree/master/memupdate
I don't have a ML instance running, so I couldn't test it (maybe there are more issues), but it has to be
$person//Name/text()
instead of
$person//Name/text
The Xpath:
$person//Name/text
should be:
$person//Name/text()
The XPath is looking to replace the text node, which doesn't exist.
Also note that the results of xdmp:node-replace will not be visible in the same transaction. To see the results you need to start a new transaction.