Doctrine2 Query Builder Left Join with Select - doctrine-orm

I want to implement this SQL using doctrine2 query builder:
SELECT c.*, COUNT(s.id) AS studentCount
FROM classes c
LEFT JOIN (
SELECT *
FROM student_classes
WHERE YEAR = '2012'
) sc ON c.id = sc.class_id
LEFT JOIN students s ON sc.student_id = s.id
GROUP BY c.id
I tried this one but didn't work
$qb = $this->getEntityManager()
->getRepository('Classes')
->createQueryBuilder('c');
$qb->select('c.id AS id, c.name AS name, COUNT(s) AS studentCount');
$qb->leftJoin(
$qb->select('sc1')
->from('StudentClasses', 'sc1')
->where('sc1.year = :year')
->setParameter('year', $inputYear),
'sc2'
);
$qb->leftJoin('sc2.students', 's');
$qb->groupBy('c.id');
return $qb->getQuery()->getScalarResult();
or should I use nativeSQL instead?
any help would be appreciated,
thanks.

What are you trying to do is really interesting, because JOIN on a SELECT seems to not be supported by Doctrine2 with DQL or QueryBuilder. Of course, you can try with a native query.
However, to answer to your question, I believe that you don't need to make a JOIN on a SELECT. Simply, JOIN on StudentClasses and then add a condition in the WHERE about the $year! The WHERE clause is made for that.

You can use WITH clause to join entity with additional check, For your subquery you can write the same using left join with year filter, In join part i have used c.studentClasses based on the assumption that in Classes entity you have some mapped property for StudentClasses entity
$qb = $this->getEntityManager()
->getRepository('Classes')
->createQueryBuilder('c');
$qb->select('c.id AS id, c.name AS name, COUNT(s) AS studentCount');
$qb->leftJoin('c.studentClasses','sc2', 'WITH', 'sc2.year = :year');
$qb->leftJoin('sc2.students', 's');
$qb->setParameter('year', $inputYear);
$qb->groupBy('c.id');

Related

Symfony One-To-Many, unidirectional with Join Table query

I have some One-To-Many, unidirectional with Join Table relationships in a Symfony App which I need to query and I can't figure out how to do that in DQL or Query Builder.
The Like entity doesn't have a comments property itself because it can be owned by a lot of different types of entities.
Basically I would need to translate something like this:
SELECT likes
FROM AppBundle:Standard\Like likes
INNER JOIN comment_like ON comment_like.like_id = likes.id
INNER JOIN comments ON comment_like.comment_id = comments.id
WHERE likes.created_by = :user_id
AND likes.active = 1
AND comments.id = :comment_id
I've already tried this but the join output is incorrect, it selects any active Like regardless of its association with the given comment
$this->createQueryBuilder('l')
->select('l')
->innerJoin('AppBundle:Standard\Comment', 'c')
->where('l.owner = :user')
->andWhere('c = :comment')
->andWhere('l.active = 1')
->setParameter('user', $user)
->setParameter('comment', $comment)
I see 2 options to resolve this:
Make relation bi-directional
Use SQL (native query) + ResultSetMapping.
For the last option, here is example of repository method (just checked that it works):
public function getLikes(Comment $comment, $user)
{
$sql = '
SELECT l.id, l.active, l.owner
FROM `like` l
INNER JOIN comment_like ON l.id = comment_like.like_id
WHERE comment_like.comment_id = :comment_id
AND l.active = 1
AND l.owner = :user_id
';
$rsm = new \Doctrine\ORM\Query\ResultSetMappingBuilder($this->_em);
$rsm->addRootEntityFromClassMetadata(Like::class, 'l');
return $this->_em->createNativeQuery($sql, $rsm)
->setParameter('comment_id', $comment->getId())
->setParameter('user_id', $user)
->getResult();
}
PS: In case of Mysql, 'like' is reserved word. So, if one wants to have table with name 'like' - just surround name with backticks on definition:
* #ORM\Table(name="`like`")
I find the Symfony documentation very poor about unidirectional queries.
Anyway I got it working by using DQL and sub-select on the owning entity, which is certainly not as fast. Any suggestion on how to improve that is more than welcomed!
$em = $this->getEntityManager();
$query = $em->createQuery('
SELECT l
FROM AppBundle:Standard\Like l
WHERE l.id IN (
SELECT l2.id
FROM AppBundle:Standard\Comment c
JOIN c.likes l2
WHERE c = :comment
AND l2.owner = :user
AND l2.active = 1
)'
)
->setParameter('user', $user)
->setParameter('comment', $comment)
;

