I have: two entities with undirectional M:M association.
class ShareInfo
{
// ...
/**
* #ORM\ManyToMany(targetEntity="Item")
* #ORM\JoinTable(name="share_info_items",
* joinColumns={#ORM\JoinColumn(name="share_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="item_id", referencedColumnName="id")})
*
* #var Item[]
*/
private $items;
}
class Item
{
// ...
// This entity has no association with ShareInfo,
// because M:M is undirectional and defined in ShareInfo entity
}
What I want:
Select data from items table (Item entity), where at least one M:M record between Item and ShareInfo exists.
My suggestion which doesn't work (I've got a semantic error):
$queryBuilder
->select('i')
->from(Item::class, 'i')
->innerJoin(ShareInfo::class, 'shareInfo', 'WITH', 'shareInfo.items = i');
In pure SQL I'd do something like this:
SELECT i.*
FROM items i
INNER JOIN share_info_items shareInfo
ON shareInfo.item_id = i.id
Can't believe there is no DQL analog for this. The only solution I can imagine is to split undirectional M:M association into bi-directional
P.S. This question has no duplicates, I checked well.
The way to achieve this is through a subquery:
$em=$this->getDoctrine()->getManager();
$queryBuilder1=$em->createQueryBuilder();
$queryBuilder1->select(array('DISTINCT i.id'))
->from('AppBundle:ShareInfo', 'share_info')
->innerJoin('share_info.items', 'i');
$queryBuilder=$em->createQueryBuilder();
$queryBuilder->select('i')
->from('AppBundle:items', 'i')
->where($queryBuilder->expr()
->in('i.id',$queryBuilder1->getDql()));
Related
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();
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
I recently worked out an issue with querying ManyToMany relationship join tables, the solution was same as this answer and was wondering how it works.
lets say i have a simple ManyToMany relationship between groups and team, there will be a groups_team tables that will automatically be created here
groups entity
/**
* Groups
*
* #ORM\Table(name="groups")
* #ORM\Entity(repositoryClass="AppBundle\Model\Repository\GroupsRepository")
*/
class Groups {
/**
* #ORM\ManyToMany(targetEntity="Team", inversedBy="group")
*/
protected $team;
public function __construct() {
$this->team = new ArrayCollection();
}
/**
* #var int
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="groupname", type="string", length=255)
*/
private $groupname;
//obligatory getters and setters :)
team entity
/**
* Team
*
* #ORM\Table(name="team")
* #ORM\Entity(repositoryClass="AppBundle\Model\Repository\TeamRepository")
*/
class Team {
/**
* #ORM\ManyToMany(targetEntity="Groups", mappedBy="team")
*/
protected $group;
public function __construct(){
$this->group = new ArrayCollection();
}
/**
* #var int
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="teamname", type="string", length=255)
*/
private $team;
//[setters and getters here]
in order to get all the teams in a group i would have to query the groups_team table.i would have directly queried the table in just mysql but in symfony i have to do this
$groups = $em->getRepository("AppBundle\Model\Entity\Groups")->findBy(array('tournament' => $tournament->getId()));
//get all teams with group id in groups_team table
foreach ($groups as $group) {
$teamsingroup = $em->getRepository("AppBundle\Model\Entity\Team")->createQueryBuilder('o')
->innerJoin('o.group', 't')
->where('t.id = :group_id')
->setParameter('group_id', $group->getId())
->getQuery()->getResult();
echo "</b>".$group->getGroupname()."</b></br>";
foreach ($teamsingroup as $teamingroup) {
echo $teamingroup->getTeam()."</br>";
}
}
Can someone explain to me how the innerJoin is working and what is the concept behind this, maybe a few documentation to learn about this. are there better way to do this with symfony and doctrine.
Using ManyToMany between 2 entities involves a third table generally called as a junction table in this type of relation when you build a DQL (doctrine query) doctrine automatically joins junction table depending on the nature of relation you have defined as annotation so considering your query
$teamsingroup = $em->getRepository("AppBundle\Model\Entity\Team")
->createQueryBuilder('o')
->innerJoin('o.group', 't')
You are joining Team entity with Group entity in innerJoin('o.group') part o is the alias for Team entity and o.group refers to property defined in Team entity named as group.
/**
* #ORM\ManyToMany(targetEntity="Groups", mappedBy="team")
*/
protected $group;
Which has a ManyToMany annotation defined for this type of relation doctrine joins your team table first with junction table and then joins your junction table with groups table and the resultant SQL will be something like
SELECT t.*
FROM teams t
INNER JOIN junction_table jt ON(t.id = jt.team_id)
INNER JOIN groups g ON(g.id = jt.group_id)
WHERE g.id = #group_id
Another thing related your way of getting team for each group you can minimize your code by excluding createQueryBuilder part within loop, once you have defined teams property as ArrayCollection i.e $this->team = new ArrayCollection(); on each group object you will get collections of teams associated to that particular group by calling getTeam() function on group object similar to below code.
foreach ($groups as $group) {
$teamsingroup = $group->getTeam();
echo "</b>".$group->getGroupname()."</b></br>";
foreach ($teamsingroup as $teamingroup) {
echo $teamingroup->getTeam()."</br>";
}
}
I guess it's literally select statement with INNER JOIN using key columns defined entity class as mappedBy or inversedBy.
Why don't you have a look of doctrine log and see what the native sql is composed?
How to get Doctrine to log queries in Symfony2 (stackoverflow)
http://vvv.tobiassjosten.net/symfony/logging-doctrine-queries-in-symfony2/ (some code examples)
I don't know your user story behind this, but I also heard that it is recommended to use one to many relationship instead of many to many, unless there is a strong reason to do so, as most of cases can be handled by one to many by reconsidering models.
I have two entities:
class User extends BaseEntity {
/**
* #ORM\OneToOne(targetEntity="Profile", mappedBy="user", cascade={"persist", "remove"})
* #var Profile
*/
protected $profile;
}
class Profile extends BaseEntity {
/**
* #ORM\OneToOne(targetEntity="User", inversedBy="profile", cascade={"persist"})
* #ORM\JoinColumn(name="user_id", referencedColumnName="id", onDelete="CASCADE")
* #var User
*/
protected $user;
}
What I'm trying to do is to SELECT only users who don't have any profile (so there's no profile row with user_id=:user_id in profiles table). However, I have no idea how to make my QueryBuilder.
First, I tried something simple like
//u as user
$query = $this->er->createQueryBuilder('u');
$query
->join('u.profile', 'p')
->where('u.profile = 1');
But that returns A single-valued association path expression to an inverse side is not supported in DQL queries. Use an explicit join instead. so I suppose there's something wrong with my relationship? I tried to switch join() for leftJoin() but it didn't help either...
So what's up with this error and how to make proper condition with where() to tell Doctrine I want only users where there's no profile?
Here is how I was able to do it. Basically I had Domain & Hosting entities and I wanted to select Domains that had no Hosting attached to them in a one to one relationship.
$er->createQueryBuilder('d')
->leftJoin('d.hosting', 'h')
->where('h.id is NULL');
Credits for this solution goes to this Answer
Hope it works for you since our situations are almost identical.
Just check for null as value of profile
//u as user
$query = $this->er->createQueryBuilder('u');
$query
->where('u.profile is NULL');
Try to use repositories, example for UserRepository:
$qb = $this->createQueryBuilder('u');
$e = $qb->expr();
$qb->leftJoin('u.profile', 'p')
->where($e->isNull('p.id'))
Raw query will look like this:
SELECT * FROM users AS u LEFT JOIN u.profile AS p WHERE p.id IS NULL;
I have a Car entity with a many-to-one relationship with an entity Owner. If I select all cars, Doctrine does one query on the Car table, and subsequently one query on the Owner table for each car. So fetching N cars becomes N+1 queries instead of a single JOIN query between the Car and Owner tables.
My entities are as follows:
/** #Entity */
class Car {
/** #Id #Column(type="smallint") */
private $id;
/** #ManyToOne(targetEntity="Owner", fetch="EAGER")
#JoinColumn(name="owner", referencedColumnName="id") */
private $owner;
public function getId() { return $this->id; }
public function getOwner() { return $this->owner; }
}
/** #Entity */
class Owner {
/** #Id #Column(type="smallint") */
private $id;
/** #Column(type="string") */
private $name;
public function getName() { return $this->name; }
}
If I want to list the cars with their owners, I do:
$repo = $em->getRepository('Car');
$cars = $repo->findAll();
foreach($cars as $car)
echo 'Car no. ' . $car->getId() .
' owned by ' . $car->getOwner()->getName() . '\n';
Now this all works very well, apart from the fact that Doctrine issues a query for each car.
SELECT * FROM Car;
SELECT * FROM Owner WHERE id = 1;
SELECT * FROM Owner WHERE id = 2;
SELECT * FROM Owner WHERE id = 3;
....
Of course I'd want my query log to look like this:
SELECT * FROM Car JOIN Owner ON Car.owner = Owner.id;
Whether I have fetch="EAGER" or fetch="LAZY" doesn't matter, and even if I make a custom DQL query with JOIN between the two entities, $car->getOwner() still causes Doctrine to query the database (unless I use EAGER, in which case $repo->findAll() causes all of them).
Am I just too tired here, and this is the way it is supposed to work - or is there a clever way to force Doctrine to do the JOIN query instead?
At least in 1.x Doctrine if you wanted to query for the related objects, you had to use DQL. For your case, the DQL query would look something like this:
//Assuming $em is EntityManager
$query = $em->createQuery('SELECT c, o FROM Car c JOIN c.owner o');
$cars = $query->execute();
Run first a DQL query where you select all the cars joined (DQL JOIN) with the owner. Put the owner in the select().
// preload cars
$qb = $em->createQueryBuilder()
->select('car, owner')
->from('\Entity\Car', 'car')
->leftJoin('c.owner', 'owner');
$query = $qb->getQuery();
// the following seems not needed, but I think it depends on the conf
$query->setFetchMode("\Entity\Car", "owner", "EAGER");
$query->execute(); //you don't have to use this result here, Doctrine will keep it
Doctrine 2 will then perform a JOIN (normally faster as it requires less db queries depending on the number of records).
Now launch your foreach, Doctrine will find the entities internally and it won't run single queries when you need the owner.
Monitor the number of queries first/after each change (eg. mysql general log)
Your query...
$car->getOwner() // "go and fetch this car's owner"
... is in a foreach loop so it will certainly issue the query several times.
If you're writing custom DQL to deal with this, $car->getOwner() shouldn't feature in this at all. This is a function of the Car class. The custom DQL you would write would mimick the exact SQL query you point out and get your join done efficiently.