I learn JPA and tried the following code:
CriteriaBuilder queryBuilder = em.getCriteriaBuilder();
CriteriaQuery<PersonDI> criteriaQuery = queryBuilder.createQuery(PersonDI.class);
Root<PersonDI> personDI = criteriaQuery.from(PersonDI.class);
Fetch<PersonDI, ContactDI> contactDI = personDI.fetch("contacts", JoinType.LEFT);
criteriaQuery.where(queryBuilder.equal(personDI.get("guid"), guids.get(0)));
criteriaQuery.select(personDI).distinct(true);
TypedQuery<PersonDI> query = em.createQuery(criteriaQuery);
So one person has many contacts. The code is working. I use H2 database and EclipseLink. However, I can't understand what {oj...} means in SQL. Explain please.
This is the generated SQL:
SELECT DISTINCT t1.col0, t1.col3, t1.col1, t1.col2, t0.col0, t0.col2 FROM {oj tbl0 t1 LEFT OUTER JOIN tbl1 t0 ON (t0.col1 = t1.col0)} WHERE (t1.col0 = ?)
This is not specific to JPA or SQL, but it is part of the JDBC specification. The JDBC specification contains a number of escapes that are intended to address (potential) incompatibilities between SQL implementations. Drivers are expected to translate this to the database specific SQL.
The {oj <join-definition>} is an escape for an outer join.
To quote from the JDBC 4.2 specification section 13.4.3 Outer Joins:
Outer joins are an advanced feature and are not supported by all data sources. Consult relevant SQL documentation for an explanation of outer joins.
The escape syntax for an outer join is:
{oj <outer-join>}
where <outer-join> has the form:
table {LEFT|RIGHT|FULL} OUTER JOIN {table | <outer-join>} ON search-condition
(Note that curly braces ({}) in the preceding line indicate that one of the items between them must be used; they are not part of the syntax.) The following SELECT statement uses the escape syntax for an outer join.
Statement stmt = con.createStatement();
stmt.executeQuery("SELECT * FROM {oj TABLE1 " +
"LEFT OUTER JOIN TABLE2 ON DEPT_NO = 003420930}");
The JDBC API provides three DatabaseMetaData methods for determining the kinds of outer joins a driver supports: supportsOuterJoins, supportsFullOuterJoins, and supportsLimitedOuterJoins.
Nowadays almost all databases support the SQL standard outer join syntax, so most drivers will simply remove the {oj and } around this. However other JDBC escapes still have their place to address differences between databases.
Interestingly, as a JPA implementation usually knows the specific database in use, I would have expected this to generate database specific SQL instead of using the form with JDBC escapes.
Related
I have been trying to get Hibernate to generate me a query with a subquery in its where clause. I've used this answer as a base to help me going, but this question mentioned only one table.
However, this is what I would need (in SQL):
SELECT [...]
FROM a
LEFT OUTER JOIN b on a.idb = b.idb
LEFT OUTER JOIN c on b.idc = c.idc
[...]
LEFT OUTER JOIN k out on j.idk = k.idk
WHERE k.date = (SELECT max(date) from k in where in.idk = out.idk) OR k.date is null
As I am not very used to using Hibernate, I'm having trouble specifying these inner joins while navigating in the inner constraints.
I was able to re-create the initial criteria as in the linked answer, but I can't seem to join the criteria and the rootCriteria.
If the entities are properly joined with #ManyToOne annotations, simply joining the criteria to the previous table will be enough to propagate the criteria to the whole query.
The following code seems to work properly to add the WHERE clause I'm looking for.
DetachedCriteria kSubquery = DetachedCriteria.forClass(TableJPE.class,"j2");
kSubQuery = kSubQuery.createAlias("k","k2");
kSubQuery.setProjection(Projections.max("k2.date"));
kSubQuery = kSubQuery.add(Restrictions.eqProperty("j.id", "j2.id"));
rootCriteria.add(Restrictions.disjunction()
.add(Subqueries.propertyEq("k.date",kSubQuery))
.add(Restrictions.isNull("k.date")));
So I have this piece of Java code:
final Query query = session.createSQLQuery("SELECT DISTINCT(expense_document.id) FROM expense_document JOIN generic_object ON expense_document.id = generic_object.id JOIN expense_document_item ON expense_document_item.document_id = expense_document.id JOIN generic_object ON expense_document_item.id = generic_object.id WHERE expense_document.client_id = :client_id").setParameter("client_id", client.getId()).setMaxResults(1000);
and when this code is executed I get:
org.hibernate.exception.SQLGrammarException: could not execute query
Caused by: com.microsoft.sqlserver.jdbc.SQLServerException: Incorrect syntax near the keyword 'from'.
I can not find what MS SQL does not like about this query. When I am connected to MySQL, this line will not cause any problems.
Try removing the parenthesis around the parameter to the distinct keyword.
Use it like this:
select distinct something from somewhere
Your query looks fine.. are you using an up to date driver for MSSQL?
See how to configure hibernate config file for sql server for supported SQL server drivers.
You should be able to do:
final Query query = session.createSQLQuery("SELECT DISTINCT(expense_document.id) FROM expense_document JOIN generic_object ON expense_document.id = generic_object.id JOIN expense_document_item ON expense_document_item.document_id = expense_document.id JOIN generic_object ON expense_document_item.id = generic_object.id WHERE expense_document.client_id = :client_id").setParameter("client_id", client.getId()).setMaxResults(1000);
or
final Query query = session.createSQLQuery("SELECT DISTINCT expense_document.id FROM expense_document JOIN generic_object ON expense_document.id = generic_object.id JOIN expense_document_item ON expense_document_item.document_id = expense_document.id JOIN generic_object ON expense_document_item.id = generic_object.id WHERE expense_document.client_id = :client_id").setParameter("client_id", client.getId()).setMaxResults(1000);
However, if you're using hibernate, why aren't you using HQL, JPA Query language or Criteria? That should ensure that you don't have to change SQL syntax depending upon the vendor as they differ slightly in different flavours. I feel using JPA with entity manager might be the way forward if you're switching between different databases as createSqlQuery will send the string as native SQL to the vendor.
Hibernate EntityManager implements the programming interfaces and
lifecycle rules as defined by the JPA 2.0 specification
https://docs.jboss.org/hibernate/entitymanager/3.6/reference/en/html_single/
If you're using something like hibernate, then createSqlQuery is really good for sending vendor specific queries when you want to set hints for example.
Hibernate (HQL) generated the following SQL for which I inserted the parameters:
select
sum(activity0_.calculated_work) as col_0_0_
, employee2_.id as col_1_0_
, projectele1_.id as col_2_0_
from
activity activity0_
inner join generic_object activity0_1_ on activity0_.id=activity0_1_.id
left outer join project_element projectele1_ on activity0_.project_element_id=projectele1_.id
left outer join employee employee2_ on activity0_.owner_id=employee2_.id
left outer join org_unit orgunit3_ on employee2_.org_unit_id=orgunit3_.id
where
activity0_1_.deleted=0 and
activity0_.client_id=22
group by
employee2_.id order by SUM(activity0_.calculated_work) DESC
Error message: Column 'project_element.id' is invalid in the select list because it is not contained in either an aggregate function or the GROUP BY clause.
I executed this SQL directly in the SQL Server Studio with the same result. I commented this line:
, projectele1_.id as col_2_0_
The SQL was then accepted by the SQL Server
The table project_element definitely has a column with the name id it is also referenced in the LEFT OUTER JOIN and there this column is not causing an error.
Removing the alias projectele1_ had no effect.
To me this looks like a really simple SQL statement. I cannot imagine what is wrong with it and the error message is not helping at all. Any ideas what could be wrong with the SQL?
Your SQL syntax is wrong.If you add projectele1_.id to group by clause it will work.Only aggregate functions work in select statement with group by clause.Or if you remove projectele1_.id from select it will work fine.
My mistake. I should have read the error message several times. projectele1_id is not in the group by clause. MS SQL does not allow to include such a column into the select list. This seems to be a consistency check.
Too bad though that the usage of HQL leads to such an exception in SQL Server but not in MySQL Server.
How to add groupBy criteria to the code below? Because if I add criteriaQuery.groupBy(from.get(minutis.Preke_.prId)); - I get exactly the same SQL statement (without groupBy):
CriteriaBuilder cb = MinutisManager.getInstance().getCriteriaBuilder();
CriteriaQuery criteriaQuery = cb.createQuery(minutis.Preke.class);
Root<minutis.Preke> from = criteriaQuery.from(minutis.Preke.class);
from.fetch(minutis.Preke_.tiekejai, JoinType.LEFT).fetch(minutis.PrekeTiekejas_.tiekejas, JoinType.LEFT);
//criteriaQuery.groupBy(from.get(minutis.Preke_.prId));
TypedQuery<minutis.Preke> typedQuery = MinutisManager.getInstance().createQuery(criteriaQuery);
typedQuery.setFirstResult(0);
typedQuery.setMaxResults(100);
typedQuery.getResultList();
EDIT 1:
criteriaQuery.distinct(true) is not an option for me. Because that command hangs the whole statement and if I use EXPLAIN:
If I use GROUP BY on the query, then EXPLAIN is:
EDIT 2:
I get this SQL statement with and without criteriaQuery.groupBy(from.get(minutis.Preke_.prId));
SELECT ... FROM preke t1 LEFT OUTER JOIN preke_tiekejas t0 ON (t0.pr_id = t1.pr_id) LEFT OUTER JOIN tiekejas t2 ON (t2.tiek_id = t0.tiek_id) LEFT OUTER JOIN gamintojas t3 ON (t3.gam_id = t1.gam_id) LEFT OUTER JOIN google_compare t4 ON (t4.pr_id = t1.pr_id) LEFT OUTER JOIN grupe t5 ON (t5.pgs_id = t1.pgs_id) LEFT OUTER JOIN preke_kaina t6 ON (t6.pr_id = t1.pr_id) ORDER BY t1.pr_id LIMIT ?, ?
The SQL for the GROUP BY query should definitely contain a GROUP BY. Ensure that you are compiling and deploying the code correctly.
It could be a bug that the group by is ignored, as normal group by queries will not select object, but aggregated values. Check that you are using the latest release, and perhaps log a bug, or try JPQL.
In general your query does not make any sense, normally on a group by query you can only select the fields that you grouped by, or aggregation functions.
Perhaps consider batch fetching instead of join fetching.
http://java-persistence-performance.blogspot.com/2010/08/batch-fetching-optimizing-object-graph.html
I will file a bug in Eclipselink, because if I change fetch line with QueryHints, everything works:
Change this (with this line I get duplicate Preke entities with populated children entities):
from.fetch(minutis.Preke_.tiekejai, JoinType.LEFT).fetch(minutis.PrekeTiekejas_.tiekejas, JoinType.LEFT);
To this (with this lines I get unique Preke entities with populated children entities):
typedQuery.setHint(QueryHints.LEFT_FETCH, "Preke.tiekejai");
typedQuery.setHint(QueryHints.LEFT_FETCH, "Preke.tiekejai.tiekejas");
I get my desired result.
EDIT 1:
The bug really exists, now max resulsts is not working. Both cases typedQuery is identical.
typedQuery.setMaxResults(100);
System.out.println(typedQuery.getResultList().size()); //prints 73
typedQuery.setMaxResults(500);
System.out.println(typedQuery.getResultList().size()); //prints 413
No problem, I found the bug report, here it is, just in case someone else needs it.Criteria api ignores group by statement
I have a MySql query like this:
select AK.*, max(AA.activityDate)
from AssessmentKey AK
join AssessmentActivity AA on AA.ASSESSMENTKEY_ID = AK.id
group by AK.id having max(AA.activityDate) <= '2012-10-02 17:30:55'
Is there a way to convert into in JPA NamedQuery. I am using OpenJPA.
If I put it directly into:
#NamedQuery(name = "AssessmentActivity.findByDate",
query = "select AK.*, max(AA.activityDate) from AssessmentKey AK
join AssessmentActivity AA on AA.ASSESSMENTKEY_ID = AK.id
group by AK.id having max(AA.activityDate) <= '2012-10-02 17:30:55'")
The error is showed here: select AK.* that identifier expected, got "*" and also it does not like on, here it says:
How can I resolve this problem?
First problem: you should replace AK.* with AK you just need the entity alias here.
Second problem: join syntax is not like that. You should write: join and navigate through the object references,eg: AK.assesmentActivity and use the where keyword instead of on
Here's a tip on join: JPQL join
Remember: you are in the ORM, dealing with Entities and their properties, not DB foreign keys and columns.
(ps: Maybe you wanted to write a NativeQuery? There you can use native SQL syntax)
EDIT:
(on your comment) So you must start your query from AA:
select AK from AssesmentActivity AA join AssesmentKey AK where AA.assesmentKey = AK ...
This way you can join them.