T4 POCO. Foreign Key Relationships (List<T> or T?)

I created a T4 template for our POCO objects using SMO to grab the object details from SQL Server. Right now I'm trying to determine how to determine the datatype of the navigation properties. My main issue is how to determine if it should be T or List<T>.
I'm not using EF or Linq to SQL.
Any ideas on what I should be checking to accurately determine the datatype?
Depending on which version of SQL you're using, you can use the INFORMATION_SCHEMA to get just about everything you need to build out your POCOs. The following is from http://searchcode.com/codesearch/view/15361587. It lists all tables and columns along with many attributes including whether the column is a foreign key.
SELECT
--TBL.TABLE_SCHEMA,
TBL.TABLE_TYPE,
COL.TABLE_NAME,
COL.ORDINAL_POSITION,
COL.COLUMN_NAME,
COL.DATA_TYPE,
COL.IS_NULLABLE,
ISNULL(COL.CHARACTER_MAXIMUM_LENGTH,-1) AS MAXIMUM_LENGTH,
--COL.TABLE_CATALOG,
(CASE KEYUSG.CONSTRAINT_TYPE WHEN 'PRIMARY KEY' THEN 'YES' ELSE 'NO' END) PRIMARY_KEY,
(CASE KEYUSG.CONSTRAINT_TYPE WHEN 'FOREIGN KEY' THEN 'YES' ELSE 'NO' END) FOREIGN_KEY,
FK.FOREIGN_TALBE,
FK.FOREIGN_COLUMN,
KEYUSG.CONSTRAINT_NAME
FROM
INFORMATION_SCHEMA.COLUMNS COL
JOIN
INFORMATION_SCHEMA.TABLES TBL
ON
COL.TABLE_NAME=TBL.TABLE_NAME
LEFT JOIN
(
SELECT
USG.CONSTRAINT_NAME,
USG.TABLE_NAME,
USG.COLUMN_NAME,
CONST.CONSTRAINT_TYPE
FROM
INFORMATION_SCHEMA.KEY_COLUMN_USAGE USG
JOIN
INFORMATION_SCHEMA.TABLE_CONSTRAINTS CONST
ON
USG.TABLE_NAME=CONST.TABLE_NAME
AND
USG.CONSTRAINT_NAME = CONST.CONSTRAINT_NAME
)
AS KEYUSG
ON
COL.TABLE_NAME=KEYUSG.TABLE_NAME
AND
COL.COLUMN_NAME=KEYUSG.COLUMN_NAME
---FOREIGHTKEYS
LEFT OUTER JOIN
(
SELECT
USAGE.TABLE_NAME,
USAGE.COLUMN_NAME,
UNI_USAGE.TABLE_NAME FOREIGN_TALBE,
UNI_USAGE.COLUMN_NAME FOREIGN_COLUMN,
CONST.CONSTRAINT_NAME,
UNIQUE_CONSTRAINT_NAME
FROM INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS CONST
JOIN
INFORMATION_SCHEMA.KEY_COLUMN_USAGE USAGE
ON
USAGE.CONSTRAINT_NAME=CONST.CONSTRAINT_NAME
JOIN
INFORMATION_SCHEMA.KEY_COLUMN_USAGE UNI_USAGE
ON
UNI_USAGE.CONSTRAINT_NAME=CONST.UNIQUE_CONSTRAINT_NAME
)
AS FK
ON
FK.TABLE_NAME=COL.TABLE_NAME
AND
FK.COLUMN_NAME = COL.COLUMN_NAME
AND
KEYUSG.CONSTRAINT_NAME=FK.CONSTRAINT_NAME
What I was looking for is a way to determine the cardinality of a foreign key. I believe it can be done by checking the uniqueness of the primary key column(s), however, we've opted to hard code the navigation properties ourselves without having to worry about this as well as a auto-generating a name for the property.

