how to apply DISTINCT on an inner joined table - php

I have the following entity, basically it says a picture has a shop
class InstagramShopPicture
{
/**
* #Exclude()
* #ORM\ManyToOne(targetEntity="InstagramShop", inversedBy="userPictures")
* #ORM\JoinColumn(name="shop_id", referencedColumnName="id", nullable=false, onDelete="CASCADE")
*/
protected $shop;
}
how do I query 10 pictures, each from a unique store in DQL?
So far I have the following:
$query = $em->createQueryBuilder()->select('picture')
->from("ShopiousMainBundle:InstagramShopPicture", 'picture')
->innerJoin('picture.shop', 'shop')
->setMaxResults(10);
;
in other words I just wanted DISTINCT shop, but I am finding it hard to transalate DISTINCT into DQL
UPDATE:
$query = $em->createQueryBuilder()
->select('pictures')
->from("AppMainBundle:InstagramShop", 'shop')
->innerJoin('shop.userPictures', 'pictures')
->innerJoin('pictures.category', 'category')
->innerJoin('category.pictureFirstLevelCategory', 'firstLevelCategory')
->distinct()
;
$query->andWhere('firstLevelCategory = :firstLevelCategory');
$query->andWhere('shop.isLocked = false');
$query->andWhere('shop.expirydate IS NOT NULL');
$query->andWhere('shop.id != :shopId');
$query->andWhere('shop.expirydate >= :currentDate');
$parameter["currentDate"] = new \DateTime('now');
$parameter["shopId"] = $shopId;
$parameter["firstLevelCategory"] = $picture->getCategory()->getPicturefirstlevelcategory()();
I tried doing the above and it gives me this error:
Error: Cannot select entity through identification variables without choosing at least one root entity alias.
UPDATE:
SELECT DISTINCT pictures FROM AppMainBundle:InstagramShop shop INNER JOIN shop.userPictures pictures INNER JOIN pictures.category category INNER JOIN category.pictureFirstLevelCategory firstLevelCategory WHERE firstLevelCategory = :firstLevelCategory AND shop.isLocked = false AND shop.expirydate IS NOT NULL AND shop.id != :shopId AND shop.expirydate >= :currentDate

Related

Doctrine QueryBuilder need change ON condition for leftJoin

