JPA N+1 issue with inheritance - jpa

If I am in the situation where I have a n+1 problem I normally use the JPA "join fetch" keyword if it is only one indirection (i. e. select p from Person p join fetch p.address) or if it is more than one indirection I use a JPA provider-proprietary query hint, which is in my case eclipselink (i. e. select p from person with the query hint eclipselink.join-fetch=p.address.city). Thats well explained in the article Java Persistence Performance.
Anyway, lately I stumbled over a datamodel where two entities where subclassed by another one.
I have an Account which owns a Contact. A Contact itself is a abstract superclass which is extended by a Person or a Company. And a Person does have a relationship to a list of Hobbyies.
#Entity
public class Account
{
#OneToOne
private Contact contact;
}
#Entity
#Inheritance(strategy = InheritanceType.JOINED)
public abstract class Contact {}
#Entity
public class Person extends Contact
{
#OneToMany
private List<Hobby> hobbies;
}
#Entity
public class Company extends Contact {}
I need to load a list of Accounts, select a from Account a. I surely ran into the n+1 problem, because of the hobbies. No problem I thought, with the two above mentioned and well probed solutions I can cope with the n+1 problem. But soon I realized that does not work.
select a from Account a join fetch a.contacts does not fetch the hobbies. Nor does select a from Account a with the query hint eclipselink.join-fetch=a.contacts. And select a from Account a with the query hint eclipselink.join-fetch=a.contacts.hobbies throws a
... navigated to a non-existent relationship-Exception.
I also tried to use the JPA treat-function i. e. select a from Account a join fetch treat(a.contact as Person) p join fetch p.hobbies, but that does not fetch the hobbies and only fetches the Persons and not the Companies along.
Does anyone have an idea how to use join fetch or eclipselink query hints to accomplish such a query optimization?
EDIT
In order to answer the comment by Chris.
The #BatchFetch annotation at the hobbies does not have any impact on the query. There is no difference to the queries without it.
Here are the sqls generated by each query with the #BatchFetch annotation
select a from Account a
1 SELECT id, contact_id FROM account
2 SELECT DISTINCT DTYPE FROM contact WHERE (ID = ?)
3 SELECT t0.ID, t0.DTYPE, t1.ID, t1.FOUNDED FROM contact t0, company t1 WHERE ((t0.ID = ?) AND ((t1.ID = t0.ID) AND t0.DTYPE = ?))
4 SELECT DISTINCT DTYPE FROM contact WHERE (ID = ?)
5 SELECT t0.ID, t0.DTYPE, t1.ID, t1.NAME FROM contact t0, person t1 WHERE ((t0.ID = ?) AND ((t1.ID = t0.ID) AND (t0.DTYPE = ?)))
6 SELECT ID, OUTDOOR, PERSON_ID FROM hobby WHERE (PERSON_ID = ?)
... (repetition of lines 2 to 6)
select a from Account a join fetch a.contacts
SELECT t3.ID, t3.contact_id, t0.ID, t0.DTYPE, t1.NAME, t2.ID, t2.FOUNDED FROM contacts t0 LEFT OUTER JOIN person t1 ON (t1.ID = t0.ID) LEFT OUTER JOIN company t2 ON (t2.ID = t0.ID), account t3 WHERE (t0.ID = t3.contact_id)
SELECT ID, OUTDOOR, PERSON_ID FROM hobby WHERE (PERSON_ID = ?)
SELECT ID, OUTDOOR, PERSON_ID FROM hobby WHERE (PERSON_ID = ?)
SELECT ID, OUTDOOR, PERSON_ID FROM hobby WHERE (PERSON_ID = ?)
SELECT ID, OUTDOOR, PERSON_ID FROM hobby WHERE (PERSON_ID = ?)
SELECT ID, OUTDOOR, PERSON_ID FROM hobby WHERE (PERSON_ID = ?)
...

Related

Selecting distinct values

