Hibernate Criteria API: get n random rows - java

I can't figure out how to fetch n random rows from a criteria instance:
Criteria criteria = session.createCriteria(Table.class);
criteria.add(Restrictions.eq('fieldVariable', anyValue));
...
Then what? I can't find any doc with Criteria API
Does it mean I should use HQL instead?
Thanx!
EDIT: I get the number of rows by:
int max = criteria.setProjecxtion(Projections.rowCount()).uniqueResult();
How do I fetch n random rows with indexes between 0 and max?
Thx again!

Actually it is possible with Criteria and a little bit of tweaking. Here is how:
Criteria criteria = session.createCriteria(Table.class);
criteria.add(Restrictions.eq("fieldVariable", anyValue));
criteria.add(Restrictions.sqlRestriction("1=1 order by rand()"));
criteria.setMaxResults(5);
return criteria.list();
any Restrictions.sqlRestriction will add keyword 'and'; so to nullify its effect,
we shall add a dummy condition and inject our rand() function.

First of all, be aware that there is no standard way to do this in SQL, each database engine uses its own proprietary syntax1. With MySQL, the SQL statement to get 5 random rows would be:
SELECT column FROM table
ORDER BY RAND()
LIMIT 5
And you could write this query in HQL because the order by clause in HQL is passed through to the database so you can use any function.
String query = "SELECT e.attribute FROM MyEntity e ORDER BY RAND()";
Query q = em.createQuery(query);
q.setMaxResults(5);
However, unlike HQL, the Criteria API currently doesn't support ORDER BY Native SQL (see HHH-2381) and in the current state, you would have to subclass the Order class to implement this feature. This is doable, refer to the Jira issue, but not available out of the box.
So, if really you need this query, my recommendation would be to use HQL. Just keep in mind it won't be portable.
1 Other readers might want to check the post SQL to Select a random row from a database table to see how to implement this with MySQL, PostgreSQL, Microsoft SQL Server, IBM DB2 and Oracle.

The Criteria API doesn't offer facilities for this. In MySQL however, you can use ORDER BY RAND() LIMIT n for this where n represents the number of random rows you'd like to fetch.
SELECT col1, col2, col3 FROM tbl ORDER BY RAND() LIMIT :n
You indeed need to execute it as HQL.

You can not fetch random rows efficiently, sorry. Hibernate can only do what SQL does, and random row fetch simply is not part of any standard SQL implementation I know - actually it is to my knowledge not part of ANY SQL that I am aware of (anyone please enlight me).
And as Hibernate is an O/R mapper, and not a wonder machine, it can only do what the underlying database supports.
If you have a known filed with ascending numbers and know start and end, you can generate a random number on the computer and ask for that row.

The answer by #PSV Bhat is difficult if you are dynamically generating your Criteria. Here is a solution that extends hibernate Order class:
import org.hibernate.Criteria;
import org.hibernate.criterion.Order;
private void addOrderByToCriteria(Criteria criteria) {
criteria.addOrder(Order.asc("foobar"));
criteria.addOrder(ORDER_RANDOM);
}
private static final OrderRandom ORDER_RANDOM = new OrderRandom();
private static class OrderRandom extends Order {
public OrderRandom() {
super("", false);
}
#Override
public String toSqlString(Criteria criteria, CriteriaQuery criteriaQuery) {
return "RANDOM()"; // or RAND() or whatever this is in your dialect
}
}

Related

CLOB and CriteriaQuery

