JPA Criteria JOIN et ON - jpa

I have been searching for a while, I haven't found any responses to my problem.
I am making an application in JSF, EclipseLink, Glassfish in which the user can have one or more groups. The application contains modules and permissions as follows:
Module => permission => utilisateur
Module => permission => groupe => utilisateur
I am looking for a criteria query (not in jqpl) to get all of the modules of a user. I have constructed my SQL query, which functions very well like this:
SELECT *
FROM core_module m
WHERE m.id IN (
SELECT m.id
FROM core_module m
INNER JOIN modules_permissions mp
ON mp.modules_id = m.id
INNER JOIN core_permission p
ON p.id = mp.permissions_id
INNER JOIN users_permissions up
ON p.id = up.permissions_id
INNER JOIN core_user u
ON u.id = 1
)
OR m.id IN (
SELECT m.id
FROM core_module m
INNER JOIN modules_permissions mp
ON m.id = mp.modules_id
INNER JOIN core_permission p
ON p.id = mp.permissions_id
INNER JOIN groups_permissions gp
ON gp.permissions_id = p.id
INNER JOIN core_group g
ON g.id = gp.groups_id
INNER JOIN users_groups ug
ON g.id = ug.groups_id
INNER JOIN core_user u
ON u.id = ug.users_id
WHERE u.id = 1
)
I am trying somehow to transform this into a criteria query without great success. I have just started the first part of the query, which gives me the modules immediately accessible to a user. I have the impression that I am missing some conditions or joins, as I am getting 2 results when I shouldn't be getting any. In effect, I haven't been able to include my ON clauses, so I can't change my to joins in my query since the entities don't exist: the table modules_permissions is created by a #ManyToMany in my entities...
Here is my first draft subquery:
List<Module> modules;
CriteriaBuilder cb = getEntityManager().getCriteriaBuilder();
CriteriaQuery<Module> query = cb.createQuery(Module.class);
Root<Module> root_module = query.from(Module.class);
Root<User> root_user = query.from(User.class);
Root<Permission> root_permission = query.from(Permission.class);
Root<Group> root_group = query.from(Group.class);
Join<Module, Permission> join_module_permission = root_module.join(Module_.permissions);
Join<Permission, User> join_permission_user = root_permission.join(Permission_.users);
query.where(cb.equal(root_user.get(User_.email), current.getEmail()));
modules = getEntityManager().createQuery(query).getResultList();

Try turning EclipseLink logging to finest so you can see the SQL that is generated. You might also use JPQL as it is closer to the SQL, and then convert to criteria once you have something working. Something like
"select m from Module m where
m in (select m1 from Module m1 join m1.permissions p join p.users u where u.id = 1) or
m in (select m2 from Module m2 join m2.permissions p2 join p2.groups g join g.users u2 where u2.id =1)"
The key here is that you use the entity relationships in the criteria query so that it matches what you want in the generated SQL. Don't use multiple from clauses when you only want those tables and their joins included in an inner query. If the above JPQL works and the relationships exist, something like this is equivalent:
CriteriaBuilder cb = getEntityManager().getCriteriaBuilder();
CriteriaQuery<Module> query = cb.createQuery(Module.class);
Root<Module> root_module = query.from(Module.class);
Path path = root_module.get(Module_.id);
Subquery<Integer> subquery = criteriaQuery.subquery(Integer.class);
Root subRoot = subquery.from(Module.class);
subquery.select(subRoot.get((Module_.id)));
Join user1 = subRoot.join(Module_.permissions).join(Permission_.users);
subquery.where(criteriaBuilder.equals(user1.get(User_.id), 1));
Subquery<Integer> subquery2 = criteriaQuery.subquery(Integer.class);
Root subRoot2 = subquery.from(Module.class);
subquery2.select(subRoot2.get((Module_.id)));
Join user2 = subRoot2.join(Module_.permissions).join(Permission_.groups).join(Group_.users);
subquery2.where(criteriaBuilder.equals(user2.get(User_.id), 1));
query.where(criteriaBuilder.or(criteriaBuilder.in(path ).value(subquery),
criteriaBuilder.in(path ).value(subquery2)));

