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

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();

Related

Fetching all objects of one class with self-referencing ManyToMany

Think of this class:
class Person {
/**
* #var ArrayCollection
*
* #ORM\ManyToMany(targetEntity="Person", fetch="EXTRA_LAZY")
* #ORM\OrderBy({"name" = "ASC"})
*/
public $friends;
/**
*
* #var Person
* #ORM\ManyToOne(targetEntity="Person")
*/
public $bestFriend;
}
I have to iterate a lot over all Persons, so I'd like to fetch join them all at once.
To save memory, I have to do this partially.
So what I can do is:
$this->em->createQuery('SELECT partial p.{id, name, bestFriend} FROM Person p')->getResult();
This is cool, after this query, all persons are in the UoW, and I can traverse the graph via $aPersion->bestFriend->bestFriend without creating an additional query to the DB, since all Persons are in memory.
However, this does not work with the ToMany association. Adding friends to the partial select gives an error. If I want to iterate over all friends, this will first create a query to the join table...
How can I realise the full hydration of the friends-ToMany-assotiation with one query? Maybe a second query could help? Or a clever join clause?
Thanks in advance!
I would create a query in PersonRepository.php with a leftJoin and a addSelect like so:
$qb = $this->em->getRepository('App:Person')
->createQueryBuilder('p')
->leftJoin('p.friends', 'friends')
->select('partial p.{id, name, bestFriend}'}
->addSelect('partial friends.{id, name}') // Retrieve what you want here
->getQuery()->getResult();
return $qb;
I have not tested this, but believe it should work.
#DirkJFaber your answer was right,
in terms of DQL here is my solution:
$this->em->createQuery('
SELECT partial p.{id, name, bestFriend}, f FROM Person p JOIN f.friends f')->getResult();

Symfony subquery for entity

Have a problem with subquery with symfony.
What I try to do - I have a table with users and a table with posts.
Posts Users
id|author|content id|username
I want create subquery to get user name by id.
/**
* #return array
*/
public function findAll()
{
return $this->getEntityManager()->createQuery(
'SELECT a, (SELECT u.username
FROM BackendBundle:User u WHERE u.id = a.author) as authorName
FROM BackendBundle:Article a'
)->getResult();
}
Result:
What am I doing wrong? What is the best way to join column from other table by id? Maybe i can use annotations?
Thx for any help.
You don't need a subquery here, what you need is a simple (INNER) JOIN to join Users with their Articles.
$em->createQuery("SELECT a FROM Article JOIN a.author'");
You don't even need an on clause in your join, because Doctrine should already know (through annotations on your entities or a separate yaml file), that the article.author field relates to user.id.
Edit:
I assume you have a User entity that is One-To-Many related to the Article entity.
class User
{
// ...
/**
* #OneToMany(targetEntity="Article", mappedBy="author")
*/
private $articles;
// ...
}
class Article
{
// ...
/**
* #var User
* #ManyToOne(targetEntity="User", inversedBy="articles")
*/
private $author;
// ...
}
Please refer to doctrines association mapping documentation: http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/association-mapping.html

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.

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.

Doctrine - entities not being fetched

I have some entities with a one-to-many / many-to-one relationship -
Production class -
/**
* #OneToMany(targetEntity="ProductionsKeywords", mappedBy="production")
*/
protected $productionKeywords;
ProductionsKeywords class -
/**
* #ManyToOne(targetEntity="Production", inversedBy="productionKeywords")
* #JoinColumn(name="production_id", referencedColumnName="id", nullable=false)
* #Id
*/
protected $production;
/**
* #ManyToOne(targetEntity="Keyword", inversedBy="keywordProductions")
* #JoinColumn(name="keyword_id", referencedColumnName="id", nullable=false)
* #Id
*/
protected $keyword;
Keyword class -
/**
* #OneToMany(targetEntity="ProductionsKeywords", mappedBy="keyword")
*/
protected $keywordProductions;
If I write a DQL query like
$query = $this->entityManager->createQuery("SELECT p FROM \Entity\Production p");
The productions, productionKeywords and keywords all load fine, however if I try to fetch join the productionKeywords and keywords like
$query = $this->entityManager->createQuery("SELECT p, pk, k FROM \EntityProduction p
LEFT JOIN p.productionKeywords pk
LEFT JOIN pk.keyword k
");
then the entities are not loaded.
Not sure what I'm doing wrong as I have the same relationship setup with some other entities and it works fine with them.
OK, so it seems that if you have a 'pure' join entity (like the ProductionsKeywords class above) then it can't be used in a fetch query. I got around the issue by using a timestamp column in the productions_keywords table as another property in the ProductionsKeywords class and then the fetch joins began to work.

Categories