The domain is:
company (id, name, adress)
employee (id, name, adress, company_id, expertise_id)
dependantrelative (id, name, employee_id)
expertise (id, name, class)
I want to know how to get the number of dependantrelatives of each employee who are unique experts in their respective companies.
The Query below does not return the correct answer. Can you help me?
SELECT DISTINCT dependantrelative.employee_id
, COUNT(*) AS qty_dependantrelatives
FROM dependantrelative
INNER JOIN employee
ON employee.id = dependantrelative.employee_id
GROUP BY dependantrelative.employee_id
I just tried out the Query below and it works, but I want to know if there is a faster and simple way of getting the answer.
SELECT employee.id
,COUNT(dependantrelative.employee_id) AS qty_dependantrelatives
FROM (
SELECT employee.company_id
, employee.expertise_id AS expert
, COUNT(employee.expertise_id)
FROM employee
GROUP BY employee.company_id
, employee.expertise_id
HAVING COUNT(employee.expertise_id)<2
) AS uniexpert
LEFT JOIN employee
ON employee.expertise_id = uniexpert.expert
LEFT JOIN salesorderdetail
ON dependantrelative.employee_id = employee.id
GROUP BY employee.id
ORDER BY employee.id

postgresql left join but dont fetch if matching condition found

I have a bit of a complicated scenario. I have two tables, employee and agency. An employee may or may not have an agency, but if an employee has an agency I want the select clause to check another condition on the agency, but if the employee does not have an agency its fine I want to fetch the employee. I'm not sure how to write the select statement for this. This is what I have come up with so far
select * from employee e left join
agency a on a.id = e.agencyID and a.valid = true;
However the problem with this is that it fetches both employees without agencies which is fine, but it also fetches employees with agencies where a.valid = false. The only option I can think of is to do an union but I'm looking for something more simpler.
A UNION could actually be the solution that performs best, but you can write the query without UNION like this:
select *
from employee e
left join agency a
on a.id = e.agencyID
where coalesce(a.valid, true);
That will accept agencies where valid IS NULL, that is, result rows where the agency part was substituted with NULLs by the outer join.
You want except the condition that both table match(agency.id = employee.agencyID) and also agency.id is false. The following query will express the condition.
SELECT
e.*,
a.*
FROM
employee e
LEFT JOIN agency a ON a.id = e.agencyID
WHERE
NOT EXISTS (
SELECT
1
FROM
agency
WHERE
a.id = e.agencyID
AND a.valid IS FALSE)
ORDER BY
e.id;

SQL query involving specific count with distinct

I have these tables:
person (id primary key, name)
money (acct primary key, loaner)
loan (id primary key, acct)
How would I create a SQL query that shows for each loaner the names of persons who took more than four loans from that specific loaner? And I want the 4 persons that he loaned to be different with each other.
SELECT
p.id, p.name, m.loaner, COUNT(*)
FROM
person p
INNER JOIN
loan l ON p.id = l.id
INNER JOIN
money m ON l.acct = m.acct
GROUP BY
id, name, lower
HAVING
COUNT(*) = 4
With this query you can find the first part of the question - what should I add?
I would try this out and see what happens :D
SELECT distinct *
FROM person as p_loaner_detailed
WHERE p_loaner_detailed.id in (
SELECT loanerId
FROM (
SELECT p.id, p.name, m.loaner as loanerId
COUNT(*)
FROM person p
INNER JOIN loan l
ON p.id = l.id
INNER JOIN money m
ON l.acct = m.acct
GROUP BY id, name, loanerId
HAVING COUNT(*) > 4
)
)

mysql get records associated directly and indirectly

I have 3 tables let's say
manager(id, name) teamleader(id, name, associated)
employee(id,name,code)
In teamleader table associated is foreign key of manager table.
In employee table code is either id of manager or teamleader .
Employee is under manager as well as teamleader.
I want to write a query to find employee which directly under by manager and under teamleader which are associated with that manager.
Please help me to solve my issue.
If i understood you question correctly you can try this:
SELECT
e.id,
e.name,
IF(m1.id IS NULL,m1.id,m2.id) as manager_id
FROM
employee e
LEFT JOIN manager m1
ON m1.id = e.code
LEFT JOIN teamleader t1
ON t1.id = e.code
LEFT JOIN manager m2
ON m2.id = t1.associated
WHERE
(
m1.id IS NOT NULL
OR
(
t1.id IS NOT NULL
AND
m2.id IS NOT NULL
)
)
If you want to put a condition on a specific manager just add at the end :
HAVING manager_id = x
where x is the manager id that you wanted

