Doctrine manyToMany join, not working - join

My selection not working, why??? I see others posts, but don't solve this problem.
symfony2 doctrine join
Doctrine Join DQL
If i use join like this:
->join('User', 'u')
The result, is 4 values, where i expected 2...
Models:
class User
{
...
/**
* #ManyToMany(targetEntity="UserGroup", mappedBy="users", cascade={"all"})
* #JoinTable(name="users_user_group")
*/
protected $userGroups;
and
class UserGroup
{
...
/**
* #ManyToMany(targetEntity="User", inversedBy="userGroups", cascade={"all"})
* #JoinTable(name="users_user_group")
**/
public $users;
QueryBuilder:
$queryBuilder = $this->getController()->getRepository()
->createQueryBuilder('ug')
->select('ug, u')
->leftJoin('User', 'u', 'WITH', 'ug.id = u.userGroups')
->andWhere('u.id IN (:ids)')
->setParameter('ids', $usersId);
I receive this message:
[Semantical Error] line 0, col 63 near 'userGroups WHERE': Error: Invalid PathExpression. StateFieldPathExpression or SingleValuedAssociationField expected.
This SQL work, why DQL not?
SELECT
*
FROM
users_user_group ug
LEFT JOIN
user u ON ug.user_id = u.id
WHERE
ug.user_id IN (1 , 2)

I changed this line:
->leftJoin('User', 'u', 'WITH', 'ug.id = u.userGroups')
for it:
->join('User', 'u', 'WITH', 'ug.id = u.id')
\o/

Related

Exists clause in criteria

Consider this query:
select user_id
from user_role a
where a.role_id = -1
and not exists (select 1
from user_role b
where b.role_id != a.role_id
and b.user_id = a.user_id);
I'm trying to recreate it using gorm criterias. Is it possible? How can I add the exists clause?
UPDATE
I've been trying to resolve this way:
UserRole.createCriteria().list{
eq('role', roleObj)
createAlias('user', 'u')
not{
inList('u.id', {
not {
eq('role', roleObj)
}
projections {
property 'user.id'
}
})
}
}
Still not working. I'm gettins this error when executing it:
DUMMY$_closure1_closure2_closure3_closure4 cannot be cast to java.lang.Long
I don't understand the error message. The inner criteria returns the list of ids and if I replace the inner criteria with a list of longs it works. Any clue?
Thanks in advance
Not tested : Try Sql restriction
UserRole.createCriteria().list {
and{
eq('roleId',-1)
sqlRestriction(" not exists(select 1
from UserRole b
where ............
)")
}
}
-- Hoping you have named your domain as UserRole and column name named as roleId.

criteria uses "inner join" instead "left join" approach by default making my query work not the way I planned

The question is: how do I make GORM generate left joins instead of inner joins in this particular example?
Testbed:
Given classes A, B and C:
class A{
B someObject
}
class B{
C importantObject
}
class C{
boolean interestingFlag
}
I want to list all the elements of A class that:
their B.C object is null OR
their B.C object interestingFlag value is false
What I tried so far:
This approach produces correct list of A where B.C is null (conditional 2 commented out) OR correct list of A where B.C.interestingFlag = false (no matter if conditional 1 is commented out or not). When both conditionals are uncommented it returns only a list of elements where A.B.C.interestingFlag = false (A.B.C = null conditional is ignored)
// approach 1 (conditional 1 is ignored)
def result = A.withCriteria{
someObject{
or{
isNull('importantObject') // conditional 1, works well when conditional 2 is commented out
importantObject{
eq('interestingFlag', false) // conditional 2, works well alone, discards conditional 1 when both of them are uncommented
}
}
}
}
Edit:
As requested in comment I'm pasting a hibernate generated sql:
Hibernate: select this_.id as id1_2_, this_.version as version1_2_,
this_.some_object_id as some3_1_2_, someobject1_.id as id2_0_,
someobject1_.version as version2_0_, someobject1_.important_object_id as
important3_2_0_, importanto2_.id as id0_1_, importanto2_.version as version0_1_,
importanto2_.interesting_flag as interest3_0_1_ from a this_
inner join b someobject1_ on this_.some_object_id=someobject1_.id
inner join c importanto2_ on someobject1_.important_object_id=importanto2_.id
where ((someobject1_.important_object_id is null or (importanto2_.interesting_flag=?)))
When I copy and paste it in the pgAdmin query tool directly with a few things changed (inner joins changed to left joins, and provided the interestingFlag = "false" parameter) everything works as I wanted (I get both A.B.C = null and A.B.C.importantFlag = false objects)
Hibernate: select this_.id as id1_2_, this_.version as version1_2_,
this_.some_object_id as some3_1_2_, someobject1_.id as id2_0_,
someobject1_.version as version2_0_, someobject1_.important_object_id as
important3_2_0_, importanto2_.id as id0_1_, importanto2_.version as version0_1_,
importanto2_.interesting_flag as interest3_0_1_ from a this_
left join b someobject1_ on this_.some_object_id=someobject1_.id
left join c importanto2_ on someobject1_.important_object_id=importanto2_.id
where ((someobject1_.important_object_id is null or (importanto2_.interesting_flag=false)))
Tested and working solution:
def result = A.withCriteria{
createAlias('someObject', 'so', CriteriaSpecification.LEFT_JOIN)
createAlias('so.importantObject', 'imp', CriteriaSpecification.LEFT_JOIN)
or {
isNull('so.importantObject')
eq('imp.interestingFlag', false)
}
}
Solution update as suggested in comment:
def result = A.withCriteria{
createAlias('someObject', 'so', JoinType.LEFT_OUTER_JOIN)
createAlias('so.importantObject', 'imp', JoinType.LEFT_OUTER_JOIN)
or {
isNull('so.importantObject')
eq('imp.interestingFlag', false)
}
}
Use left join to achieve this. This should work, but I haven't tested it live.
def result = A.withCriteria{
someObject {
createAlias("importantObject", "io", CriteriaSpecification.LEFT_JOIN)
or{
isNull('importantObject') // conditional 1
eq('io.interestingFlag', false) // conditional 2
}
}
}
Eidt: based on this post: http://grails.1312388.n4.nabble.com/CriteriaBuilder-DSL-Enhancements-td4644831.html this should also work:
def result = A.withCriteria{
someObject {
isNull('importantObject') // conditional 1
importantObject(JoinType.LEFT) {
eq('interestingFlag', false) // conditional 2
}
}
}
Please post your results on both solutions.
Edit 2: This is a query that is exactly a situation you've described, but differs from your examples. You must start from here, use showSql to debug generated SQLs and fiddle with left joins.
def result = A.withCriteria{
or {
isNull('someObject')
eq('someObject.importantObject.interestingFlag', false)
}
}
I was able to achieve left outer joins only with HQL
Class Transaction {
String someProperty
static hasMany = [reviews: Review]
static hasOne = [reviewQueue: ReviewQueue]
}
Class ReviewQueue {
Transaction transaction
Boolean isComplete
Boolean isReady
}
Class Review {
Transaction transaction
String reviewResult
}
def list = Transaction.executeQuery(
"select t.id, rq.isReady, rq.isComplete, count(r.transaction.id) " +
"from Transaction t " +
"join t.reviewQueue rq " +
"left outer join t.reviews r " +
"where rq.isComplete = false " +
"and rq.isReady = true " +
"group by t.id " +
"having count(r.transaction.id) = 0 " +
"order by rq.transaction.id ",
[max: 10, offset: 0])

Doctrine 2 JOIN ON error

I try to execute this query in my CompanyRepository
$qb = $this->_em->createQueryBuilder();
$qb->select(array('c', 'ld'))
->from('Model\Entity\Company', 'c')
->leftJoin('c.legaldetails', 'ld', \Doctrine\ORM\Query\Expr\Join::ON, 'c.companyid=ld.companyid');
$query = $qb->getQuery();
echo($query->getSQL());
When I try to do it I having error:
Fatal error: Uncaught exception 'Doctrine\ORM\Query\QueryException' with message '[Syntax Error] line 0, col 69: Error: Expected end of string, got 'ON'' in /home/raccoon/web/freetopay.dev/www/class/new/library/Doctrine/ORM/Query/QueryException.php on line 42
These are my models:
<?php
namespace Model\Entity;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
/**
* Company
*
* #ORM\Table(name="Company")
* #ORM\Entity(repositoryClass="\Model\Repository\CompanyRepository")
*/
class Company
{
/**
* #var integer $companyid
*
* #ORM\Column(name="CompanyID", type="integer", nullable=false)
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $companyid;
/**
* #var \Model\Entity\LegalDetails $legaldetails
*
* #ORM\OneToOne(targetEntity="\Model\Entity\Legaldetails", mappedBy="companyid")
*/
private $legaldetails;
//other fields
public function __construct()
{
$this->legaldetails = new ArrayCollection();
}
//setters and getters
and legaldetails entity:
<?php
namespace Model\Entity;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
/**
* Legaldetails
*
* #ORM\Table(name="LegalDetails")
* #ORM\Entity
*/
class Legaldetails
{
/**
* #var integer $legalid
*
* #ORM\Column(name="LegalID", type="integer", nullable=false)
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $legalid;
/**
* #var \Model\Entity\Company $company
*
* #ORM\Column(name="CompanyID", type="integer", nullable=false)
* #ORM\OneToOne(targetEntity="\Model\Entity\Company", inversedBy="companyid")
* #ORM\JoinColumn(name="companyid", referencedColumnName="companyid")
*/
private $company;
What is wrong?
For those who came here with the question about "Expected end of string, got 'ON'", but could not find the right answer, as I couldn't (well, there is an answer, but not exactly about QueryBuilder). In general, yes, you don't need to specify the joining columns. But what if you need to add extra filtering. For example, I was looking to add an extra condition (to allow nulls in join).
The problem here is that even though the constant Join::ON exists (and comments in Join expression mention it as well), there is no ON in DQL. Instead, one should use WITH (Join::WITH).
Here is my usage example:
$qb->leftJoin('p.metadata', 'm', Join::WITH, "IFNULL(m.name, '') = 'email'");
P.S. Predicting questions about IFNULL() - it is a Benjamin Eberlei's Doctrine extension.
There's a pretty clear explanation about how JOIN's work with DQL here:
With DQL when you write a join, it can be a filtering join (similar to the concept of join in SQL used for limiting or aggregating results) or a fetch join (used to fetch related records and include them in the result of the main query). When you include fields from the joined entity in the SELECT clause you get a fetch join
this should be enough to get what you want (info about all companies with legal info loaded):
$query = $em->createQuery('SELECT c, ld FROM \Model\Entity\Company c JOIN c.legaldetails ld');
$companies = $query->getResult(); // array of Company objects with the legaldetails association loaded
EDIT:
i used a regular join in my query, so companies with no legal info won't be returned in the query. if you want ALL companies even though they have no legal info loaded you should try with the left join as you were doing
We both thought in terms of SQL. But in DQL WITH is used instead of ON. example
Edit:
If you know SQL, why you don't use query such as:
$query = $this->getEntityManager()->createQuery('
SELECT t...
');
Put there the SQL that you think should be there, check it. If it works - the problem is in Doctrine code, if not - the error is in SQL/DQL

Subquery Doctrine Couldn't find class

I'm trying to create a query like this one :
$q = Doctrine_Query::create()
->select('p.nombre')
->addSelect('(select count(*) from alojamiento a left join
localidad l on a.localidad_id=l.id where p.id=l.provincia_id and
a.activo=true)')
->from('provincia p');
but it fails : error 500, couldn't find class a.
And :
$q = Doctrine_Query::create()
->select('nombre')
->addSelect('(select count(*) from alojamiento left join localidad on
alojamiento.localidad_id=localidad.id where
provincia.id=localidad.provincia_id and alojamiento.activo=true)')
->from('provincia');
leads to : SQLSTATE[42S22]: Column not found: 1054 Unknown column 'a.localidad_id' in 'on clause'.
Any help would be greatly appreciated !
assuming you have the alojamiento.localidad relationship configured in your doctrine models, you could try this:
$q = Doctrine_Query::create()
->select('p.nombre')
->addSelect('(select count(*) from Alojamiento a
left join a.Localidad l
where Provincia.id = Localidad.provincia_id
and l.activo=true) as count')
->from('Provincia p');

LINQ to Entites: how can I implement this complex t-sql in LINQ (multiple joins, DISTINCT, NOT EXISTS)?

I have a complex query that I'm trying to reproduce in LINQ to Entities, but I'm not there yet - is it possible?
The t-sql query looks like:
select distinct
C.id,
L.id
from
dp
join L on L.fk = DP.id
join M on ( M.l_code = L.l_code and M.dp_code = DP.dp_code )
join C on C.c_code = M.c_code
where not exists ( select id from map where map.cid = c.id and map.lid = l.id )
Tables look like:
DP:
id (pk)
dp_code (varchar)
L:
id (pk)
fk (fk to DP.ID)
l_code (varchar)
M:
id (pk)
l_code (varchar, matches value in L.l_code)
dp_code (varchar, matches value in DP.dp_code)
c_code (varchar, matches the value in C.c_code)
C:
id (pk)
c_code (varchar)
MAP:
id (pk)
cid (fk to C.id)
lid (fk to L.id)
My LINQ looks like:
IQueryable foo = from dp in ctx.DP
from l in dl.L
join m in ctx.M on l.l_code equals m.m_code
// Q1: how can I add: AND m.dp_code = dp.dp_code
join c in ctx.C on m.c_code = c.c_code
// this works, but why can't I do it as an AND in the join?
where m.dp_code == dp.dp_code
select new
{
cid = c.id,
cid = c.id
}.Distinct();
So, questions:
Q1: Why can't I do two conditions in the join? User error, or limitations in LINQ?
Q2: How can I add the NOT EXISTS to the query? I've looked at this question, but can't see how to implement the NOT EXISTS subquery.
You can, but it's usually wrong to do a join at all. Still, if you must, you use anonymous types: on new { l: l.l_code, d: dp.code } equals new { l: m_code, d: m.dp_code }
where !(from m in map where whatever select m).Any(). But as with (1), it's better to use associations.

Resources