The final answer is :
in JPQL :
#NamedQuery(name = "core_user.findModuleAssociated", query = "select m from Module m where m.id in (select m1.id from Module m1 join m1.permissions p join p.user u where u.id = :userid) or m.id in (select m2.id from Module m2 join m2.permissions p2 join p2.group g join g.users u2 where u2.id = :userid)")
in criteria query :
List<Module> modules;
CriteriaQuery<Module> query = cb.createQuery(Module.class);
Root<Module> root_module = query.from(Module.class);
Path path = root_module.get(Module_.id);
Subquery<Integer> subquery = query.subquery(Integer.class);
Root subRoot = subquery.from(Module.class);
subquery.select(subRoot.get((Module_.id)));
Join user1 = subRoot.join(Module_.permissions).join(Permission_.user);
subquery.where(cb.equal(user1.get(User_.id), current.getId()));
Subquery<Integer> subquery2 = query.subquery(Integer.class);
Root subRoot2 = subquery.from(Module.class);
subquery2.select(subRoot2.get((Module_.id)));
Join user2 = subRoot2.join(Module_.permissions).join(Permission_.group).join(Group_.users);
subquery2.where(cb.equal(user2.get(User_.id), current.getId()));
query.where(cb.or(cb.in(path).value(subquery), cb.in(path).value(subquery2)));
modules = getEntityManager().createQuery(query).getResultList();
Special thanks to Chris

Related

How do you use group by and having clause in EF with parent/child relationship?

How can I write a linq to entities query that includes a group by and a having clause?
For example in SQL:
SELECT * FROM dbo.tblParent p
INNER JOIN
(
SELECT a.ID
FROM dbo.tblParent a
join dbo.tblChild c ON a.ID = c.FkParentID
WHERE a.ColValue = 167
GROUP BY A.ID
HAVING COUNT(c.ID) = 1
) t ON p.ID = t.ID
I found my own answer.
// this is far from pretty but it works as far as I can tell
Apps = (from x in context.tblParents
join t in (
from p in context.tblParents
join c in context.tblChilds
on p.ID equals c.FkParentID
where p.ColValue == 167
group c.ID by p.ID into grouped
where grouped.Count() == 1
select new { grouped.Key }) on x.ID equals t.Key
select x);

Trying to . convert a value to a name during output

the values returned in column a.user_id are id numbers. I would like to return them as a name or initials e.g. 1 = Chris O or C.ONeill
I have tried CASE function but did not know where to put it
SELECT DISTINCT
sb.start_time::date AS shift_date,
i.title AS industry,
wl.name AS venue_name,
w.first_name,
w.last_name,
MIN(cs.start_time::date) AS first_completed_shift,
i.title AS industry,
w.interviewed_on AS induction_date,
d.issue_date AS edbs_issue_date,
d.created_at AS date_dbs_added,
a.user_id --IS IT POSSIBLE TO GET THESE AS VALUES,e.g. a.user_id '1' = 'Chris O or C.ONeill' USING USER NAMES INSTEAD OF USER ID?
FROM shift_bookings sb
JOIN jobs j ON sb.job_id = j.id
JOIN listings l ON j.listing_id = l.id
JOIN work_locations wl ON l.venue_id = wl.id
JOIN workers w ON sb.worker_id = w.id
JOIN completed_shifts cs ON w.id = cs.worker_id
JOIN documents d ON w.id = d.documentable_id
JOIN audits a ON d.id = a.auditable_id
JOIN industries i ON j.industry_id = i.id
WHERE sb.shift_id IN (253106)
AND d.document_type_id = 33
AND a.auditable_type = 'Document'
GROUP BY
sb.start_time::date,
wl.name,
w.first_name,
w.last_name,
i.title,
w.interviewed_on,
d.issue_date,
d.created_at,
a.user_id
a.user_id output will be something else I can set

sql multiple join (right. left, inner) to mongodb aggregate