I have native sql query with left join when have on with or condition, how to represent it in query builder ?
$query = " SELECT te.id
FROM task_executions AS te
INNER JOIN tasks AS t ON t.id = te.task_id
LEFT JOIN cost_objects AS co ON co.id = t.cost_object_id
LEFT JOIN cost_object_managers AS com ON com.cost_object_id = co.id OR com.cost_object_id = co.parent_id
and I need represent it in query builder. But in User entity I have ManyToMany relation, without separate table and when I try left join WITH condition this is not same what I need. I need change relation for ON
LEFT JOIN cost_object_managers AS com ON com.cost_object_id = co.id OR com.cost_object_id = co.parent_id
User entity
class User
{
...
/**
* #ORM\ManyToMany(targetEntity="CostObject", mappedBy="users")
*/
private $costObjects;
}
CostObject entity
class CostObject
{
/**
* #var CostObject
*
* #ORM\ManyToOne(targetEntity="CostObject", inversedBy="children")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="parent_id", referencedColumnName="id", onDelete="CASCADE")
* })
*/
private $parent;
/**
* #var ArrayCollection
*
* #ORM\ManyToMany(targetEntity="User", inversedBy="costObjects")
* #ORM\JoinTable(name="cost_object_managers",
* joinColumns={#ORM\JoinColumn(name="cost_object_id", referencedColumnName="id", onDelete="CASCADE")},
* inverseJoinColumns={#ORM\JoinColumn(name="user_id", referencedColumnName="id", onDelete="CASCADE")}
* )
*/
private $users;
and my query builder without condition
$qb->select('te')
->from('AppBundle:TaskExecution', 'te')
->innerJoin('te.task', 't')
->leftJoin('t.costObject', 'co')
->leftJoin('co.users', 'com')
this is $query->getSQL()
SELECT some_name FROM task_executions t0_ INNER JOIN tasks t1_ ON t0_.task_id = t1_.id LEFT JOIN cost_objects c2_ ON t1_.cost_object_id = c2_.id LEFT JOIN cost_object_managers c4_ ON c2_.id = c4_.cost_object_id LEFT JOIN users u3_ ON u3_.id = c4_.user_id ORDER BY t0_.execution_start DESC
In this example I see ON relation condition LEFT JOIN users u3_ ON u3_.id = c4_.user_id. And need change it like in native sql
Now I have
$qb->select('te')
->from('AppBundle:TaskExecution', 'te')
->innerJoin('te.task', 't')
->leftJoin('t.costObject', 'co')
->leftJoin(
'co.users',
'com',
Join::ON,
$qb->expr()->orX(
'co = com.costObjects',
'co.parent = com.costObjects'
)
)
but got error
[Syntax Error] line 0, col 112: Error: Expected end of string, got 'ON'
if I used WITH condition, in my sql represent I still have relation by id, I don't need that
->leftJoin(
'co.users',
'com',
Join::WITH,
$qb->expr()->orX(
'co MEMBER OF com.costObjects',
'co.parent MEMBER OF com.costObjects'
)
)
LEFT JOIN users u3_ ON u3_.id = c4_.user_id AND (EXISTS (SELECT 1 FROM cost_object_managers c5_ INNER JOIN cost_objects c6_ ON c5_.cost_object_id = c6_.id WHERE c5_.user_id = u3_.id AND c6_.id IN (c2_.id)) OR EXISTS (SELECT 1 FROM cost_object_managers c5_ INNER JOIN cost_objects c6_ ON c5_.cost_object_id = c6_.id WHERE c5_.user_id = u3_.id AND c6_.id IN (c2_.parent_id)))
I mean users u3_ ON u3_.id = c4_.user_id AND but in native query we have only LEFT JOIN cost_object_managers AS com ON com.cost_object_id = co.id OR com.cost_object_id = co.parent_id
How it's reproduce in Query Builder with ON condition type?
If you have the query and it works for you, you don't need to do all the work to transform it to DQL or the QueryBuilder programmatic syntax. You can just use Doctrine's Native Query and then - if needed - map the result to your object. Just create a custom repository and in it a new method that roughly looks like this:
public function findTaskExecutionBy...()
{
$query = $this->entityManager->createNativeQuery('SELECT te.id FROM ...');
return $query->getSingleScalarResult(); // If it's just one id you expect
}
You can also use $query->getResult() if you expect multiple id's to be returned. Or use the ResultSetMapping if you want the whole Task-object:
$rsm = new ResultSetMappingBuilder($this->entityManager);
$rsm->addRootEntityFromClassMetadata('App\Entity\Task', 'te');
$query = $this->entityManager->createNativeQuery(
'SELECT te.* FROM ...',
You can also check the Doctrine documentation for a more detailed explanation and some more examples: http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/native-sql.html
$rsm
);

Symfony Doctrine Query For Many to Many Releationships

I have Many to Many relationship between Institutes and Courses. I want to build query that returns only the institutes list whom some courses has been assigned. I have wrote queries in this situation for one to many. but for not many to many. here is the relationships,
class Institutes {
/**
* #ORM\ManyToMany(targetEntity="Courses", inversedBy="institutes")
* #ORM\JoinTable(name="institute_courses",
* joinColumns={#ORM\JoinColumn(name="institute_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="course_id", referencedColumnName="id")}
* )
*/
protected $courses;
}
class Courses {
/**
* #ORM\ManyToMany(targetEntity="Institutes", mappedBy="courses")
*/
protected $institutes;
}
here is the query that i have written, but didn't work properly.
$repository->createQueryBuilder('s')
->leftJoin('CoursesBundle:Courses','c', 'ON c.institutes = s.courses')
->where('s.active = :active')
->andWhere('s.verified = :active')
->setParameter('active', true)
->orderBy('s.name', 'ASC');
This should do the trick:
$repository->createQueryBuilder('i')
->innerJoin('i.courses','c')
->where('i.active = TRUE')
->andWhere('i.verified = TRUE')
->orderBy('i.name', 'ASC');
You can use a JOIN as you would with other kinds of associations. The following query will find all courses which have been assigned at least to one institute:
SELECT c FROM SomeBundle:Courses c JOIN c.institutes i
You can filter the results further by adding a join condition:
SELECT c FROM SomeBundle:Courses c JOIN c.institutes i WITH i.something = :someParam

Doctrine paginator not working with count in querybuilder

Each Disque (d) has an associated collection of Note (n).
My querybuilder is to get every Disque based on how many Note are associated.
Here's a peak on the classes
class Disque
{
...
/**
* #ORM\ManyToMany(targetEntity="Note", cascade={"persist"})
*/
private $notes;
...
}
class Note
{
...
/**
* #ORM\ManyToOne(targetEntity="NoteValeur", cascade={"persist"})
* #ORM\JoinColumn(nullable=true)
*/
private $noteValeur;
...
}
class NoteValeur
{
...
/**
* #Gedmo\Slug(fields={"titre"})
* #ORM\Column(name="slug", unique=true, length=32)
*/
private $slug;
}
I'm using Doctrine Querybuilder to fetch the results, using a count for setting the threshold.
<?php
...
class DisqueRepository extends \Doctrine\ORM\EntityRepository
{
public function getDisquesByNotesAndAllInfo($slug, $seuil, $page, $nbPerPage)
{
$q = $this->createQueryBuilder('d');
$q
->select('d as infosDisque')
->leftJoin('d.pochettes', 'p')
->addSelect('p')
->leftJoin('d.groupes','g')
->addSelect('g')
->leftJoin('d.labelDisque', 'l')
->addSelect('l')
->leftJoin('d.chroniques','c')
->addSelect('c')
->leftJoin('d.notes', 'n')
->addSelect('n')
->innerJoin('n.noteValeur','nv')
->addSelect('nv')
->groupBy('d')
->addSelect('COUNT(d) as NbNotes')
->where('nv.slug LIKE :slug')
->setParameter('slug', $slug)
->having($q->expr()->gte('NbNotes',':seuil'))
->setParameter('seuil', $seuil)
->setFirstResult( ($page-1) * $nbPerPage)
->setMaxResults($nbPerPage)
->orderBy('d.dateSortieDisque', 'desc')
;
return new Paginator($q, true);
}
}
The result set is perfectly fine. The problem is that when I change the $page value, the offset in the SQL is not increased. I'm getting the same range of results no matter what.
The Symfony profiler gives me the following executable query
SELECT DISTINCT id_44
FROM (
SELECT
COUNT(s0_.id) AS sclr_0, s0_.id AS id_1, s0_.titre AS titre_2, s0_.datesortiedisque AS datesortiedisque_3, s0_.nombreDisque AS nombreDisque_4, s0_.remarque AS remarque_5, s0_.tracklist AS tracklist_6, s0_.lineup AS lineup_7, s0_.slug AS slug_8, s0_.coderef AS coderef_9,
s1_.id AS id_10, s1_.alt AS alt_11, s1_.url AS url_12, s1_.coderef AS coderef_13, s1_.ordre AS ordre_14, s1_.fichier AS fichier_15,
s2_.id AS id_16, s2_.nom AS nom_17, s2_.site AS site_18, s2_.motto AS motto_19, s2_.popularite AS popularite_20, s2_.slug AS slug_21, s2_.coderef AS coderef_22,
s3_.id AS id_23, s3_.titre AS titre_24, s3_.slug AS slug_25, s3_.coderef AS coderef_26,
s4_.id AS id_27, s4_.texte AS texte_28, s4_.resume AS resume_29, s4_.dateSaisie AS dateSaisie_30, s4_.lectures AS lectures_31, s4_.slug AS slug_32, s4_.coderef AS coderef_33,
s5_.id AS id_34, s5_.dateSaisie AS dateSaisie_35, s5_.coderef AS coderef_36, s6_.id AS id_37,
s6_.titre AS titre_38, s6_.valeur AS valeur_39, s6_.image AS image_40, s6_.coderef AS coderef_41, s6_.octal AS octal_42, s6_.slug AS slug_43,
s0_.id AS id_44, s0_.titre AS titre_45, s0_.datesortiedisque AS datesortiedisque_46, s0_.nombreDisque AS nombreDisque_47, s0_.remarque AS remarque_48, s0_.tracklist AS tracklist_49, s0_.lineup AS lineup_50, s0_.slug AS slug_51, s0_.coderef AS coderef_52
FROM
se_disque s0_
LEFT JOIN disque_pochette d7_ ON s0_.id = d7_.disque_id
LEFT JOIN se_pochette s1_ ON s1_.id = d7_.pochette_id
LEFT JOIN disque_groupe d8_ ON s0_.id = d8_.disque_id
LEFT JOIN se_groupe s2_ ON s2_.id = d8_.groupe_id
LEFT JOIN se_labeldisque s3_ ON s0_.label_disque_id = s3_.id
LEFT JOIN disque_chronique d9_ ON s0_.id = d9_.disque_id
LEFT JOIN se_chronique s4_ ON s4_.id = d9_.chronique_id
LEFT JOIN disque_note d10_ ON s0_.id = d10_.disque_id
LEFT JOIN se_note s5_ ON s5_.id = d10_.note_id
INNER JOIN se_notevaleur s6_ ON s5_.note_valeur_id = s6_.id
WHERE
s6_.slug LIKE 'classique'
GROUP BY
s0_.id, s0_.titre, s0_.datesortiedisque, s0_.nombreDisque, s0_.remarque, s0_.tracklist, s0_.lineup, s0_.slug, s0_.coderef, s0_.label_disque_id, s0_.format_id, s0_.format_discographique_id
HAVING
sclr_0 >= 2
) dctrn_result
ORDER BY datesortiedisque_3 DESC LIMIT 10 OFFSET 10
I can't seem to change the offset in this query no matter what I pass to the $q->setFirstResult() method.
Does anybody have a clue ?
Well, guess what, I was using a debug value in my controller and I had forgotten about it :
$disques = $dr->getDisquesByNotesAndAllInfo($slug, $seuil, 2, $nbPerPage);
All works fine now that I changed back this messy "2" value.
Sorry about that. Thanks to both of you for your help !

doctrine dql where clause interpretation with associated entity

Given this entity
class SystemRecord
{
/**
* #ORM\Id
* #ORM\Column(type="integer", name="ID")
* #ORM\GeneratedValue
* #var int
*/
private $id;
/**
* #ORM\ManyToOne(targetEntity="Application\Entity\User")
* #ORM\JoinColumn(name="USER_SERIAL", referencedColumnName="SERIAL", nullable=false)
* #var User
*/
private $user;
/**
* #ORM\Column(type="utcdatetime", name="DATE_DATA_WAS_FETCHED", nullable=false)
* #var DateTimeInterface
*/
private $dateDataWasFetched;
}
...and this dql
$dql = "
select r
from Application\\Entity\\SystemRecord r
join Application\\Entity\\User u
where r.dateDataWasFetched = (
select max(r2.dateDataWasFetched)
from Application\\Entity\\SystemRecord r2
)
and u.serial = :serial
";
$query = $this->getEntityManager()->createQuery($dql);
$query->setParameter('serial', $user->getSerial());
$sql = $query->getSql();
... I'm hoping to get "the SystemRecords for the user with the specified serial, but only those with the most recent date out of any SystemRecord". In other words, procedurally, "find the most recent date of any SystemRecord for any user. Then find records for the specified user which occurred on that date."
If I were writing sql, I would write
select *
from SYSTEM_RECORDS r
join USER u
on r.USER_SERIAL = u.SERIAL
where DATE_DATA_WAS_FETCHED = (select max(DATE_DATA_WAS_FETCHED) from SYSTEM_RECORDS)
and u.SERIAL = ?
But, doctrine is giving me the following sql
SELECT ...fields from s0_ ...
FROM SYSTEM_RECORDS s0_
INNER
JOIN
USER u1_
ON (s0_.DATE_DATA_WAS_FETCHED = (SELECT max(s2_.DATE_DATA_WAS_FETCHED) AS dctrn__1
FROM SYSTEM_RECORDS s2_) AND u1_.SERIAL = ?)
Which isn't what I want. That gives me "SystemRecords for all users whose SystemRecords have the same date as the most recent SystemRecords for the user with the specified serial".
How do I formulate my query using dql?
If I understand you correctly you need to use a sub query like you did but I think you are missing the in expression. With QueryBuilder you would built the query to get your result like this (I always write my queries with QueryBuilder):
$qb->select(r)
->from('SystemRecord', 'r')
->join('r.user', 'u')
->where(
$qb->expr()->in(
'r.dateDataWasFetched',
"SELECT max(r2.dateDataWasFetched) FROM Application\\Entity\\SystemRecord r2"
)
)
->andWhere('u.serial' = :user_serial)
->setParameter('user_serial', $user->getSerial())
->getQuery()
->getResult();
This answer is based on this answer to similar question on stackoverflow.
EDIT:
If you really want the DQL then you can easily get it from your QueryBuilder instance after building the query using the getDQL method like this:
$dql = $qb->getQuery()->getDQL();
I was able to solve/avoid my problem by avoiding a join
$dql = "
select r
from Application\\Entity\\SystemRecord r
where r.dateDataWasFetched = (
select max(r2.dateDataWasFetched)
from Application\\Entity\\SystemRecord r2
)
and r.user = :user
";
$query = $this->getEntityManager()->createQuery($dql);
$query->setParameter('user', $user);
Resulting sql(correct)
SELECT ...fields from s0_ ...
FROM SYSTEM_RECORDS s0_
WHERE s0_.DATE_DATA_WAS_FETCHED = (SELECT max(s1_.DATE_DATA_WAS_FETCHED) AS dctrn__1
FROM SYSTEM_RECORDS s1_) AND s0_.USER_SERIAL = ?
The notable difference is that instead of specifying the id for the associated entity(via u.serial = :serial, I'm now specifying the entity itself(via r.user = :user). This allows me to omit the join, too. btw - The serial field is tagged with #ORM\Id in my User entity.
However, this is just avoiding the problem. I'm still perplexed by how doctrine interprets the query when a join is present.
Edit - real solution found
Thanks to Wilt, after using the query builder and then using the getDQL() method I found the missing detail. The working dql is
select r
from Application\Entity\SystemRecord r
join r.user u
where r.dateDataWasFetched = (
select max(r2.dateDataWasFetched)
from Application\\Entity\\SystemRecord r2
)
and u.serial = :serial
Note that the difference between the DQL in my original question, and this working solution is join Application\\Entity\\User u vs join r.user u, respectively.

query builder, one to many where many is empty

I have an entity Category, and this category has a recursive relationship with itself where each category can be a parent to several other categories. The relationship looks like this:
/**
* #var parent
* #ORM\ManyToOne(targetEntity="Category")
* #ORM\JoinColumn(referencedColumnName="id", onDelete="CASCADE")
*/
private $parent;
/**
* #ORM\OneToMany(targetEntity="Category", mappedBy="parent")
*/
private $children;
I need to make a query builder query that selects all categories that are either children(have a parent) Or the categories that have no parent and also have no children (in other words all categories except the parents that have children) I can't seem to be able to do it. Please help.
You need next DQL query:
$query = 'SELECT c FROM AcmeBundle:Category c LEFT JOIN c.parent p LEFT JOIN c.children ch WHERE p IS NOT NULL OR (ch IS NULL AND p IS NULL)';
If you need QueryBuilder sequence for this query you can use next code:
$qb = $em->createQueryBuilder();
$query = $qb
->select('c')
->from('AcmeBundle:Category', 'c')
->leftJoin('c.parent', 'p')
->leftJoin('c.children', 'ch')
->where($qb->expr()->orX(
$qb->expr()->isNotNull('p'),
$qb->expr()->andX(
$qb->expr()->isNull('ch'),
$qb->expr()->isNull('p'),
)
))
->getQuery();

Categories