I clearly defined a WHERE statements in the main and sub-query. This is printed in the JPA log if I omit the subquery. However, the same where clause ist not applied, if I add the subquery. What is it about JPA Criteria query, that omits the WHERE clause (selection σ)?
// 1) MainQuery
// Create the FROM
Root<PubThread> rootPubThread = cq.from(PubThread.class);
// Create the JOIN fro the first select: join-chaining. You only need the return for ordering. e.g. cq.orderBy(cb.asc(categoryJoin.get(Pub_.title)));
Join<Pub, PubCategory> categoryJoin = rootPubThread.join(PubThread_.pups).join(Pub_.pubCategory);
// Create the WHERE
cq.where(criteriaBuilder.not(criteriaBuilder.equal(rootPubThread.get(PubThread_.id), threadId)));
// Create the SELECT, at last
cq.select(rootPubThread).distinct(true);
// 2) Subquery
Subquery<PubThread> subquery = cq.subquery(PubThread.class);
Root<PubThread> rootPubThreadSub = subquery.from(PubThread.class);
subquery.where(criteriaBuilder.equal(rootPubThread.get(PubThread_.id), threadId));
Join<Pub, PubCategory> categoryJoinSub = rootPubThreadSub.join(PubThread_.pups).join(Pub_.pubCategory);
subquery.select(rootPubThreadSub);
Predicate correlatePredicate = criteriaBuilder.equal(rootPubThreadSub.get(PubThread_.id), rootPubThread);
subquery.where(correlatePredicate);
cq.where(criteriaBuilder.exists(subquery));
Here is the output of this query:
select distinct
pubthread0_.id as id3_,
pubthread0_.dateCreated as dateCrea2_3_,
pubthread0_.dateModified as dateModi3_3_,
pubthread0_.name as name3_
from
pubthread pubthread0_
inner join
pub_pubthread pups1_ ON pubthread0_.id = pups1_.pubThreads_id
inner join
pub pub2_ ON pups1_.pups_id = pub2_.id
inner join
PubCategory pubcategor3_ ON pub2_.pubCategoryId = pubcategor3_.id
where
exists( select
pubthread4_.id
from
pubthread pubthread4_
inner join
pub_pubthread pups5_ ON pubthread4_.id = pups5_.pubThreads_id
inner join
pub pub6_ ON pups5_.pups_id = pub6_.id
inner join
PubCategory pubcategor7_ ON pub6_.pubCategoryId = pubcategor7_.id
where
pubthread4_.id=pubthread0_.id)
Got it. As soon as you defined a predicate, the where clause is rejected. That is for sure. Combine the where clause with a conjunction within a Predicate instance and append it in the where clause. Use an criteriaBuilder.and(..., ...). To semantically do the same thing.
So now, there are two conjunctional predicates: One for the subquery and one for the main query. Notice the obsolete single select(...) I ruled out from the code (it's commented).
CriteriaBuilder criteriaBuilder = em.getCriteriaBuilder();
CriteriaQuery<PubThread> mainQuery = criteriaBuilder
.createQuery(PubThread.class);
// 1) MainQuery
// Create the FROM
Root<PubThread> rootPubThread = mainQuery.from(PubThread.class);
// Create the JOIN from the first select: join-chaining. You only need the return for ordering. e.g. cq.orderBy(cb.asc(categoryJoin.get(Pub_.title)));
Join<Pub, PubCategory> categoryJoin = rootPubThread.join(PubThread_.pups).join(Pub_.pubCategory);
// Create the WHERE
mainQuery.where(criteriaBuilder.not(criteriaBuilder.equal(rootPubThread.get(PubThread_.id), threadId)));
// Create the SELECT, at last
mainQuery.select(rootPubThread).distinct(true);
// 2) Subquery
Subquery<PubThread> subquery = mainQuery.subquery(PubThread.class);
Root<PubThread> rootPubThreadSub = subquery.from(PubThread.class);
//subquery.where(criteriaBuilder.equal(rootPubThread.get(PubThread_.id), threadId));
Join<Pub, PubCategory> categoryJoinSub = rootPubThreadSub.join(PubThread_.pups).join(Pub_.pubCategory);
subquery.select(rootPubThreadSub);
//Predicate correlatePredicate = criteriaBuilder.equal(rootPubThreadSub.get(PubThread_.id), rootPubThread);
Predicate correlatePredicate = criteriaBuilder.and(
criteriaBuilder.equal(rootPubThreadSub.get(PubThread_.id), rootPubThread),
criteriaBuilder.equal(rootPubThread.get(PubThread_.id), threadId)
);
subquery.where(correlatePredicate);
//Predicate correlatePredicate = criteriaBuilder.equal(rootPubThreadSub.get(PubThread_.id), rootPubThread);
Predicate mainPredicate = criteriaBuilder.and(
criteriaBuilder.not(criteriaBuilder.equal(rootPubThread.get(PubThread_.id), threadId)),
criteriaBuilder.exists(subquery)
);
//cq.where(criteriaBuilder.exists(subquery));
mainQuery.where(mainPredicate);
TypedQuery<PubThread> typedQuery = em.createQuery(mainQuery);
List<PubThread> otherPubThreads = typedQuery.getResultList();
return otherPubThreads;
The result is something like this
where
pubthread4_.id=pubthread0_.id
and pubthread0_.id=1
Related
I'm trying to use a VaultCustomQueryCriteria (Corda - Java) with the aggregate function SUM, but I get no results.
If I use another VaultCustomQueryCriteria, the query works.
What am I doing wrong?
Below some examples:
Query OK:
QueryCriteria statusCriteria = new QueryCriteria.VaultQueryCriteria(Vault.StateStatus.UNCONSUMED);
Field name = ExampleSchemaV1.Ingestion.class.getDeclaredField("name");
QueryCriteria countCriteria = new QueryCriteria.VaultCustomQueryCriteria(Builder.equal(name, "Mark"));
List<StateAndRef<IngestionState>> results = rpcOps.vaultQueryByCriteria(countCriteria,IngestionState.class).getStates();
Query KO: (no results)
QueryCriteria statusCriteria = new QueryCriteria.VaultQueryCriteria(Vault.StateStatus.UNCONSUMED);
Field nr = ExampleSchemaV1.Ingestion.class.getDeclaredField("nr");
Field name = ExampleSchemaV1.Ingestion.class.getDeclaredField("name");
CriteriaExpression sumQta = Builder.sum(nr, Arrays.asList(name));
QueryCriteria sumQtaCriteria = new QueryCriteria.VaultCustomQueryCriteria(sumQta);
QueryCriteria criteria = statusCriteria.and(sumQtaCriteria);
List<StateAndRef<IngestionState>> results = rpcOps.vaultQueryByCriteria(criteria,IngestionState.class).getStates();
Each vault query returns a Vault.Page object. When performing a sum query, the result of the sum is accessible via Vault.Page.getOtherResults(), rather than via Vault.Page.getStates().
This is because the sum query doesn't return any actual states, but rather the result of a computation over these states.
Im trying to Right join in symfony. I tried as described here Doctrine 2 - Outer join query and here Symfony - Using Outer Joins with Doctrine ORM .
$query = $em->getRepository('AppBundle:raports')->createQueryBuilder('r')
->select('r')
->leftJoin('r.requestRaports rr WITH rr.formId = :formId', false)
->setParameter('formId', $requestId->getFormId())
->getQuery();
it gives
SELECT
r0_.id AS id_0,
r0_.adminComment AS adminComment_1,
r0_.addDate AS addDate_2,
r0_.submitDate AS submitDate_3,
r0_.statusId AS statusId_4,
r0_.userId AS userId_5,
r0_.requestId AS requestId_6,
r0_.requestRaports AS requestRaports_7
FROM
raports r0_
LEFT JOIN request_raports r1_ ON r0_.requestRaports = r1_.id
AND (r1_.formId = ?)
When i try
$query = $em->getRepository('AppBundle:raports')->createQueryBuilder('r')
->select('r')
->join('r.requestRaports rr WITH rr.formId = :formId', false)
->setParameter('formId', $requestId->getFormId())
->getQuery();
it looks like that
SELECT
r0_.id AS id_0,
r0_.adminComment AS adminComment_1,
r0_.addDate AS addDate_2,
r0_.submitDate AS submitDate_3,
r0_.statusId AS statusId_4,
r0_.userId AS userId_5,
r0_.requestId AS requestId_6,
r0_.requestRaports AS requestRaports_7
FROM
raports r0_
INNER JOIN request_raports r1_ ON r0_.requestRaports = r1_.id
AND (r1_.formId = ?)
But i want query like
SELECT * FROM raports r RIGHT JOIN request_raports rr ON
r.requestRaports = rr.id
How to make right join work in doctrine2?
You can ether use a LEFT JOIN and reversing your SQL like this
SELECT * FROM request_raports rr LEFT JOIN raports r ON r.requestRaports = rr.id
Or you can create your own "right join". If you look at leftJoin definition in doctrine, it's like :
leftJoin($join, $alias, $conditionType = null, $condition = null, $indexBy = null)
{
$parentAlias = substr($join, 0, strpos($join, '.'));
$rootAlias = $this->findRootAlias($alias, $parentAlias);
$join = new Expr\Join(
Expr\Join::LEFT_JOIN, $join, $alias, $conditionType, $condition, $indexBy
);
return $this->add('join', array($rootAlias => $join), true);
}
So, it may look like this :
$qb = $this->createQueryBuilder('b');
$rightJoin = new Expr\Join('RIGHT', 'r.requestRaports', 'rr', Expr\Join::WITH, 'rr.formId = :formId');
$qb
->select('r')
->add('join', ['r' => $rightJoin], true)
...
I have not tested this and don't know if its the best way to do it ...
I'm working on a Java project, using Hibernate to administrate data on a SQL database.
I try to fetch a list of instances from the Database, that have a minimal timestamp of the group they share. The group is modeled by a container.
Here is a minimal model sketch:
#Entity
#Table(name = "object")
public class Object implements Serializable{
#Id
#GeneratedValue(strategy = GenerationType.Auto)
long obj_id;
#Column(name = "time_stamp", nullable = false)
Date timestamp;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "container_id", nullable = false)
Container con;
}
#Entity
#Table(name = "container")
public class Container{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
long con_id;
#OneToMany(mappedBy = "container")
List<object> obj_list;
}
So there are some objects with a timestamp and containers that group these objects.
For example, there are two containers, con_a and con_b:
Container con_a:
con_id = 1
obj_list = {obj_a, obj_b}
Container con_b:
con_id = 2
obj_list = {obj_c}
And three objects, obj_a, obj_b, obj_c:
Object obj_a
obj_id = 1
timestamp = 10
con = con_a
Object obj_b
obj_id = 2
timestamp = 20
con = con_a
Object obj_c
obj_id = 3
timestamp = 30
con = con_b
The desired List in this example would look like:
List<Object> = {obj_a, obj_c}
I seem to move in a circle, as I do not even know where to "start" the query:
Criteria crit = session.createCriteria(Container.class). ...
or
Criteria crit = session.createCriteria(Object.class). ...
It seems both possible for me, but i just have no idea how to go on from any of those 2 possibilities.
Update [2014.07.11, 14:19]:
I tried and started the query with the Object class and used a Subquery:
Session session = getSession();
Transaction transaction = session.beginTransaction();
DetachedCriteria IdListOfGroupMinimum = DetachedCriteria.forClass(Object.class, "obj")
IdListOfGroupMinimum.createAlias("con.id", "containerId")
.setProjection(
.Projections.projectionList()
.add(Projections.property("obj.id"))
.add(Projections.min("obj.timestamp"))
.add(Projections.groupProperty("containerId")))
.setProjection(Projection.property("obj.id"));
Criteria objects = session.createCriteria(object.class, "obj")
objects.add(Subqueries.in("obj.id", IdListOfGroupMinimum));
List<Object> = objects.list();
But I received the following error:
javax.servlet.ServletException: org.hibernate.QueryException: not an association: id
I tried to do this:
SELECT * from Object
WHERE id IN (
SELECT obj.id
FROM Object obj
INNER JOIN (
SELECT obj.containerID, MIN(obj.timestamp) AS minimum
FROM Object obj
GROUP BY obj.containerID) subquery
ON obj.containerID = subquery.containerID
WHERE obj.timestamp = subquery.minimum
)
I found a solution for my problem which is probably not the most elegant one, but it works.
Mainly I used the SQL-Query that I already posted above:
Session session = getSession();
Transaction transaction = session.beginTransaction();
//This query fetches the IDs of the smallest objects in each group with
//regard to the timestamp
Query q = session.createSQLQuery(
"SELECT obj.id FROM Object obj "
+ "INNER JOIN ( "
+ "SELECT obj.containerID, MIN(obj.timestamp) AS minimum "
+ "FROM Object obj "
+ "GROUP BY obj.containerID) subquery "
+ "ON obj.containerID = subquery.containerID "
+ "WHERE obj.timestamp = subquery.minimum "
);
//This tells Hibernate that the result are values of type Long
q.addScalar("id", LongType.INSTANCE)
//Creates a list of the found IDs
#SuppressWarnings("unchecked")
List<Long> ids = q.list();
//Fetches all object with those IDs...
Criteria smallestOfEachGroup = session.createCriteria(Object.class)
.add(Restrictions.in("id", ids);
//...and saves them in a list.
#SuppressWarnings("unchecked")
List<Object> desiredList = smallestOfEachGroup.list()
try{
transaction.commit();
} catch(HibernateException e) {
transaction.rollback();
}
As all my sketches are not the real code, so there might be still naming errors.
Anyway, I hope this helps someone.
I still would be pleased by any more elegant solution.
Update [2014.07.20, 18:50]:
I found a solution that uses Hibernate Criteria exclusively :)
Session session = getSession();
Transaction transaction = session.beginTransaction();
//This subquery fetches the minimal timestamp of a container.
DetachedCriteria minOfGroup = DetachedCriteria.forClass(Object.class);
minOfGroup.add(Restrictions.eqProperty("con.con_id", "outerObject.con.con_id")
.setProjection(Projections.min("timestamp"));
//This subquery fetches the IDs of all Objects, whose timestamp is minimal
//in their container.
DetachedCriteria groupwiseMin = DetachedCriteria.forClass(Object.class, "outerObject");
groupwiseMin.add(Subqueries.propertyEq("timestamp", minOfGroup));
.setProjections(Projections.id())
//This subquery fetches all Objects whose IDs are fetched by the groupwiseMin
//query
Criteria groupwiseMinObjects = session.createCriteria(Object.class);
groupwiseMinObjects.add(Subqueries.propertyIn("obj_id", groupwiseMin));
List<Object> desiredObjects = groupwiseMinObjects.list();
try{
transaction.commit();
} catch(HibernateException e) {
transaction.rollback();
}
I think you can make this query even shorter, if you remove the groupwiseMinObjects query above replace the groupwiseMin query by:
Criteria anotherGroupWiseMinObjects = session.createCriteria(Object.class, "outerObject");
anotherGroupwiseMinObjects.add(Subqueries.propertyEq("timestamp", minOfGroup));
But I did not test that.
In my original project I use several subqueries that converge in a single query.
That means after some subqueries, there is a final query like:
Criteria finalQuery = session.createCriteria(Object.class);
finalQuery.add(Subqueries. (...) )
(...)
.add(Subqueries. (...) );
I want to find records for related_elements table, where relationId belongs to a list
suppose a Set tempSet contains[2,3,4]
I have to check for these value contains in related_element table using jpa criteria query
CriteriaBuilder cb1=entityManager.getCriteriaBuilder();
CriteriaQuery<RelatedElements> cq1=cb1.createQuery(RelatedElements.class);
Root<RelatedElements> RelatedElementsRoot=cq1.from(RelatedElements.class);
for (Integer tSet : tempSet) {
ParameterExpression<Integer> pRelatedElement=cb1.parameter(Integer.class);
cq1.multiselect(cb1.count(RelatedElementsRoot.<RelatedElements>get("relatedElementsPk").<Integer>get("relationId"))).where(cb1.equal(RelatedElementsRoot.get("relationId"), pRelatedElement));
TypedQuery<RelatedElements> qry = entityManager.createQuery(cq1);
qry.setParameter(pRelatedElement, tSet);
count = entityManager.createQuery(cq1).getSingleResult().getRelationId();
}
but its now working...any suggessions
second try
CriteriaBuilder cb1=entityManager.getCriteriaBuilder();
CriteriaQuery<Integer> cq1 = cb1.createQuery(Integer.class);
Root<RelatedElements> RelatedElementsRoot=cq1.from(RelatedElements.class);
for (Integer tSet : tempSet) {
ParameterExpression<Integer> pRelatedElement=cb1.parameter(Integer.class);
cq1.multiselect(cb1.count(cq1.from(RelatedElements.class)));
cq1.where((cb1.equal(RelatedElementsRoot.get("relatedElementsPk").get("relationId"), pRelatedElement)));
TypedQuery<Integer> qry = entityManager.createQuery(cq1);
qry.setParameter(pRelatedElement, tSet);
count =qry.getSingleResult();
}
its giving exception at qry.setParameter
Unable to locate appropriate constructor on class [java.lang.Integer] [select new java.lang.Integer(count(*)) from com.mcd.webex.model.RelatedElements as generatedAlias0, com.mcd.webex.model.RelatedElements as generatedAlias1 where generatedAlias0.relatedElementsPk.relationId=:param0]
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Long> cq = cb.createQuery(Long.class);
Root<Dzialy> root = cq.from(Dzialy.class);
EntityType <Dzialy> Dzialy_ = root.getModel();
cq.select((cb.countDistinct(root)));
cq.where( cb.equal( root.get(Dzialy_.getSingularAttribute("DZI_id")), 1) );
long l = em.createQuery(cq).getSingleResult();
em - EntityManager
DZI_id - column name
1 - searching value
Dzialy - entity class
As documented, CriteriaBuilder.count returns Expression<java.lang.Long>. Consequently type argument to CriteriaQuery and TypedQuery should be Long as well. Same holds true for type of count variable.
When there is only one value to be selected, then it makes sense to use CriteriaQuery.select instead of multiselect, because then such an error is catched already in compile time.
Long count;
...
CriteriaQuery<Long> cq1 = cb1.createQuery(Long.class);
...
cq1.select(cb1.count(cq1.from(RelatedElements.class)));
...
TypedQuery<Long> qry = entityManager.createQuery(cq1);
CriteriaBuilder qb = entityManager.getCriteriaBuilder();
CriteriaQuery<Long> cq = qb.createQuery(Long.class);
cq.select(qb.count(cq.from(YourEntity.class)));
cq.where(/*your stuff*/);
return entityManager.createQuery(cq).getSingleResult();
We have a named query like this:
UPDATE Foo f SET f.x = 0 WHERE f.x = :invoiceId
Foo in this case is an entity with a superclass, using the table-per-class inheritance strategy.
The SQL that EclipseLink generates is:
UPDATE foo_subclass SET x = ?
WHERE EXISTS(SELECT t0.id
FROM foo_superclass t0, foo_subclass t1
WHERE ((t1.x = ?) AND ((t1.id = t0.id) AND (t0.DTYPE = ?)))
(The ? slots are correctly filled in.)
On Informix 11.70, we get an error that the subquery cannot access the table being changed.
Here is the documentation that I was able to find on subquery restrictions on Informix: http://publib.boulder.ibm.com/infocenter/idshelp/v115/index.jsp?topic=%2Fcom.ibm.sqls.doc%2Fids_sqs_2005.htm
Other databases also feature restrictions on subqueries like this, so although this is manifesting as an Informix issue, I'm sure that if we ran this against, say, MySQL, we would get a similar error.
How can I get EclipseLink to honor these restrictions? Is there a better query I should be using?
Instead of:
UPDATE foo_subclass SET x = ?
WHERE EXISTS(SELECT t0.id
FROM foo_superclass t0, foo_subclass t1
WHERE ((t1.x = ?) AND ((t1.id = t0.id) AND (t0.DTYPE = ?)))
do this:
UPDATE
foo_subclass SET x = ?
WHERE
foo_subclass.x = ? AND
EXISTS(SELECT t0.id
FROM foo_superclass t0
WHERE ((foo_subclass.id = t0.id) AND (t0.DTYPE = ?))
Note that on 11.50 you cannot use alias for foo_subclass. It's allowed in 11.70.
I'm assuming "id" are primary keys (or at least unique identifiers).
Found the answer. Looks like EclipseLink had to handle this case for MySQL, which also has similar issues.
The answer is that in your InformixPlatform subclass, you need to override the following methods to solve this problem:
supportsLocalTemporaryTables(): this needs to return true
shouldAlwaysUseTempStorageForModifyAll(): this needs to return true
dontBindUpdateAllQueryUsingTempTables needs to return true
getCreateTempTableSqlPrefix(): this needs to return CREATE TEMP TABLE
getCreateTempTableSqlSuffix(): this needs to return WITH NO LOG
isInformixOuterJoin(): needs to return false
getTempTableForTable(DatabaseTable): this needs to do this:
return new DatabaseTable("TL_" + table.getName(), "" /* no table qualifier */, table.shouldUseDelimiters(), this.getStartDelimiter(), this.getEndDelimiter());
In addition, you need to override the following methods as well for proper InformixPlatform behavior:
appendBoolean(Boolean, Writer): the stock Informix platform does not write out boolean literals properly. Yours needs to do this:
if (Boolean.TRUE.equals(booleanValue)) {
writer.write("'t'");
} else {
writer.write("'f'");
}
You need to override writeUpdateOriginalFromTempTableSql so that it contains the same code as the H2Platform's override does:
#Override
public void writeUpdateOriginalFromTempTableSql(final Writer writer, final DatabaseTable table, final Collection pkFields, final Collection assignedFields) throws IOException {
writer.write("UPDATE ");
final String tableName = table.getQualifiedNameDelimited(this);
writer.write(tableName);
writer.write(" SET ");
final int size = assignedFields.size();
if (size > 1) {
writer.write("(");
}
writeFieldsList(writer, assignedFields, this);
if (size > 1) {
writer.write(")");
}
writer.write(" = (SELECT ");
writeFieldsList(writer, assignedFields, this);
writer.write(" FROM ");
final String tempTableName = this.getTempTableForTable(table).getQualifiedNameDelimited(this);
writer.write(tempTableName);
writeAutoJoinWhereClause(writer, null, tableName, pkFields, this);
writer.write(") WHERE EXISTS(SELECT ");
writer.write(((DatabaseField)pkFields.iterator().next()).getNameDelimited(this));
writer.write(" FROM ");
writer.write(tempTableName);
writeAutoJoinWhereClause(writer, null, tableName, pkFields, this);
writer.write(")");
}
Lastly, your constructor needs to call this.setShouldBindLiterals(false).
With these changes, it seems that Informix is happy.