//I have a sql like this
SELECT
x.attr1,
p.attr2,
p2.attr3,
o.attr4,
p2.attr5,
a2.attr6
FROM TABLE_O o
RIGHT JOIN TABLE_A a ON a.id = o.id AND a.id2 IS NULL
LEFT JOIN TABLE_A a2 ON a2.id = o.id AND a2.id2 = a.id3
LEFT JOIN TABLE_P p ON a.id4 = p.id4
LEFT JOIN TABLE_P p2 ON a2.id4 = p2.id4
INNER JOIN TABLE_X x ON a.id3 = x.id3
WHERE o.type = 'someText' AND a2.id4
IN ( SELECT id3 FROM TABLE_P WHERE TYPE = "anotherText")
AND x.attr1 = "someText"
//I need an idea about how can i do this on mongo aggregates, thanks.
nosql is a non-relational database type. but The following link may be useful.
mongodb aggregation

Postgres UPDATE statement

I have moved from mysql to psql, but find it hard to get my head around the UPDATE statement using multiple left joins.
How would you rewrite this in Postgres? (I am using postresql 9.4)
update task t
left join project p on t.project_id = p.id
left join client c on t.client_id = c.id
left join user u on t.user_id = u.id
set t.project_name = p.name,
t.client_name = c.name,
t.user_name = u.name;
Any pointer will be welcome.
Here you go:
WITH task_data AS (
SELECT t.id,
p.name AS project_name,
c.name AS client_name,
u.name AS user_name
FROM task t
LEFT JOIN project p ON t.project_id = p.id
LEFT JOIN client c ON t.client_id = c.id
LEFT JOIN "user" u ON t.user_id = u.id
)
UPDATE task t
FROM task_data d
SET
project_name = d.project_name,
client_name = d.client_name,
user_name = d.user_name
WHERE t.id = d.id
I would be curious to see if there is a more efficient way

TSQL efficiency - INNER JOIN replaced by EXISTS

Can the following be rewritten to be more efficient?
I would use EXISTS if I didn't need fields from country but I do need those fields, and am not sure how to write this to make it more efficient.
SELECT distinct
p.ProvinceID,
p.Abbv as RegionCode,
p.name as RegionName,
cn.Code as CountryCode,
cn.Name as CountryName
FROM dbo.provinces AS p
INNER JOIN dbo.Countries AS cn ON p.CountryID = cn.CountryID
INNER JOIN dbo.Cities c on c.ProvinceID = p.ProvinceID
INNER JOIN dbo.Listings AS l ON l.CityID = c.CityID
WHERE l.IsActive = 1 AND l.IsApproved = 1
There are two things to note:
You're joining to dbo.Listings which results in many records, so you need to use DISTINCT (usually an expensive operator)
For any tables with columns not in the select you can move into an EXISTS (but the query planner effectively does this for you anyway)
So try this:
SELECT
p.ProvinceID,
p.Abbv as RegionCode,
p.name as RegionName,
cn.Code as CountryCode,
cn.Name as CountryName
FROM dbo.provinces AS p
INNER JOIN
dbo.Countries AS cn
ON p.CountryID = cn.CountryID
WHERE EXISTS (SELECT 1 FROM
dbo.Listings l
INNER JOIN dbo.Cities c
on l.CityID = c.CityID
WHERE c.ProvinceID = p.ProvinceID
AND l.IsActive = 1 AND l.IsApproved = 1
)
Check the query plans before and after - the query planner might be smart enough to do this anyway, but you have removed your distinct
The following will often perform even better by providing the optimizer more useful information:
SELECT
p.ProvinceID,
p.Abbv as RegionCode,
p.name as RegionName,
cn.Code as CountryCode,
cn.Name as CountryName
FROM dbo.provinces AS p
INNER JOIN
dbo.Countries AS cn
ON p.CountryID = cn.CountryID
INNER JOIN (
SELECT
p.ProvinceID
FROM
dbo.Listings l
INNER JOIN dbo.Cities c
on l.CityID = c.CityID
WHERE l.IsActive = 1 AND l.IsApproved = 1
GROUP BY
p.ProvinceID
) list
on list.ProvinceID = p.ProvinceID