LEFT JOIN with AND operator using Criteria API - sql

I am trying to build dynamic queries using JPA and Criteria API but without metamodels.
With the tables:
Foo:
ID
1
2
3
4
Bar:
ID FooID Number
1 2 44
2 2 55
3 3 55
I want to retrieve all Foo entities where no matching Bar has Number 44. (Expecting Foo 1, 3, 4)
The SQL should look something like this:
select distinct *
from Foo foo0_
left join Bar bar0_ on foo0_.ID=bar0_.FooID and bar0_.Number=44
where bar0__.id is null;
My code looks something like this:
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Foo> cq = cb.createQuery(Foo.class);
Root<Foo> fooRoot = cq.from(Foo.class);
cq.select(fooRoot).distinct(true);
List<Predicate> allPredicates = new ArrayList<Predicate>();
List<Predicate> orPredicates = new ArrayList<Predicate>();
List<Predicate> andPredicates = new ArrayList<Predicate>();
andPredicates.add(cb.equal(fooRoot.join("bars", JoinType.LEFT).get("number"), 44));
andPredicates.add(cb.isNull(fooRoot.join("bars", JoinType.LEFT).get("ID")));
orPredicates.add(cb.and(andPredicates.toArray(new Predicate[andPredicates.size()])));
allPredicates.add(cb.or(orPredicates.toArray(newPredicate[orPredicates.size()])));
cq.where(allPredicates.toArray(new Predicate[allPredicates.size()]));
TypedQuery<Foo> query = em.createQuery(cq);
query.getResultList();
But that generates this SQL:
select distinct *
from Foo foo0_
left outer join Bar bar1_ on foo0_.id=bar1_.FooID
left outer join Bar bar2_ on foo0_.id=bar2_.FooID
where bar1_.Number=44 and (bar2_.id is null);
and returns no Foo.
Any ideas? Thank you!

You don't specify which JPA implementation this is for, and clearly the SQL may differ for each.
That aside, your joining is wrong. Each join call will do a JOIN, and you only want 1 join if what you write is correct. Also you can make use of JPA 2.1 "ON" conditions on the join (rather than putting it in the WHERE).
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Foo> cq = cb.createQuery(Foo.class);
Root<Foo> fooRoot = cq.from(Foo.class);
cq.select(fooRoot).distinct(true);
Join barJoin = fooRoot.join("bars", JoinType.LEFT);
Predicate onCond = cb.equal(barJoin.get("number"), 44);
barJoin.on(onCond);
Then do the same as your question except using the barJoin; no idea what those AND/OR conditions are trying to do - your suggested SQL has none of that.

Related

Spring Data JPA - Left Join with Multiple Criteria

I need to do a join using a JOIN TABLE ON ... AND ... using Spring Data JPA criteria builder.
I know I can do a basic join like so:
Join<ReportEntity, ProductEntity> productJoin = root.join("products", JoinType.LEFT);
But can I specify extra criteria for the join? If not, is there another way to achieve this using the Criteria Builder? This is the SQL query I'd like to reproduce:
SELECT r.id, p.rare
FROM REPORT r
LEFT JOIN PRODUCT p
ON r.id = p.report_id AND p.rare = 1
WHERE p.report_id IS NULL;
Note that specifying p.rare = 1 in the above query in the WHERE clause does not give the desired result, it needs to go in the ON clause.
Join<ReportEntity, ProductEntity> productJoin = root.join("products", JoinType.LEFT);
Predicate joinPredicate = criteriaBuilder.equal(root.get("id"), productJoin.get("reportId"));
Predicate rarePredicate = criteriaBuilder.equal(productJoin.get("rare"), 1);
productJoin.on(joinPredicate, rarePredicate);

How to filter all objects that contain multiple elements in a many-to-many relationship using Django's ORM?

I have two classes in Django linked through a ManyToManyField (the User class is the built-in User model):
from django.contrib.auth.models import User
class Activity():
participants = models.ManyToManyField(User, related_name='activity_participants')
I want to find all the activities in which two users are simultaneously participating.
I managed to solve my problem using a raw query (my app name is "core", therefore the "core" prefix in the table names):
SELECT ca.id FROM core_activity_participants AS cap, core_activity AS ca
INNER JOIN core_activity_participants AS cap2 ON cap.activity_id
WHERE cap.user_id == 1 AND cap2.user_id == 2
AND cap.activity_id == cap2.activity_id
AND ca.id == cap.activity_id
However, if possible, I'd like to avoid using raw queries, since it breaks uniformity from the rest of my app. How could I make this query, or one equivalent to it, using Django's ORM?
If you're using Django 1.11 or later the intersection queryset method will give you the records you want.
# u1 and u2 are User instances
u1_activities = Activity.objects.filter(participants=u1)
u2_activities = Activity.objects.filter(participants=u2)
common_activities = u1_activities.intersection(u2_activities)
Will produce a query something like this:
SELECT "core_activity"."id"
FROM "core_activity"
INNER JOIN "core_activity_participants"
ON ("core_activity"."id" = "core_activity_participants"."activity_id")
WHERE "core_activity_participants"."user_id" = 1
INTERSECT
SELECT "core_activity"."id"
FROM "core_activity"
INNER JOIN "core_activity_participants"
ON ("core_activity"."id" = "core_activity_participants"."activity_id")
WHERE "core_activity_participants"."user_id" = 2
You can also add extra querysets to the intersection if you want to check for activity overlap between more than 2 users.
Update:
Another approach, which works with older Django versions, would be
u1_activities = u1.activity_participants.values_list('pk', flat=True)
common_activities = u2.activity_participants.filter(pk__in=u1_activities)
Which produces a query like
SELECT "core_activity"."id"
FROM "core_activity"
INNER JOIN "core_activity_participants"
ON ("core_activity"."id" = "core_activity_participants"."activity_id")
WHERE (
"core_activity_participants"."user_id" = 2
AND "core_activity"."id" IN (
SELECT U0."id"
FROM "core_activity" U0
INNER JOIN "core_activity_participants" U1
ON (U0."id" = U1."activity_id")
WHERE U1."user_id" = 1
)
)