How do you perform a search on a 1-to-many relationship when the criteria could be on either table?

I am using t-sql. I have what I thought would be an easy search. There is a 1-to-many relationship between SalesPerson and TradeShow. 1 salesperson could have gone to many trade shows. I need to be able to search on the SalePerson. I also need to be able to search on the LAST trade show they attended. I thought I would be able to do simple join and group on their last trade show, but I can not display the City or State.
SELECT SalePersonID, FirstName, LastName, TradeShow.DateLastWent
FROM SalesPerson INNER JOIN
(SELECT SalePersonID, MAX(DateLastWent) AS DateLastWent
FROM TradeShow
GROUP BY SalesPersonID) AS TradeShow ON SalesPerson.SalePersonID= TradeShow.SalePersonID
This workds, but the Tradeshow also has city and State. I need to be able to search on and display city and state. But if I include them in the subquery, I have to include thm in an aggregate function, and if I do that, I get the incorrect city and state.
The tables are simple
SALEPERSON
salespersonID PK
firstname
lastname
TRADESHOW
tradeshowID PK
datelastwent
city
state
salespersonID FK
Re-word it: what you want is the salesperson, plus the information from the last show that they have been to.
Select
SalePersonID,
FirstName,
LastName,
TradeShow.DateLastWent,
TradeShow.City,
TradeShow.State
From
SalesPerson
Inner Join TradeShow
On SalesPerson.SalePersonID = TradeShow.SalePersonID
Where
TradeShow.TradeShowID =
(Select Top 1 Latest.TradeShowID
From TradeShow As Latest
Where SalesPerson.SalePersonID = Latest.SalePersonID
Order By Latest.DateLastWent Desc)
You can join TradeShow twice :
SELECT SalePersonID, FirstName, LastName, TS1.DateLastWent,
TS2.City, TS2.State
FROM SalesPerson INNER JOIN
(SELECT SalePersonID, MAX(DateLastWent) AS DateLastWent
FROM TradeShow
GROUP BY SalesPersonID
) AS TS1 ON (SalesPerson.SalePersonID= TradeShow.SalePersonID)
INNER JOIN TradeShow TS2 ON
(TS2.SalePersonID = TS1.SalePersonID AND TS2.DateLastWent = TS1.DateLastWent)
WHERE TS2.City = 'CityName'
There is likely a more elegant way to solve this, but my first thought is to simply grab the newest TradeShow record to join with
SELECT SalePersonID, FirstName, LastName, TradeShow.DateLastWent
FROM SalesPerson
INNER JOIN (
SELECT *
FROM (
SELECT TradeShowId, DateLastWent, City, State, SalesPersonId
FROM TradeShow
ORDER BY datelastwent DESC
)
WHERE ROWNUM <= 1
) ON SalesPerson.SalesPersonId = TradeShow.SalesPersonId
Edit
Oops... been playing with Oracle too much
ROW_NUMBER() OVER(order by date) or SELECT TOP X
would be thw SQL Server way for doing this... don't have an instance of SQL-Server running, but pretty sure the syntax ends up being something like
SELECT SalePersonID, FirstName, LastName, TradeShow.DateLastWent
FROM SalesPerson
INNER JOIN (
SELECT TradeShowId, DateLastWent, City, State, SalesPersonId, ROW_NUMBER() OVER(PARTITION BY TradeShow.SalesPersonId ORDER BY DateLastWent DESC) RowNumber
FROM TradeShow
) ON SalesPerson.SalesPersonId = TradeShow.SalesPersonId AN TradeShow.RowNumber = 1