XQuery 3.0 equivalent group by in xquery 1.0 version - xquery

I have the following code, and the saxon jar file I have cannot read xquery 3.0.
for $item in doc("order.xml")//item
group by $d := $item/#dept
order by $d
return
<department totalQuantity="{sum($item/#quantity)}"
code="{$d}"
numItems="{count($item)}"/>
The output displays the department, totalQuantity, and the number of items.
It's supposed to be grouped by the code, the code above doesn't compile.
I was hoping theres a way that would work with xquery 1.0

Typically the XQuery 1.0 equivalent of a XQuery 3.0 "group by"-enhanced FLWOR expression relies on the fn:distinct-values() function to find the distinct keys for grouping your items together. Having found the keys, you can simply iterate through the keys with an XQuery 1.0 FLWOR expression and select the items with the matching key. Here is the XQuery 1.0 equivalent of your query:
let $items := doc("order.xml")//item
let $depts := distinct-values($items/#dept)
for $dept in $depts
let $dept-items := $items[#dept eq $dept]
order by $dept
return
<department totalQuantity="{sum($dept-items/#quantity)}"
code="{$dept}"
numItems="{count($dept-items)}"/>

Related

How to sort by dynamically with ascending or descending in Marklogic?

let $sortelement := 'Salary'
let $sortby := 'ascending'
for $doc in collection('employee')
order by $doc/*[local-name() eq $sortelement] $sortby
return $doc
This code throws and error, what is the correct way to do this?
If you are just looking to build the order by dynamically within a FLWOR statement, you can't. As Michael Kay points out in the comments, you could use a conditional statement and decide whether or not to reverse() the ascending (default) sorted sequence.
let $sortelement := 'Salary'
let $sortby := 'ascending'
let $results :=
for $doc in collection('employee')
order by $doc/*[local-name() eq $sortelement]
return $doc
return
if ($sortby eq 'descending')
then reverse($results)
else $results
Depending upon how many documents are in your collection, retrieving every document and sorting them won't scale. It can take a long time, and can exceed memory limits for expanded tree cache.
If you have indexes on those elements, then you can dynamically build a cts:index-order() and specify as the third parameter for cts:search() in order to get them returned in the specified order:
let $sortelement := 'Salary'
let $sortby := 'ascending'
return
cts:search(doc(),
cts:collection-query("employee"),
cts:index-order(cts:element-reference(xs:QName($sortelement)), $sortby)
)

Getting ORA-22922 (nonexistent LOB value) or no result at all with wm_concat()

(Using Oracle 11.2)
I have a rather complicated SQL with something like
wm_concat( distinct abc )
that is expected to return some varchar2(4000) compatible result.
It causes ORA-00932: inconsistent datatypes in my select used in some coalesce( some_varchar_col, wm_concat( ... ) ).
So I tried casting it via two different methods:
dbms_lob.substr( ..., 4000 ) -- L) tried even with 3000 in case of "unicode byte blow-up"
cast( ... as varchar2(4000)) -- C) tried even with 3000 in case of "unicode byte blow-up"
(The are used in a view, but playing around with it suggests, it is not related to the views)
Depending on the column and other operators I either get N) no result or O) ORA-22922:
select * from view_with_above_included where rownum <= 100
N) My Eclipse Data Explorer JDBC connection returns without any result (no columns without results, no (0 rows effected), only the query time statistics). (It could be an internal exception not treated as such?)
O)
ORA-22922: nonexistent LOB value
ORA-06512: in "SYS.DBMS_LOB", line 1092
ORA-06512: in line 1
Strangely the following test queries work:
-- rownum <= 100 would already cause the above problems
select * from view_with_above_included where rownum <= 10
or
select * from view_with_above_included
but looking at the actual aggregated data does not show aggregated data that would exceed 1000 characters in length.
Luckily, it works with the listagg( ... ) function provided since 11.2 (we are already running on), so we did not have to investigate further:
listagg( abc, ',' ) within group ( order by abc )
(Where wm_concat(...) is, as one should know, some internal and officially unsupported function.)
a rather nice solution (because it is not so bloated) to implement the distinct functionality is via self-referencing regexp functionality which should work in many cases:
regexp_replace(
listagg( abc, ',' ) within group ( order by abc )
, '(^|,)(.+)(,\2)+', '\1\2' )
(Maybe/Hopefully we will see some working listagg( distinct abc ) functionality in the future, which would be very neat and cool like the wm_concat syntax. E.g. this is no problem since a long time with Postgres' string_agg( distinct abc )1 )
-- 1: postgres sql example:
select string_agg( distinct x, ',' ) from unnest('{a,b,a}'::text[]) as x`
If the list exceeds 4000 characters, one cannot use listagg anymore (ORA-22922 again).
But luckily we can use the xmlagg function here (as mentioned here).
If you want to realize a distinct on a 4000-chars-truncated result here, you could outcomment the (1)-marked lines.
-- in smallercase everything that could/should be special for your query
-- comment in (1) to realize a distinct on a 4000 chars truncated result
WITH cfg AS (
SELECT
',' AS list_delim,
'([^,]+)(,\1)*(,|$)' AS list_dist_match, -- regexp match for distinct functionality
'\1\3' AS LIST_DIST_REPL -- regexp replace for distinct functionality
FROM DUAL
)
SELECT
--REGEXP_REPLACE( DBMS_LOB.SUBSTR( -- (1)
RTRIM( XMLAGG( XMLELEMENT( E, mycol, listdelim ).EXTRACT('//text()')
ORDER BY mycol ).GetClobVal(), LIST_DELIM )
--, 4000 ), LIST_DIST_MATCH, LIST_DIST_REPL ) -- (1)
AS mylist
FROM mytab, CFG

XQuery get distinct values after tokenize?

I want to get the distinct countries of all mountains, but sometimes a mountain will be in more than one country as indicated by having multiple country codes in a string like this:
<mountain id="mount-Kangchendzonga" country="NEP IND"></mountain>
I can get all the distinct strings associated with a country using
let $mts := doc("mondial.xml")/mondial//mountain
let $countries := distinct-values(data($mts/#country))
But this isn't quite correct because if I had one mountain with country="NEP IND" and another with country="NEP" these would be recognized as distinct.
let $countries := distinct-values(concat(' ', data($mts/#country)))
let $countries := distinct-values(tokenize(data($mts/#country), "\s+"))
Is there a way I could first split up a string of a country by white space, and then get the distinct values of these? I have tried using distinct-values on concatenated and tokenized data like I showed above, but both result in errors with the compiler.
This is one possible way to combine tokenize() and distinct-values() to get the distinct country names :
let $all-countries :=
for $c in $mts/#country
return tokenize($c, "\s+")
let $distinct-countries := distinct-values($all-countries)
xpathtester.com demo
Or in XQuery 3.1, as suggested in comment below :
($mts/#country ! tokenize(., '\s+')) => distinct-values()

Is it possible to execute immediate like this

This code works:
uno:=1;
dos :='insert into TRABAJADOR('||inValuesToInsert||') values('||inValuestoPas||')';
execute IMMEDIATE dos using uno,addColN, addColS(8),addColD,addColS(100),
addColS(60),addFKColN('DEPT'),addFKColS('PAIS'),addFKColS('CATEGORIA') ;
But I need to do the execute immediate statement like this (the variable tres contains the data below, sorry for the syntax, I know it's not well done):
tres:= 'dos using uno,addColN, addColS(8),addColD,addColS(100),
addColS(60),addFKColN('DEPT'),addFKColS('PAIS'),addFKColS('CATEGORIA')'
uno:=1;
dos :='insert into TRABAJADOR('||inValuesToInsert||') values('||inValuestoPas||')';
execute IMMEDIATE dos using tres;
Is this possible? Please give to me another suggestion in case of do not be possible. Thanks
First of all, you need to understand the difference between just random part of string of SQL query (for example, ' from table1 t1 join table2 t2 ') and parameter. Parameter is a certain placeholder, which always means value of something (field, variable, etc.), that will be provided later, in runtime. In USING clause of EXECUTE IMMEDIATE statement you can use only parameters. If you want to construct your query in runtime from parts, you can do it, but you have to process names of fields and parameters by different methods. Example:
declare
field_list varchar2(100) := 'my_field1, my_field2, my_field3';
table_name varchar2(30) := 'my_table';
v1 number := 1;
v2 number := 2;
v3 number := 3;
begin
execute immediate 'insert into ' || table_name || '(' || field_list ||
') values (:p1, :p2, :p3)' using v1, v2, v3;
end;
/
Here :P1, :P2 and :P3 is parameters, that will be replaced by their values from variables v1, v2 and v3. table_name and field_list from the point of view of a server is NOT parameters, it is static names of database objects.

Auto increment with XQuery Update?

Does XQuery Update support auto increment attributes, just like auto increment fields in SQL?
I'm using BaseX as my database.
Given an answer from Christian GrĂ¼n on the BaseX mailing list, this is doable when the node one is adding is defined in the XQuery Update statement, and hence can be enhanced using an {enclosed expression} before inserting it:
You might specify the attribute counter within your XML file/database
and increment it every time when you insert an element. A simple
example:
input.xml:
<root count="0"/>
insert.xq:
let $root := doc('input.xml')/root
let $count := $root/#count
return (
insert node <node id='{ $count }'/> into $root,
replace value of node $count with $count + 1
)
I've not been able to achieve the same with an external org.w3c.dom.Document created in Java, and added to the XML database using XQJ and declare variable $doc external. Here, one might be tempted to update the "auto-increment" data after adding the document. However, the processing model defines that changes are not visible until all the commands have been queued (the Pending Update List). Hence a new document or node is, by definition, simply not visible for updates in the same FLWOR expression. So:
db:add('db1', '<counters comment-id="0"/>', 'counters')
...followed by repetitive executions of the following, will NOT work:
let $doc := document{ <note id=""><text>Hello world</text></note> }
let $count := /counters/#comment-id
return (
db:add('db1', $doc, 'dummy'),
replace value of node $count with $count + 1
for $note in /note[#id='']
return replace value of node $note/#id with $count (: WRONG! :)
)
Above, the last inserted document will always have <note id="">, and will not be updated until the next document is added. (Also, it would not work when somehow multiple documents with <note id=""> would exist.)
Though in the example above one could successfully delete the for $note in ... part and use:
let $doc := document{ <note id="{ $count }"><text>Hello world</text></note> }
...I had no luck setting <note id="{ $count }"> in the Document in the Java code, as that enclosed expression would not be replaced then.
Finally, some state for similar solutions:
[...] perform badly as it will lock out concurrent updates. You should consider using xdmp:random() to generate a 64 bit random number for your unique identifier.
In our case, the id would also be used in URLs; not too nice then.
See also XRX/Autoincrement File ID and Using XQuery to return Document IDs.
This would depend on the implementation of the underlying data-store, because the auto-increment attribute is on the column definition in relational databases.
Probably, "yes".

Resources