I have an entity that has a CLOB attribute:
public class EntityS {
...
#Lob
private String description;
}
To retrieve certain EntityS from the DB we use a CriteriaQuery where we need the results to be unique, so we do:
query.where(builder.and(predicates.toArray(new Predicate[predicates.size()]))).distinct(true).orderBy(builder.asc(root.<Long> get(EntityS_.id)));
If we do that we get the following error:
ORA-00932: inconsistent datatypes: expected - got CLOB
I know that's because you cannot use distinct when selecting a CLOB. But we need the CLOB. Is there a workaround for this using CriteriaQuery with Predicates and so on?
We are using an ugly workaround getting rid of the .unique(true) and then filtering the results, but that's crap. We are using it only to be able to keep on developing the app, but we need a better solution and I don't seem to find one...
In case you are using Hibernate as persistence provider, you can specify the following query hint:
query.setHint(QueryHints.HINT_PASS_DISTINCT_THROUGH, false);
This way, "distinct" is not passed through to the SQL command, but Hibernate will take care of returning only distinct values.
See here for more information: https://thoughts-on-java.org/hibernate-tips-apply-distinct-to-jpql-but-not-sql-query/
Thinking outside the box - I have no idea if this will work, but perhaps it is worth a shot. (I tested it and it seems to work, but I created a table with just one column, CLOB data type, and two rows, both with the value to_clob('abcd') - of course it should work on that setup.)
To de-duplicate, compute a hash of each clob, and instruct Oracle to compute a row number partitioned by the hash value and ordered by nothing (null). Then select just the rows where the row number is 1. Something like below (t is the table I created, with one CLOB column called c).
I expect that execution time should be reasonably good. The biggest concern, of course, is collisions. How important is it that you not miss ANY of the CLOBs, and how many rows do you have in the base table in the first place? Is something like "one chance in a billion" of having a collision acceptable?
select c
from (
select c, row_number() over (partition by dbms_crypto.hash(c, 3) order by null) as rn
from t
)
where rn = 1;
Note - the user (your application, in your case) must have EXECUTE privilege on SYS.DBMS_CRYPTO. A DBA can grant it if needed.

What is the limit of hibernate in clause

we know hibernate has this in clause:
Criteria criteria = session.createCriteria(User.class);
criteria.add(Restrictions.in(userIds));
Is there any limit on the size of userIds (which is an ArrayList, say)?
Thanks
It actually depends on the particular database you use. For example in Oracle this limit is 1000.
If you need to pass more values you need to use another approach. For example put the values into a temporary table and then do a select where id in (select id from temptable) query.

hibernate hql and setMaxResults

I've got some quarrels with hibernate.
My query, yet optimized, is quite heavy. One of my optimization consist on limiting the resultset returned.
So with hibernate I've used the method setMaxResultSet, but I hit the same problem described in this post:
Hibernate: Pagination with setFirstResult and setMaxResult
(the issue is that using setMaxResultSet hibernate in some cases wrap the query like this:
select * from (your query) where rownum <= :rownum)
So, the solution in that case was to add an orderBy, bu I've millions of records and an orderBy kills the execution time of the query.
I've managed to overcome the problem using the createNativeQuery and passing the exact query I need (something like "my query where rownum <= :rownum" instead of "select * from (your query) where rownum <= :rownum", and goodbye portability), but honestly I don't get why Hibernate acts like this...
As the previous post suggests, hibernate resolve an SQL like that as long as your query "is not stable" because, if I haven't misunderstand, the order of the records may not be the same between two executions, but I don't get how that method could solve this stability problem.
I am using the same pagination in hibernate.the HQL is given below.it may be useful for you.
(i) initially you should use this Query
List<Object> Entity_Cls_Lst= Objclass.createQuery("from library where book_id>Book_ID order by book_id").list();
(ii) after scrolling you should take last result data's Book_ID and pass to the query in where condition.
List<Object> Entity_Cls_Lst= Objclass.createQuery("from library where book_id>Book_ID order by book_id").setMaxResults(MAX_RECORDS).list();

Randomly select Entites from database table?

How would I randomly select rows from a database table? I'm using JPA and would like to use the Criteria API if possible. I'm aware there is an SQL equivalent, something like:
SELECT TOP 5 Id, Name FROM mNames
ORDER BY NEWID()
But how would I do this with JPQL and the Criteria API?
Possibly, with a NativeQuery? Is there a better way?
If all you need is a single random row, then you can do something like this:
//random is instance of java's Random class, and numberOfRows is total number of rows in the table
long rowIndex = random.nextLong()%numberOfRows;
TypedQuery typedQuery = ...;
typedQuery.setFirstResult(rowIndex);
typedQuery.setMaxResults(1);
I didn't test the code but you should get the idea.
If the primary key of your entity is not an arbitrary number, but something functional, you might be better off, creating a valid key randomly and then querying the specific row, which is fast operation, instead of executing a possibly large number of rows just to filter out everything but one.

HQL limit in subselect query

I want to insert foreign key of one table in another on basis of certain criteria. Structure is like
insert into CustomerResult(customer,draw) select c.idCustomer, from Customer c,Draw d where ..... and c.idCustomer in (select cc.idCustomer from Customer cc where ..... limit 10)
here i want to insert only fix no of records which fulfill certain criteria. I know hql has no limit keyword but want to implement like this. any suggestion?
I don't think that what you want to do (use limit in subquery) is directly supported by Hibernate. Have a look at these previous answers:
How to set a limit to inner query in Hibernate?
How do you do a limit query in HQL?

Categories