INNER JOIN in DQL in Doctrine 2 / Zend Framework 2

I have an issue with DQL in Doctrine 2.
Subqueries seem to be unavailable in DQL, so I don't know how to transform :
SELECT DISTINCT a.ID_DOMAINE, L_DOMAINE, b.ID_SS_DOMAINE, L_SS_DOMAINE, c.ID_COMPETENCE, L_COMPETENCE
FROM ((qfq_prod.REF_DOMAINE a inner join qfq_prod.REF_SS_DOMAINE b on a.id_domaine = b.id_domaine)
inner join qfq_prod.REF_COMPETENCE c on b.id_ss_domaine = c.id_ss_domaine)
inner join qfq_prod.REF_PERS_COMP d on c.id_competence = d.id_competence
into a DQL expression.
I tried it and got
"Error: Class '(' is not defined."
I saw that we can use Query Builder to do this as well.
Being new with Doctrine 2, can someone explain to me how I can do this please ?
My DQL is currently :
$query = $this->getEntityManager()->createQuery ( "SELECT DISTINCT a.ID_DOMAINE, L_DOMAINE, b.ID_SS_DOMAINE, L_SS_DOMAINE, c.ID_COMPETENCE, L_COMPETENCE
FROM ((BdDoctrine\Entity\Domaine a inner join BdDoctrine\Entity\SsDomaine b on a.id_domaine = b.id_domaine)
inner join BdDoctrine\Entity\Competence c on b.id_ss_domaine = c.id_ss_domaine)
inner join BdDoctrine\Entity\LienPersComp d on c.id_competence = d.id_competence" );
$res = $query->getResult ();
Subqueries seem to be unavailable in DQL, so I don't know how to transform :
Actually, they are. Your code (no offence) is hardly readable so I will give you an example:
//controller
$repo = $this->getDoctrine()->getRepository("Your:Bundle:Category") ;
$results = $repo->findAllForSomePage() ;
// CategoryRepository.php
public function findAllForSomePage()
{
return $this->createQueryBuilder("o")
->innerJoin("o.products", "p", "WITH", "p.price>:price")->addSelect("p")
->setParameter("price", 50)
->where("o.id IN (SELECT s1.id FROM Your:Bundle:Something s1 WHERE s1.col1=5)")
->getQuery()->getResult() ;
}
Here is presumed you have Category hasMany Products relation and that you defined CategoryRepository file. You should never create queries in controller.
This example will fetch Categories only if they have Products with price bigger than 50, AND the ID of categories are those fetched by fictional subquery. This 100% works.
You should apply the same logic on your requirement.
Also, you should not use ON statement when using joins, that is handled by doctrine.
If you have the relationships properly defined in your entities, then you can make your joins on those relationships. And as Zeljko mentioned, you don't need to specify the ON condition, as the entities should already know how they are related. You are joining entities not tables. (That's under the hood.)
I don't know what your entities look like, so I made a guess at the relationship names below, but it should give you the idea.
$dql =
<<<DQL
SELECT
DISTINCT a.ID_DOMAINE, b.L_DOMAINE, b.ID_SS_DOMAINE, b.L_SS_DOMAINE, c.ID_COMPETENCE, c.L_COMPETENCE
FROM
BdDoctrine\Entity\Domaine a
JOIN a.ss_domaine b
JOIN b.competence c
JOIN c.lien_pers_comp d
DQL;
$query = $this->getEntityManager()->createQuery($dql);
$res = $query->getResult();

Is it possible to convert into DQL or Doctrine Query Builder

