JPQL TREAT AS /LEFT OUTER JOIN

2019-05-09 22:07发布

I'm trying the "TREAT AS" function from JPA 2.1 (eclipselink) and I'm stuck with an error from JPA:

Exception Description: ReportQuery result size mismatch. Expecting [263], but retrieved [197]

Here is my JPQL query (I change some parts to be more explicit):

String jpql = "select distinct s, accountAD "
            + "from SheetAccountUser s "
            + "left join fetch s.userTarget target "
            + "left join TREAT(target.accounts AS ADAccount) accountAD ";

ADAccount is a subclass from AbstractAccount (@Inheritance(strategy = InheritanceType.JOINED). A User have a list of AbstractAccount.

I want to select sheets with the AD account of the userTarget. I want null if there is no userTarget or if the userTarget has no AD account (left join).

The problem comes from the treat operator. The SQL generated query have a left join between the AbstractAccount table and the ADAccount table. This cause the retrieve of one line per account type of the targetUser.

Here is the SQL query generated:

SELECT DISTINCT 
t0.Id, --etc
t6.Id, t6.name, --etc
t7.userId --etc
FROM sheet t0 
LEFT OUTER JOIN user t6 ON (t6.Id = t0.userTargetId),
account t7 LEFT OUTER JOIN ad_account t8 ON ((t8.userId = t7.userId) AND (t8.idApp = t7.idApp))
WHERE (t7.userId = t6.Id) AND (t7.DTYPE = 'ADAccount');

We can see the left outer join between account and ad_account. Also, the ad_account table is not present in the select clause. (The idApp field is a part of the primary key and maintain the unique (userId, idApp) constraint). I don't know if it's a problem with my understanding or JPA.

Thank you for helping me!

2条回答
太酷不给撩
2楼-- · 2019-05-09 22:38

The TREAT expression is meant as a way of allowing access a subclass' parameters within the query; filtering is a necessary byproduct but not the main intent. It is more along the lines where you might want a query for "People who are workers with a salary > 100k or people who are managers with the number of directs <10". In that case, a strict join between the people+employees and people+managers would hinder the query.

The TYPE expression allows you to control the filtering yourself so that you get the strict results you are looking for. Something like:

"select distinct s, accountAD "
            + "from SheetAccountUser s "
            + "left join fetch s.userTarget target "
            + "left join target.accounts accountAD where TYPE(accountAD) = ADAccount";

Might be more what you need. Remember that you will need to explicitly list any subclasses you want included though.

If you must use Treat to filter but want outer joins over target.accounts, try something like:

"select distinct s, accountAD "
                + "from SheetAccountUser s "
                + "left join fetch s.userTarget target "
                + "left join target.accounts accounts "
                + " join TREAT(accounts AS ADAccount) accountAD";

might work, though you may want to just use the TREAT within the where clause when needed.

查看更多
趁早两清
3楼-- · 2019-05-09 22:42
 "select distinct s, accountAD "
                + "from SheetAccountUser s "
                + "left join fetch s.userTarget target "
                + "left join target.accounts accountAD where TYPE(accountAD) = ADAccount";

This request doesn't return sheet that have a target without AD Account.

 "select distinct s, accountAD "
                + "from SheetAccountUser s "
                + "left join fetch s.userTarget target "
                + "left join target.accounts accounts "
                + " join TREAT(accounts AS ADAccount) accountAD";

This one generate the same SQL that my first jpql request and generate an Eclipselink error.

I also have multiple lines for one sheet if the target has one AD Account and at least one other Account type: One line with the AD Account attributes setted and others with null values (these different values block the distinct clause, so do the DTYPE value).

Fortunately, I just need 2 informations about AD Account: Its existence and one boolean "desactivated".

After more reflexion I had an idea:

    "select u, "
                // 0 if line with no target or no ADAccount or with another account type, else 1 (one 1 by sheet/target) 
                + "sum( "
                + "     case "
                + "     when accountAD.desactivated is not null then 1 "
                + "     else 0 "
                + "     end "
                + ") as ADAccountExists, "
                // the target have an AD Account desactivated
                + "sum( " 
                + "     case "
                + "     when compteAD.desactivated = 1 then 1 " 
                + "     else 0 "
                + "     end" 
                + ") as ADAccountDesactivated " 
                + "from SheetAccountUser s "
                + "left join fetch s.userTarget target "
                + "left join treat(target.accounts as ADAccount) accountAD "
                + "group by s,target " //the group by maintains unicity of the sheets

It's working fine but it's very ugly. I hoppe someday to find another method.

查看更多
登录 后发表回答