Sorting
Note
This article focuses only on querying side of the sorting. If you are interested in reading how to create indexes and change default sorting behavior go here.
Basics
By default, all index values are sorted lexicographically, this can be changed in index definition, but sorting is not applied until you request it by using appropriate methods, so following queries will not return sorted results, even if we define in our index appropriate sorting option:
QProduct p = QProduct.product;
List<Product> results = session.query(Product.class, Products_ByUnitsInStock.class)
.where(p.unitsInStock.gt(10)).toList();
QProduct p = QProduct.product;
List<Product> results = session.advanced().documentQuery(Product.class, Products_ByUnitsInStock.class)
.whereGreaterThan(p.unitsInStock, 10).toList();
QueryResult result = store.getDatabaseCommands()
.query("Products/ByUnitsInStock", new IndexQuery("UnitsInStock_Range:{Ix10 TO NULL}"));
public static class Products_ByUnitsInStock extends AbstractIndexCreationTask {
public Products_ByUnitsInStock() {
map = " from product in docs.Products " +
" select new " +
" { " +
" product.UnitsInStock " +
" };";
QProduct p = QProduct.product;
sort(p.unitsInStock, SortOptions.INT);
}
}
So, to start sorting, we need to request to order by some specified index field. In our case we will order by UnitsInStock
in descending order:
QProduct p = QProduct.product;
List<Product> results = session.query(Product.class, Products_ByUnitsInStock.class)
.where(p.unitsInStock.gt(10)).orderBy(p.unitsInStock.desc()).toList();
QProduct p = QProduct.product;
List<Product> results = session.advanced().documentQuery(Product.class, Products_ByUnitsInStock.class)
.whereGreaterThan(p.unitsInStock, 10).orderByDescending(p.unitsInStock).toList();
IndexQuery query = new IndexQuery();
query.setQuery("UnitsInStock_Range:{Ix10 TO NULL}");
query.setSortedFields(new SortedField[] {new SortedField("-UnitsInStock_Range")});
QueryResult result = store.getDatabaseCommands().query("Products/ByUnitsInStock", query);
public static class Products_ByUnitsInStock extends AbstractIndexCreationTask {
public Products_ByUnitsInStock() {
map = " from product in docs.Products " +
" select new " +
" { " +
" product.UnitsInStock " +
" };";
QProduct p = QProduct.product;
sort(p.unitsInStock, SortOptions.INT);
}
}
Convention
You probably noticed that we used -
in a name of a field passed to SortedField
that was used in commands, which means that we want to sort our results in a descending order. Using +
symbol or no prefix means that ascending sorting is requested.
Of course you can change order and field name in SortedField
later since all properties have public access.
Ordering by score
When query is issued, each index entry is scored by Lucene (you can read more about Lucene scoring here) and this value is available in metadata information of a document under Temp-Index-Score
(the higher the value, the better the match). To order by this value you can use orderByScore
or orderByScoreDescending
methods:
QProduct p = QProduct.product;
List<Product> results = session.query(Product.class, Products_ByUnitsInStock.class)
.where(p.unitsInStock.gt(10)).orderByScore().toList();
QProduct p = QProduct.product;
List<Product> results = session.advanced().documentQuery(Product.class, Products_ByUnitsInStock.class)
.whereGreaterThan(p.unitsInStock, 10).orderByScore().toList();
IndexQuery query = new IndexQuery();
query.setQuery("UnitsInStock_Range:{Ix10 TO NULL}");
query.setSortedFields(new SortedField[] {new SortedField(Constants.TEMPORARY_SCORE_VALUE)});
QueryResult result = store.getDatabaseCommands().query("Products/ByUnitsInStock", query);
public static class Products_ByUnitsInStock extends AbstractIndexCreationTask {
public Products_ByUnitsInStock() {
map = " from product in docs.Products " +
" select new " +
" { " +
" product.UnitsInStock " +
" };";
QProduct p = QProduct.product;
sort(p.unitsInStock, SortOptions.INT);
}
}
Random ordering
If you want to randomize the order of your results each time the query is executed you can use randomOrdering
method (API reference here):
QProduct p = QProduct.product;
List<Product> results = session.query(Product.class, Products_ByUnitsInStock.class)
.customize(new DocumentQueryCustomizationFactory().randomOrdering()).where(p.unitsInStock.gt(10))
.toList();
QProduct p = QProduct.product;
List<Product> results = session.advanced().documentQuery(Product.class, Products_ByUnitsInStock.class)
.randomOrdering().whereGreaterThan(p.unitsInStock, 10).toList();
IndexQuery query = new IndexQuery();
query.setQuery("UnitsInStock_Range:{Ix10 TO NULL}");
query.setSortedFields(
new SortedField[] {new SortedField(Constants.RANDOM_FIELD_NAME + ";" + UUID.randomUUID())});
QueryResult result = store.getDatabaseCommands().query("Products/ByUnitsInStock", query);
public static class Products_ByUnitsInStock extends AbstractIndexCreationTask {
public Products_ByUnitsInStock() {
map = " from product in docs.Products " +
" select new " +
" { " +
" product.UnitsInStock " +
" };";
QProduct p = QProduct.product;
sort(p.unitsInStock, SortOptions.INT);
}
}
Ordering when field is Analyzed
When sorting must be done on field that is marked as Analyzed then due to Lucene limitations sorting on such a field is not supported. To overcome this, the solution is to create another field that is not marked as Analyzed and sort by it.
QSorting_Products_ByName_Search_Result p = QSorting_Products_ByName_Search_Result.result;
List<Products_ByName_Search.Result> results = session
.query(Products_ByName_Search.Result.class, Products_ByName_Search.class)
.search(p.name, "Louisiana").orderBy(p.nameForSorting.desc()).toList();
List<Product> results = session.advanced().documentQuery(Product.class, Products_ByName_Search.class)
.search("Name", "Louisiana").orderByDescending("NameForSorting").toList();
IndexQuery indexQuery = new IndexQuery();
indexQuery.setQuery("Name:Louisiana*");
indexQuery.setSortedFields(new SortedField[] {new SortedField("-NameForSorting")});
store.getDatabaseCommands().query("Products/ByName/Search", indexQuery);
public static class Products_ByName_Search extends AbstractIndexCreationTask {
@QueryEntity public static class Result {
private String name;
private String nameForSorting;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getNameForSorting() {
return nameForSorting;
}
public void setNameForSorting(String nameForSorting) {
this.nameForSorting = nameForSorting;
}
}
public Products_ByName_Search() {
QProduct p = QProduct.product;
map = "from product in docs.products select new { Name = product.Name, NameForSorting = product.Name } ";
indexes.put(p.name, FieldIndexing.ANALYZED);
}
}
Custom sorting
If you want to sort using your custom algorithm you need create your own sorter that inherits from IndexEntriesToComparablesGenerator
and deploy it to plugins folder on the server.
public abstract class IndexEntriesToComparablesGenerator
{
protected IndexQuery IndexQuery;
protected IndexEntriesToComparablesGenerator(IndexQuery indexQuery)
{
IndexQuery = indexQuery;
}
public abstract IComparable Generate(IndexReader reader, int doc);
}
For example, if we want to sort by specified number of characters from an end, and we want to have an ability to specify number of characters explicitly, we can implement our sorter like this:
public class SortByNumberOfCharactersFromEnd : IndexEntriesToComparablesGenerator
{
private readonly int len;
public SortByNumberOfCharactersFromEnd(IndexQuery indexQuery)
: base(indexQuery)
{
len = IndexQuery.TransformerParameters["len"].Value<int>(); // using transformer parameters to pass the length explicitly
}
public override IComparable Generate(IndexReader reader, int doc)
{
var document = reader.Document(doc);
var name = document.GetField("FirstName").StringValue; // this field is stored in index
return name.Substring(name.Length - len, len);
}
}
And it can be used like this:
List<Employee> results = session.query(Employee.class, Employee_ByFirstName.class).customize(
new DocumentQueryCustomizationFactory()
.customSortUsing("AssemblyQualifiedName", true)).addTransformerParameter("len", 1).toList();
List<Employee> results = session.advanced().documentQuery(Employee.class, Employee_ByFirstName.class)
.customSortUsing("SortByNumberOfCharactersFromEnd", true)
.setTransformerParameters(ImmutableMap.of("len", RavenJToken.fromObject(1))).toList();
}
gion sorting_5_5
xQuery indexQuery = new IndexQuery();
edField sortedField = new SortedField(Constants.CUSTOM_SORT_FIELD_NAME +
"-" + // "-" - descending, "" - ascending
";" +
"SorterFullName");
xQuery.setSortedFields(new SortedField[] {sortedField});
xQuery.setTransformerParameters(ImmutableMap.of("len", RavenJToken.fromObject(1)));
e.getDatabaseCommands().query("Employees/ByFirstName", indexQuery);
IndexQuery indexQuery = new IndexQuery();
SortedField sortedField = new SortedField(Constants.CUSTOM_SORT_FIELD_NAME +
"-" + // "-" - descending, "" - ascending
";" +
"SorterFullName");
indexQuery.setSortedFields(new SortedField[] {sortedField});
indexQuery.setTransformerParameters(ImmutableMap.of("len", RavenJToken.fromObject(1)));
store.getDatabaseCommands().query("Employees/ByFirstName", indexQuery);
public static class Employee_ByFirstName extends AbstractIndexCreationTask {
public Employee_ByFirstName() {
QEmployee e = QEmployee.employee;
map = "from employee in docs.employees select new { employee.FirstName } ";
store(e.firstName, FieldStorage.YES);
}
}