doctrine dql where clause interpretation with associated entity - php

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.

Related

Doctrine query: best approach to creating a select query joining three tables?

I have three tables that I need to join to extract a result set, and I am not sure if I should use createQueryBuilder, createNativeQuery, or some other approach.
My three tables are
Email (joined to member via field: member)
Member (joined to company via field: current_company)
Company
The Entities are properly annotated in the code. For example
In the Email Entity:
/**
* #ORM\ManyToOne(targetEntity="Member")
* #ORM\JoinColumn(name="member", referencedColumnName="id", nullable=true)
*/
protected $member;
In the Member Entity:
/**
* #ORM\ManyToOne(targetEntity="Company")
* #ORM\JoinColumn(name="current_company", referencedColumnName="id", nullable=true)
*/
protected $current_company;
And in the Company Entity:
/**
* #ORM\ManyToMany(targetEntity="Member", mappedBy="companies")
*/
protected $members;
What I need to do is extract a set of records from Email where the Members are all associated with one Company.
The following SQL in MySQL returns what I need, but I am new to Doctrine and do not know how best to translate this query into something Doctrine can use to extract the same results:
select e.* from email e
join member m on m.id = e.member
join company c on c.id = m.current_company
where m.current_company = '95f1b5a4-03c9-11e9-85b1-989096db2d5f';
Can anyone help, and which approach should be employed createQueryBuilder, createNativeQuery, etc?
Update, I was able to get it working using plain old SQL, but I am not sure if this is an acceptable workaround in the Doctrine world. Thoughts and suggestions from those with more experience would be greatly welcome!
$conn = $this->em->getConnection();
$sql = "select e.* from fitchek.email e join fitchek.member m on m.id = e.member join fitchek.company c on c.id = m.current_company where m.current_company = ?";
$stmt = $conn->prepare($sql);
$stmt->bindValue(1, $company_uuid);
$stmt->execute();

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 QueryBuilder add where clause on Join

I have a query builder with the following select:
$starterRepository = $this->getDoctrine()
->getManager()
->getRepository('CatalogBundle:Starter');
$query = $starterRepository->createQueryBuilder('s')
->where('s.active = 1')
->orderBy('s.voltage, s.power, s.serial');
It selects the table "starters", but inside Starter.php I have an association "references" like this:
/**
* #var ArrayCollection
*
* #ORM\ManyToMany(targetEntity="StarterReference", inversedBy="starters")
* #ORM\JoinTable(name="starters_references")
*/
protected $references;
So inside my query results I have both "starters" and "starters_references" tables. 1 starter has many starter references. Now the problem is that I need to select not all of the starter references, but only the ones which has some value in column named "ref_usage".
So I need to write where clause in my query, I am trying this:
->where('reference.ref_usage = 1')
But this way I get only one "starter" item with all the references it has included. And I need all the starter items, but with only the references with ref_usage 1.
Any ideas?
Here is the full files and functions I am using.
Controller function:
http://pastebin.com/0tTEcQbn
Entity "Starter.php":
http://pastebin.com/BFLpKtec
Entity "StarterReference.php":
http://pastebin.com/Kr9pEMEW
EDIT:
This is the query I get if I use:
->where('reference.ref_usage = 1')
SELECT COUNT(*) AS dctrn_count FROM (SELECT DISTINCT id0 FROM (SELECT s0_.id AS id0, s0_.serial AS serial1, s0_.voltage AS voltage2, s0_.power AS power3, s0_.rotation AS rotation4, s0_.teeth AS teeth5, s0_.module AS module6, s0_.b_terminal AS b_terminal7, s0_.comment AS comment8, s0_.commenten AS commenten9, s0_.commentru AS commentru10, s0_.commentpl AS commentpl11, s0_.commentde AS commentde12, s0_.commentes AS commentes13, s0_.type AS type14, s0_.adaptation AS adaptation15, s0_.alternative_product_1 AS alternative_product_116, s0_.alternative_product_2 AS alternative_product_217, s0_.alternative_product_3 AS alternative_product_318, s0_.alternative_product_4 AS alternative_product_419, s0_.active AS active20 FROM starters s0_ INNER JOIN starters_references s2_ ON s0_.id = s2_.starter_id INNER JOIN starter_reference s1_ ON s1_.id = s2_.starterreference_id WHERE s0_.active = 1 AND s1_.ref_usage = 1 ORDER BY s0_.voltage ASC, s0_.power ASC, s0_.serial ASC) dctrn_result) dctrn_table
As you can see it adds ref_usage = 1 to where clause. But the problem is that I don't need it here, I only need to check ref_usage when I inner join my references.
You should join the references in your query and then add the where clause.
$query = $starterRepository->createQueryBuilder('s')
->join('s.references', 'reference')
->where('s.active = 1')
->andwhere('reference.ref_usage = 1')
->orderBy('s.voltage, s.power, s.serial');

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 select multiple objects

I have 2 classes that I want to select from DB using 1 query in Symfony2 Doctrine.
The first entity is Calculation and the second is Polynomial. They have 1:1 relationship:
/**
* Acme\UserBundle\Entity\Calculation
*
* #ORM\Table(name="Calculation")
* #ORM\Entity(repositoryClass="AppBundle\Entity\CalculationRepository")
*/
class Calculation {
//...
/**
* #ORM\OneToOne(targetEntity="Polynomial")
* #ORM\JoinColumn(name="result_polynomial_id", referencedColumnName="id", nullable=false)
**/
private $resultPolynomial;
//...
}
I have a query, that returns me all Calculations of one user:
public function findByUser( $user ) {
return $this->getEntityManager()->createQuery(
'SELECT c
FROM AppBundle:User u
JOIN AppBundle:Polynomial p WITH u = p.user
JOIN AppBundle:Calculation c WITH p = c.resultPolynomial
WHERE u = :user
ORDER BY c.id'
)
->setParameter('user', $user)
->getResult();
}
And to the question... Is there a way to get resultPolynomials of the calculations in one query? If I use something like SELECT c, c.resultPolynomial I get error:
[Semantical Error] line 0, col 12 near 'resultPolynomial
': Error: Invalid PathExpression. Must be a StateFieldPathExpression.
And if I use foreach cycle all calculations to get the their resultPolynomials there are many queries to the DB (1 for each calculation) and it is not good for performance if I have many calculations.
I making a guess because you did not post your User definition nor your Polynomial definition.
I think you can make your DQL this way:
SELECT c, p
FROM AppBundle:User u
JOIN u.polynomial p
JOIN p.calculation c
WHERE u = :user
ORDER BY c.id
I guess you already defined your relations in your model so you don't need to repeat it here.

Categories