I have 3 tables with column
A:
id
B:
id
a_id
C:
id
b.id
with native query:
SELECT a.id, b.id, c.id
FROM A as a
LEFT JOIN B as b
INNER JOIN C as c ON b.id = c.b_id
ON a.id = b.a_id
i have tried
SELECT a.id, b.id, c.id
FROM App\HomeBundle\Entity\A as a
LEFT JOIN App\HomeBundle\Entity\B as b
INNER JOIN App\HomeBundle\Entity\C as c
ON c.id = c.bId
ON a.id = b.aId
i got error:
Error: Expected Literal, got 'JOIN'
Is it possible to convert my native query to DQL or query builder? If possible, how it will be look like?
This is only a guess but too long for a comment. If I'm completely off base I'll delete this answer.
Assuming your native query is syntactically correct, perhaps MySQL is applying the last ON condition to the result of an INNER JOIN between b and c. If that's true, see if this gives you the same result:
SELECT a.id, b.id, c.id
FROM App\HomeBundle\Entity\A as a
LEFT JOIN (
SELECT bx.aID
FROM App\HomeBundle\Entity\B as bx
INNER JOIN App\HomeBundle\Entity\C as c
ON bx.id = c.bId
) b
ON a.id = b.aId
Note I corrected what I believe to be an error in your attempted solution (where you said ON c.id = c.bId).
This is an older question, but I believe you need to JOIN on the relationship, and not the entity.
For example, instead of
SELECT a
FROM Entity\A as a
LEFT JOIN Entity\B as b
It should be:
SELECT a
FROM Entity\A as a
LEFT JOIN a.entityB as b
If that is not clear, a.entityB is a property of the A entity, called "entityB". That property defines the relationship between A and B.
When you just JOIN entity to entity, Doctrine does not know how they are related. However, if you JOIN based on the property, Doctrine can use the annotations (or other mapping) to determine the relationship of A and B.
I've always had to use the WITH keyword instead of ON when using doctrine.

subquery in join with doctrine dql

I want to use DQL to create a query which looks like this in SQL:
select
e.*
from
e
inner join (
select
uuid, max(locale) as locale
from
e
where
locale = 'nl_NL' or
locale = 'nl'
group by
uuid
) as e_ on e.uuid = e_.uuid and e.locale = e_.locale
I tried to use QueryBuilder to generate the query and subquery. I think they do the right thing by them selves but I can't combine them in the join statement. Does anybody now if this is possible with DQL? I can't use native SQL because I want to return real objects and I don't know for which object this query is run (I only know the base class which have the uuid and locale property).
$subQueryBuilder = $this->_em->createQueryBuilder();
$subQueryBuilder
->addSelect('e.uuid, max(e.locale) as locale')
->from($this->_entityName, 'e')
->where($subQueryBuilder->expr()->in('e.locale', $localeCriteria))
->groupBy('e.uuid');
$queryBuilder = $this->_em->createQueryBuilder();
$queryBuilder
->addSelect('e')
->from($this->_entityName, 'e')
->join('('.$subQueryBuilder.') as', 'e_')
->where('e.uuid = e_.uuid')
->andWhere('e.locale = e_.locale');
You cannot put a subquery in the FROM clause of your DQL.
I will assume that your PK is {uuid, locale}, as of discussion with you on IRC. Since you also have two different columns in your query, this can become ugly.
What you can do is putting it into the WHERE clause:
select
e
from
MyEntity e
WHERE
e.uuid IN (
select
e2.uuid
from
MyEntity e2
where
e2.locale IN (:selectedLocales)
group by
e2.uuid
)
AND e.locale IN (
select
max(e3.locale) as locale
from
MyEntity e3
where
e3.locale IN (:selectedLocales)
group by
e3.uuid
)
Please note that I used a comparison against a (non empty) array of locales that you bind to to the :selectedLocales. This is to avoid destroying the query cache if you want to match against additional locales.
I also wouldn't suggest building this with the query builder if there's no real advantage in doing so since it will just make it simpler to break the query cache if you add conditionals dynamically (also, it's 3 query builders involved!)