order the xquery result dynamically - xpath

I want to use the ascending or descending option dynamically based on xpath value.
let $order := "ascending"
for $aRslt in ("1","2","3","4")
order by ($aRslt)
if($order eq "ascending") then ascending else descending
return $aRslt
Using this throwing Error.
We can have the if condition for the whole "for" statement. But when we have more conditions in where; order by; and lot of statements in return, then it looks code duplicating just for the ascending or descending.
Is there any option to use without using condition for the whole "for".

Whenever I've had to order dynamically this is whats worked best for me.
It takes advantage of multiple order spec. (http://www.w3.org/TR/xquery/#id-orderby-return) This is a bit different because of order Modifier being a token and not a variable or expression so it can be hard to work with.
And that's why you have to have the two if statements and the empty else statements.
let $order := "ascending"
for $aRslt in ("1","2","3","4")
order by
if($order eq "ascending") then $aRslt else() ascending,
if($order eq "descending") then $aRslt else () descending
return $aRslt

Related

eXist-db collection sort

Following on from this question about navigating collections using pos:
In eXist 4.7 I have a collection in myapp/data/ which contains thousands of TEI XML documents. I use the following solution from Martin Honnen to get the document before and after a certain document
let $data := myapp/data
let $examples := $data/tei:TEI[#type="example"]
for $example at $pos in $examples
where $example/#xml:id = 'TC0005'
return (
$examples[$pos - 1],
$example
$examples[$pos + 1]
)
With this I would have expected $examples[$pos - 1] to produce document 'TC0004' and $examples[$pos + 1] to produce 'TC0006' (based on the sort order seen in eXide collection navigation view for example). They do not, producing the inverse instead.
Honnen and Michael Kay responded that
ordering of documents within a collection is very much processor-dependent
Applying an order by $example/#xml:id ascending clause did not change the result for the better.
So, the question is how can I impose an alpha-numeric order on $data?
Many thanks.
It seems at the XQuery level you can change let $examples := $data/tei:TEI[#type="example"] to
let $examples := sort($data/tei:TEI[#type="example"], (), function($e) { $e/#xml:id })
(assuming the XQuery/XPath 3.1 higher-order sort function is available) or to
let $examples := for $e in $data/tei:TEI[#type="example"] order by $e/#xml:id return $e
using the order by clause.
I don't know whether exist-db has some way to impose an order during the creation or during the selection of a collection.
Based on experience with older versions of eXist, the $pos value while going through a loop is not the sorted position order. It is the position while going through.
What you first want to do is create an ordered list, then get the three items from the list you're looking for.
let $data := myapp/data[tei:TEI/#type eq 'example']
let $examples := for $e in $data order by $e/#xml:id ascending return $e
let $pos := index-of($examples/#xml:id, 'TC0005')
return if (count($pos) eq 1) then (
if ($pos gt 1) then $examples[$pos - 1] else (),
$examples[$pos]
$examples[$pos + 1]
) else ()
A potential problem with this approach is that you'll have to sort all items every time. Creating a sorted cached list may alleviate this problem and would also allow for a much more efficient query, where you can use preceding-sibling and following-sibling from the query result.
Another potential solution, if the naming convention for the IDs is consistent, would be to query the before and after IDs.
The check to see if there is one item in $pos is to prevent cases where #xml:id is not unique (yes, that would be against the spec, but it happens in real world data) or no item exists. Keep in mind that index-of returns an array of indexes - 0 or more.

xquery, preserve sort order

Is there any way to preserve sort order in xquery? My problem is that the data has to get passed to the MVC framework's get-response() function on the return, so I think it's automatically reverting to document order. I thought that doing the sort right in the first paramter of the subsequence() function would capture the first 'n' items after they are sorted, but it does not. I also tried having the $search-results parameter sorted before the call to subsequence(), but that did not work either. See the following code:
let $data :=
<figures count="{$count}"
mediatypes="{$mtypes}"
start="{$start}"
end="{$start+$myns:image-paging-default}"
page="{$page}"
increment="{$myns:image-paging-default}"
total-pages="{
if ($count lt $myns:image-paging-default) then
1
else
ceiling(($count + 1) div $myns:image-paging-default)
}"
{
subsequence(
( for $item in ($search-results)
order by $item//figure/#ftype descending
return $item),
$start,
$myns:image-paging-default)
}
</figures>
let $sidebar := xdmp:get-server-field('imagefacets')
return utils:get-response($req, ($data,$sidebar) )

Linq statement fails with dynamic where clause

I want to retrieve commissions with a certain order number.
This works:
var expression = from commission in db.Auftraege
where commission.Auftragsnummer == orderNbr
select new Commission() { EF_Commission = (commission as Auftrag) };
return expression.ToList();
However, if i transform this to use a dynamic where clause (because i want to apply some more filters), the where-clause does not seem to be applied. Instead, all commissions are returned instead of only those with a specific number:
//base query
var expression = from commission in db.Auftraege select new Commission() { EF_Commission = (commission as Auftrag) };
//now add a where clause if the input parameter was specified
if (orderNbr >= 0)
expression.Where(commission => commission.EF_Commission.Auftragsnummer == orderNbr);
return expression.ToList();
I have looked at a dozen examples but they all seem to do it this way. Does anybody have an idea why the second query ignores the where clause?
You need to assign the interim expression to something (perhaps to itself). expression.Where() does not alter the existing query - it returns a new one.
So:
expression = expression.Where(...);

Reversing IQueryable based on passed property for sorting logic

I am implementing sort based on parameter passed to ascending or descending OrderBy method
else if (showGrid.Sortdir == "DESC")
{
alerts = DB.Incidents.OfType<Alert>().Where(
a =>
a.IncidentStatusID == (int)AlertStatusType.New ||
a.IncidentStatusID == (int)AlertStatusType.Assigned ||
a.IncidentStatusID == (int)AlertStatusType.Watching)
.OrderByDescending(a => showGrid.Sort);
}
else
{
alerts = DB.Incidents.OfType<Alert>().Where(
a =>
a.IncidentStatusID == (int)AlertStatusType.New ||
a.IncidentStatusID == (int)AlertStatusType.Assigned ||
a.IncidentStatusID == (int)AlertStatusType.Watching)
.OrderBy(a => showGrid.Sort);
}
In case of ascending order sorting it works fine but for descending order sorting doesn't work. I debugged the code and I found that list is not revered its same as ascending order. Please help me
Ok. I've written a small test. It is funny, but your code can actually compile and work, but very differently from what you expect :)
Obviously showGrid is not of type Alert, it is an instance of some other class, that incidentally have the same propery as Alert, called Sort.
First I was confused, because expected this code to fail to compile.
// The signature of OrderBy
public static IOrderedQueryable<TSource> OrderBy<TSource, TKey>(this IQueryable<TSource> source, Expression<Func<TSource, TKey>> keySelector)
// In your case it will result in
public static IOrderedQueryable<Alert> OrderBy<Alert, string>(this IQueryable<Alert> source, Expression<Func<Alert, string>> keySelector)
//when you call it like you do
DB.Incidents.OfType<Alert>().OrderByDescending(a => showGrid.Sort);
// You supply a property from object of type different from your entity.
// This is incorrect usage, the only object you can use here is the
// "a" argument. Like this:
DB.Incidents.OfType<Alert>().OrderByDescending(a => a.Sort);
// Because anything else does not make any sense to entity provider.
So your order by simply does not work.
As far as I understood, what you want is to perform sorting based on selection in UI. This is not easily achieved in strongly-typed LINQ. Because as I showed above, you send a property, not a value to the OrderBy. It does not care about the value inside the prop. So there are several solutions to the problem:
Write a big switch, that will check every possible Sort value, and will append appropriate 'OrderBy(a => a.YouPropToSort)' to the query. This is straitforward, and you should begin with this. Of course this is a static way, and will require to change code everytime you want new columns to be added for sorting.
Create argument for your OrderBy using 'LINQ Expression Trees'. For you case it should not be very hard to do. Look for the term, you will find a lot of examples.
Try to use Dynamic LINQ. I did not not use it myself, just looked at the docs. This seems to be an extension to the normal LINQ which allows you to write parts of queries as strings, to overcome limitations like the current one with dynamic sorting.
Here's my solution to sorting based on user selections:
Create your base query
var query = DB.Incidents.OfType<Alert>.Where(
a =>
a.IncidentStatusID == (int)AlertStatusType.New ||
a.IncidentStatusID == (int)AlertStatusType.Assigned ||
a.IncidentStatusID == (int)AlertStatusType.Watching);
and then apply your sort using a case statement
bool desc = showGrid.SortDir = "DESC";
switch(showGrid.Sort)
{
case "col1":
query = desc ? query.OrderByDescending( a => a.Col1 ) : query.OrderBy( a => a.Col1 );
break;
case "col2":
query = desc ? query.OrderByDescending( a => a.Col2 ) : query.OrderBy( a => a.Col2 );
break;
...
}
var results = query.ToList();

Use LINQ for arbitrary sorting

Let's say we have an entity that has attributes att1 and att2, where att1 can have values a,b,c and att2 can have values 1,2,3. Is it possible to use LINQ so that we can sort items in the collection by applying arbitrary sorting rule without implementing IComparable. I am facing an issue were business requires that on some screens items in the collection be sorted one way and in other screens some other way. For example rule can state that items need to be sorted so that "b" is listed first, then "a", then "c" and within each group, "3" is first, then "1" then "2".
Sure. You can use OrderBy with a predicate that returns more or less whatever kind of arbitrary "sort order" you please. For example:
objectsWithAttributes.OrderBy(x =>
{
// implement your "rules" here -- anything goes as long as you return
// something that implements IComparable in the end. this code will sort
// the enumerable in the order 'a', 'c', 'b'
if (x.Attribute== 'a')
return 0;
else if (x.Attribute== 'c')
return 1;
else if (x.Attribute== 'b')
return 2;
}).ThenBy(x =>
{
// implement another rule here?
});

Resources