Querydsl: how to make left join by column

Im trying to match this SQL query in querydsl
SELECT tr.* FROM test.TRIP_REQ tr left outer join test.ADDR_BOOK ab on tr.REQ_USERID=ab.USER_ID
I know how to make left join query if you join into identity column but struggle to make it work with joining on 2 alternative columns. tr.REQ_USERID and ab.USER_ID are not identity columns
This is my querydsl:
QTripReq qTripReq = QTripReq.tripReq;
QAddressBook qABook = QAddressBook.addressBook;
JPAQuery query = new JPAQuery(entityManager);
query.from(qTripReq).leftJoin(qABook).on(qTripReq.requestorUser.id.eq(qABook.user.id)).list(qTripReq);
This throws error:
Path expected for join! [select tripReq from com.TripReq tripReq left join ADDR_BOOK addressBook with tripReq.requestorUser.id = addressBook.user.id where tripReq.assignedCompany.id = ?1]
You need to add a target entity path to leftJoin(), so that
QTripReq qTripReq = QTripReq.tripReq;
QAddressBook qABook = QAddressBook.addressBook;
JPAQuery query = new JPAQuery(entityManager);
query.from(qTripReq).leftJoin(qTripReq.addressBook, qABook).on(qTripReq.requestorUser.id.eq(qABook.user.id)).list(qTripReq);
Take a look on Using joins section in docs.

Convert a SQL query to LINQ query

I have the following SQL query
SELECT *
FROM LOC l
JOIN CLA c ON l.IdLoc = c.IdLoc
JOIN FA f on c.IdCla = f.IdCla
LEFT JOIN CON co ON f.IdCla = co.IdCla
AND co.DeletedDate IS NULL
AND co.IdUser = f.IdUser
WHERE f.IdUser = 7
AND f.DeletedDate IS NULL
I would like to convert it to LINQ but I'm absolutely not at ease with LEFT JOIN and "temp table" with LINQ.
Moreover, I tried to convert it but it seems it is impossible to create a join clause with a WHERE inside in LINQ (Linqer told me that and Linqpad doesn't seem able to convert from SQL to LINQ in free version)
Could you give me clue ?
Thanks a lot
I think you are looking for something like this. I left out the select clause so that you can pull out what you need. Things to note:
To join multiple columns, create anonymous types. The field names in the anonymous types must match.
To create a =NULL condition, create a variable name that matches the field name in the other entity. Set it =null but coerce it to the nullable data type of the field you are setting it equal to.
Edit: Updated query to move where clause to joins
from l in LOC
join c in CLA
on l.IdLoc equals c.IdLoc
join f in FA
on new { c.IdCla, IdUser = 7, DeletedDate = (DateTime?)null }
equals new { f.IdCla, f.IdUser, f.DeletedDate }
join co in CON
on new { f.IdCla, DeletedDate = (DateTime?)null, f.IdUser }
equals new { co.IdCla, co.DeletedDate, co.IdUser } into lj
from l in lj.DefaultIfEmpty()

Spatial Join in Entity Framework

I want to write a join statement in LINQ using the dbgeography's "Intersects" method (I am using EF June 2011 CTP). The problem is if I write something like this:
var joinQuery = from spQ in spatialTableQuery
join mnQ in MainQuery
on spQ.Polygon.Intersects(mnQ.PointGeography) equals 1
I get the following error:
The name 'mnQ' is not in scope on the left side of 'equals'. Consider
swapping the expressions on either side of 'equals'.
In SQL I have written a similar query as below so I know SQL suppports it:
SELECT * FROM Address a
INNER JOIN SPATIALTABLE b
WITH(INDEX(geog_sidx))
ON b.geom.STIntersects(a.PointGeography) = 1
Try something like this:
var joinQuery =
from spQ in spatialTableQuery
from mnQ in MainQuery
where spQ.Polygon.Intersects(mnQ.PointGeography) = 1