This feels like a trivial use case for Hibernate or JPA, but I've been struggling for a couple of days to get this to work.
I have an position entity class that has latitude, longitude and updateTime fields (among others). I would like to count the number of distinct combinations of those three fields while ignoring the others. In SQL, this is trivial:
SELECT COUNT(*) FROM (SELECT DISTINCT LONGITUDE, LATITUDE, UPDATE_TIME FROM POSITION) AS TEMP;
It is important that I abstract myh database implementation from the rest of my application because different users may wish to use different database engines. (Heck I use h2 for testing and mariadb for local production...)
I have been trying to translate this SQL into Java code using either Hibernate or JPA syntax, but I cannot figure out how.
EDIT - Here is as close as I have been able to get using JPA (ref: https://en.wikibooks.org/wiki/Java_Persistence/Criteria)
public long getCountDistinctInFlightPositions() {
Session session = sessionFactory.openSession();
CriteriaBuilder criteriaBuilder = session.getCriteriaBuilder();
CriteriaQuery<Tuple> innerQuery = criteriaBuilder.createTupleQuery();
Root<Position> position = innerQuery.from(Position.class);
innerQuery.multiselect(
position.get("longitude"),
position.get("latitude"),
position.get("updateTime")
);
// The method countDistinct(Expression<?>) in the type CriteriaBuilder is not applicable for the arguments (CriteriaQuery<Tuple>)
criteriaBuilder.countDistinct(innerQuery);
return 1;
}
You can do it this way:
CriteriaQuery<Long> countQuery = cb.createQuery( Long.class );
Root<Position> root = countQuery.from( Position.class );
countQuery.select( cb.count( root.get( "id" ) ) );
Subquery<Integer> subQuery = countQuery.subquery( Integer.class );
Root<Position> subRoot = subQuery.from( Position.class );
subQuery.select( cb.min( subRoot.get( "id" ) ) );
subQuery.groupBy( subRoot.get( "longitude" ),
subRoot.get( "latitude" ),
subRoot.get( "updateTime" ) );
countQuery.where( root.get( "id" ).in( subQuery ) );
Long count = entityManager.createQuery( countQuery ).getSingleResult();
This effectively generates the following SQL:
SELECT COUNT( p0.id ) FROM Position p0
WHERE p0.id IN (
SELECT MIN( p1.id )
FROM Position p1
GROUP BY p1.longitude, p1.latitude, p1.updateTime )
In a scenario where I have 3 rows and 2 of them have the same tuple of longitude, latitude, and update time, the query will return a result of 2.
Make sure you maintain a good index on [Longtitude, Latitude, UpdateTime] here so that you can take advantage of faster GROUP BY execution. The PK is already b-tree indexed so the other operations wrt COUNT/MIN should be accounted for easily by that index already.
I'm required to write a couple of UPDATE queries to parse data from a .csv file, but i do not know how the table works, i don't even have direct access to the Database, i just got an INSERT query like this one :
insert into lr_umbrales_valores (umcod_id, uvfec_dt, uvval_nm)
values ((select umcod_id from lr_umbrales
where lrcod_nm = (
select lrcod_id from lr_lineas_referencia
where me_metrica_nm = ?
and fecha_baja_dt is null)
and umtip_tx='S'), sysdate, ?)
So i'm trying this :
UPDATE LR_UMBRALES_VALORES SET UVVAL_NM = ?
WHERE (
SELECT UMCOD_ID FROM LR_UMBRALES
WHERE LRCOD_NM = (
SELECT LRCOD_ID FROM LR_LINEAS_REFERENCIA
WHERE ME_METRICA_NM = ?
AND FECHA_BAJA_DT IS NULL
)
AND UMTIP_TX = 'S')
AND UVFEC_DT = TO_DATE(?, 'DD/MM/YYYY HH24:MI:SS')");
This gives me a 'Missing Expression' Error (ORA-00936)
This is the only Information about the tables i got :
This is the Table i need to Update (its UVVAL_NM)
From this one, i get the UMCOD_ID when LRCOD_NM is the same as the LRCOD_ID from the next table.
Get the LRCOD_ID when ME_METRICA_NM is the same as the '?' parameter
Any tip in how to approach this? Needless to say, i'm completely new with SQL so this may be a very obvious mistake but i cannot get it right.
This part
WHERE (
SELECT UMCOD_ID FROM LR_UMBRALES
WHERE LRCOD_NM = (
SELECT LRCOD_ID FROM LR_LINEAS_REFERENCIA
WHERE ME_METRICA_NM = ?
AND FECHA_BAJA_DT IS NULL
)
AND UMTIP_TX = 'S')
causes the error. Maybe you wanted something like
WHERE UMCOD_ID = (
SELECT UMCOD_ID FROM LR_UMBRALES
WHERE LRCOD_NM = (
SELECT LRCOD_ID FROM LR_LINEAS_REFERENCIA
WHERE ME_METRICA_NM = ?
AND FECHA_BAJA_DT IS NULL
)
AND UMTIP_TX = 'S')
I am writing one application to search for specific fields in the form and display the corresponding query to the user (user can save the executed query and load the query later). I am using an API to generate the query based on the selected fields. The API returns the generated query in following format.
Generated query:
SELECT T0."id" AS "COL0"
FROM student_table T0
WHERE ( ( ( T0."student_name" = ? )
AND ( T0."grade" = ? ) )
AND ( T0."student_no" LIKE ? ) )
Expected query:
SELECT T0."id" AS "COL0"
FROM student_table T0
WHERE ( ( ( T0."student_name" = 'John' )
AND ( T0."grade" = 'A' ) )
AND ( T0."student_no" LIKE '12%' ) )
Now I have to build proper query from above query. Since I have the field values in the from, I want to replace the "?" with corresponding values.
Is it possible to replace the '?' with corresponding field values using java string replaceAll method ? can we use the concept of back references to replace the question marks with corresponding values ? If not possible with regex please suggest a better approach to solve the problem.
Edit
I think prepared statement wont help much since I need to automate this process for hundreds of fields which are unordered and it is a very costly operation also. (correct me If I am wrong)
I am actually using my company specific platform API, I am adding criteria's and query columns to the query definition and it returns the executed query in the above mentioned format. User should be able to save the Expected query, I should display both the query and result of query in UI.
Try to execute query in following way
String insertTableSQL = "SELECT T0.\"ID\" AS \"COL0\" FROM STUDENT_TABLE T0 WHERE ( ( ( T0.\"STUDENT_NAME\" = ? ) AND ( T0.\"GRADE\" = ? ) ) AND ( T0.\"STUDENT_NO\" LIKE ? ) )";
PreparedStatement preparedStatement = dbConnection.prepareStatement(insertTableSQL);
preparedStatement.setString(1, "John");
preparedStatement.setString(2, "A");
preparedStatement.setString(3, "12%");
preparedStatement.execute();
You can use PreparedStatement to execute such queries that contains ?
PreparedStatement ps = null;
ResultSet rs = null;
String sQuery = "SELECT T0."ID" AS "COL0" FROM STUDENT_TABLE T0 WHERE ( ( ( T0."STUDENT_NAME" = ? ) AND ( T0."GRADE" = ? ) ) AND ( T0."STUDENT_NO" LIKE ? ) )";
ps = objConnection.prepareStatement(sQuery);
ps.setString(1, valueOf_T0."STUDENT_NAME");
ps.setString(2, valueOf_T0."GRADE");
ps.setString(3, valueOf_T0."STUDENT_NO");
rs = ps.executeQuery();
As you have data values and you know the type of database fields, so that you can set values in setter methods according to your data type like setInt(), setDouble(),etc.
Read more about : http://docs.oracle.com/javase/6/docs/api/java/sql/PreparedStatement.html
Thanks
I am using a String SQL query in Java where i need to conditionally ignore or add AND clauses. For example suppose below query,
select t.name from test t where
t.id=?
and
t.name=?
and
t.status=?
so its like if name is empty or null i want to ignore it and if count status is total i need to execute below condition t.status= 'cancel' and t.status = 'confirm'. Like below pseudo query but i guess i can only use conditions in stored procedures right not in normal string sql?
select t.name from test t where
t.id=?
and
if(t.name != null){
t.name=?
}
and
if(t.status.equals = 'total'){
t.status='total'
}else{
t.status = ?
}
How about something like
select t.name from test t where
t.id=?
and
(t.name=? OR t.name IS NULL)
and
(
(t.status=? AND t.statuc <> 'total')
OR (t.status = 'total')
)
May be try this technice
(:param IS NULL OR <someField> = (other condition) :param)
You query looks like
SELECT ... FROM ...
WHERE
(:param IS NULL OR <someField> = (other condition) :param)
AND
(:param1 IS NULL OR <someField> = (other condition) :param1)
FOR YOU Example
WHERE t.status = CASE WHEN t.status = 'Total' THEN 'Total' ELSE :param END
How can I set a Hibernate Parameter to "null"? Example:
Query query = getSession().createQuery("from CountryDTO c where c.status = :status and c.type =:type")
.setParameter("status", status, Hibernate.STRING)
.setParameter("type", type, Hibernate.STRING);
In my case, the status String can be null. I have debugged this and hibernate then generates an SQL string/query like this ....status = null... This however does not Work in MYSQL, as the correct SQL statement must be "status is null" (Mysql does not understand status=null and evaluates this to false so that no records will ever be returned for the query, according to the mysql docs i have read...)
My Questions:
Why doesnt Hibernate translate a null string correctly to "is null" (and rather and wrongly creates "=null")?
What is the best way to rewrite this query so that it is null-safe? With nullsafe I mean that in the case that the "status" String is null than it should create an "is null"?
I believe hibernate first translates your HQL query to SQL and only after that it tries to bind your parameters. Which means that it won't be able to rewrite query from param = ? to param is null.
Try using Criteria api:
Criteria c = session.createCriteria(CountryDTO.class);
c.add(Restrictions.eq("type", type));
c.add(status == null ? Restrictions.isNull("status") : Restrictions.eq("status", status));
List result = c.list();
This is not a Hibernate specific issue (it's just SQL nature), and YES, there IS a solution for both SQL and HQL:
#Peter Lang had the right idea, and you had the correct HQL query. I guess you just needed a new clean run to pick up the query changes ;-)
The below code absolutely works and it is great if you keep all your queries in orm.xml
from CountryDTO c where ((:status is null and c.status is null) or c.status = :status) and c.type =:type
If your parameter String is null then the query will check if the row's status is null as well. Otherwise it will resort to compare with the equals sign.
Notes:
The issue may be a specific MySql quirk. I only tested with Oracle.
The above query assumes that there are table rows where c.status is null
The where clause is prioritized so that the parameter is checked first.
The parameter name 'type' may be a reserved word in SQL but it shouldn't matter since it is replaced before the query runs.
If you needed to skip the :status where_clause altogether; you can code like so:
from CountryDTO c where (:status is null or c.status = :status) and c.type =:type
and it is equivalent to:
sql.append(" where ");
if(status != null){
sql.append(" c.status = :status and ");
}
sql.append(" c.type =:type ");
The javadoc for setParameter(String, Object) is explicit, saying that the Object value must be non-null. It's a shame that it doesn't throw an exception if a null is passed in, though.
An alternative is setParameter(String, Object, Type), which does allow null values, although I'm not sure what Type parameter would be most appropriate here.
It seems you have to use is null in the HQL, (which can lead to complex permutations if there are more than one parameters with null potential.) but here is a possible solution:
String statusTerm = status==null ? "is null" : "= :status";
String typeTerm = type==null ? "is null" : "= :type";
Query query = getSession().createQuery("from CountryDTO c where c.status " + statusTerm + " and c.type " + typeTerm);
if(status!=null){
query.setParameter("status", status, Hibernate.STRING)
}
if(type!=null){
query.setParameter("type", type, Hibernate.STRING)
}
HQL supports coalesce, allowing for ugly workarounds like:
where coalesce(c.status, 'no-status') = coalesce(:status, 'no-status')
I did not try this, but what happens when you use :status twice to check for NULL?
Query query = getSession().createQuery(
"from CountryDTO c where ( c.status = :status OR ( c.status IS NULL AND :status IS NULL ) ) and c.type =:type"
)
.setParameter("status", status, Hibernate.STRING)
.setParameter("type", type, Hibernate.STRING);
For an actual HQL query:
FROM Users WHERE Name IS NULL
You can use
Restrictions.eqOrIsNull("status", status)
insted of
status == null ? Restrictions.isNull("status") : Restrictions.eq("status", status)
Here is the solution I found on Hibernate 4.1.9. I had to pass a parameter to my query that can have value NULL sometimes. So I passed the using:
setParameter("orderItemId", orderItemId, new LongType())
After that, I use the following where clause in my query:
where ((:orderItemId is null) OR (orderItem.id != :orderItemId))
As you can see, I am using the Query.setParameter(String, Object, Type) method, where I couldn't use the Hibernate.LONG that I found in the documentation (probably that was on older versions). For a full set of options of type parameter, check the list of implementation class of org.hibernate.type.Type interface.
Hope this helps!
this seems to work as wel ->
#Override
public List<SomeObject> findAllForThisSpecificThing(String thing) {
final Query query = entityManager.createQuery(
"from " + getDomain().getSimpleName() + " t where t.thing = " + ((thing == null) ? " null" : " :thing"));
if (thing != null) {
query.setParameter("thing", thing);
}
return query.getResultList();
}
Btw, I'm pretty new at this, so if for any reason this isn't a good idea, let me know. Thanks.