29

I need to send a query to retrieve values that has a specific group of characters as following:

Lets say I am interested in 'XX' so it should search for any field that its value starts with 'XX' or has ' XX' (space XX). For example XXCDEF, PD XXRF and CMKJIEK XX are valid results.

I have following query that returns the correct results but I need to sort them in a way that it first return those with XX at the beginning then other results. As following:

XXABCD
XXPLER
XXRFKF
AB XXAB
CD XXCD
ZZ XXOI
POLO XX

Code

Criteria criteria = session.createCriteria(Name.class, "name")
                .add(Restrictions.disjunction()
                     .add(Restrictions.ilike("name.fname", fname + "%"))
                     .add(Restrictions.ilike("name.fname", "%" + " " + fname + "%"))
                    )
                .setProjection(Projections.property("name.fname").as("fname"));
        List<String> names = (List<String>) criteria.list();
Jack
  • 6,430
  • 27
  • 80
  • 151
  • 2
    I am not sure what order is from row 4 to row 7? – Jegg Jul 08 '15 at 01:28
  • @Jegg I should show the group of Strings that have XX at the beginning. The rest (row 4 to row7) can be those that have XX (space XX) in middle followed by those that have XX (space XX) at the end, another option is to apply alphabetical order one rows 4 to 7. – Jack Jul 08 '15 at 04:23
  • Why not sort them in java once you retrieve the results? – Sean F Jul 10 '15 at 03:33
  • @SeanF that would be a huge load on memory unless you would know an optimal solution – Jack Jul 10 '15 at 10:06

6 Answers6

32

With JPQL (HQL):

select fname from Name
where upper(fname) like :fnameStart or upper(fname) like :fnameMiddle
order by (case when upper(fname) like :fnameStart then 1 else 2 end), fname

query.setParameter("fnameStart", "XX%");
query.setParameter("fnameMiddle", "% XX%");

With Criteria

With Criteria it's much trickier. Firstly, you have to resort to native SQL in the order clause. Secondly, you have to bind the variable.

public class FirstNameOrder extends Order {
    public FirstNameOrder() {
        super("", true);
    }

    @Override
    public String toSqlString(Criteria criteria, CriteriaQuery criteriaQuery) throws HibernateException {
        return "case when upper(FIRST_NAME) like ? then 1 else 2 end";
    }
}

The case expression syntax and the upper function name should be changed in accordance with your database (and the column name if it's different, of course).

It is easy to add this to the Criteria, but there is no API to bind the parameter.

I tried to trick Hibernate by passing in an unused variable to the custom sql restriction so that it is effectively used for the variable in the order by clause:

Criteria criteria = session.createCriteria(Name.class, "name")
   .add(Restrictions.disjunction()
      .add(Restrictions.ilike("name.fname", fname + "%"))
      .add(Restrictions.ilike("name.fname", "%" + " " + fname + "%")))
   .setProjection(Projections.property("name.fname").as("fname"))
   .add(Restrictions.sqlRestriction("1 = 1", fname + "%", StringType.INSTANCE))
   .addOrder(new FirstNameOrder())
   .addOrder(Order.asc("fname"));

and it works fine.

Obviously, this solution is not recommended and I suggest using JPQL for this query.

Dragan Bozanovic
  • 23,102
  • 5
  • 43
  • 110
  • As OP using Hibernate HQL is more appropriate. – Roman C Jul 10 '15 at 12:18
  • True, but the query should be the same. – Dragan Bozanovic Jul 10 '15 at 12:30
  • @DraganBozanovic thanks for your brilliant answer, I had a look at this link http://www.mysqltutorial.org/mysql-case-statement/ to better understand your query but have not got much. Would you tell me to which part of the query number 1 and 2 are referring to? – Daniel Newtown Jul 16 '15 at 22:07
  • You're welcome. They don't refer to any part of the query, they are just constant expressions by which the database will sort the rows. Since the sort is ascending (by default), and 1 is lower than 2, rows which evaluate the case expression to 1 come before those that evaluate it to 2. Then, with the second sort criterion, we sort the rows with equal first criterion value alphabetically by `fname`. – Dragan Bozanovic Jul 16 '15 at 22:36
  • Very nice trick, saved me really. I put the HACK for the parameter directly in the Order class; it works because the WHERE clause has already been processed at this moment, so you don't end-up with the `AND 1=1` at all in the final query, but the parameter is still added near the end of the WHERE clause. Could still fail if there are parameters in the GROUP BY. – Guillaume F. Nov 16 '20 at 12:00
3

Hibernate supports Order: http://docs.jboss.org/hibernate/orm/4.2/devguide/en-US/html/ch11.html#ql-ordering Because of the special criteria, I think you have to custom the Order in Hibernate. This link may help: http://blog.tremend.ro/2008/06/10/how-to-order-by-a-custom-sql-formulaexpression-when-using-hibernate-criteria-api/

Kenny Tai Huynh
  • 1,464
  • 2
  • 11
  • 23
2

Run two selects, one filtered for all the strings starting with 'XX', the second filtered for the others.

wero
  • 32,544
  • 3
  • 59
  • 84
1

You can use Predicates in criteria... something like this:

public List<Name> findByParameter(String key, String value, String orderKey)

            CriteriaBuilder builder = this.entityManager.getCriteriaBuilder();
            CriteriaQuery<Name> criteria = builder.createQuery(this.getClazz());
            Root<Name> root = criteria.from(Name.getClass());
            criteria.select(root);
            List<Predicate> predicates = new ArrayList<Predicate>();
            predicates.add(builder.equal(root.get(key), value));
            criteria.where(predicates.toArray(new Predicate[predicates.size()]));
            if (orderKey!=null  && !orderKey.isEmpty()) {
                criteria.orderBy(builder.asc(root.get(orderKey)));
            }
            result = this.entityManager.createQuery(criteria).getResultList();

        return result;
}
0

Stupid but it may work for your case.

Since you got your correct result you can just reconstruct your results as follows:

  1. pick up all results starting with XX you put them into a list L1 and do the normal sort like Collections.sort(L1);
  2. for all other results, do the same like Collections.sort(L2)as list of L2
  3. At last , put them together

    List newList = new ArrayList(L1);

    newList.addAll(L2);

Please note. Collections.sort are following the natural ordering of its elements.

Jegg
  • 549
  • 3
  • 11
  • 1
    Another option is to retrieve separate lists by hibernate and merge them together but I am wondering if it is possible to retrieve them all by a single query. – Jack Jul 08 '15 at 04:26
0

If you don't want to sort the result in memory,you can modify your criteria,I'll show you the SQL

select * from table where fname like 'XX%' order by fname
union all
select * from table where fname like '% XX%' order by fname
union all
select * from table where fname like '% XX' order by fname

the result will be your order and alphabetical and then apply your filter.

xy1m
  • 61
  • 3