Complex Query in Query Builder - php

How can I execute this query using QueryBuilder in Symfony 2
SELECT um.id, sv.patentgroup_id, pm.portfolio_id, pp.id
FROM sv_patents sv
LEFT JOIN pm_patentgroups pm ON sv.patentgroup_id = pm.id
LEFT JOIN pm_portfolios pp ON pm.portfolio_id = pp.id
LEFT JOIN um_users um ON pp.user_id
The associations in entity classes are defined as
In SvPatents I have
/**
* #var PmPatentgroups
*
* #ORM\ManyToOne(targetEntity="PmPatentgroups")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="patentgroup_id", referencedColumnName="id")
* })
*/
private $patentgroup;
In PmPatentgroups I have
/**
* #var PmPortfolios
*
* #ORM\ManyToOne(targetEntity="PmPortfolios")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="portfolio_id", referencedColumnName="id")
* })
*/
private $portfolio_id;
In PmPortfolios I have
/**
* #var UmUsers
*
* #ORM\ManyToOne(targetEntity="UmUsers")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="user_id", referencedColumnName="id")
* })
*/
private $user;
I tried this but it is giving me the whole results from the table looks like joins are not working propoerly
$repository = $this->getDoctrine()->getRepository('MunichInnovationGroupBundle:SvPatents');
$qb = $repository->createQueryBuilder('sv')
->leftJoin('sv.patentgroup','pm')
->leftJoin('pm.portfolio_id','pp')
->leftJoin('pp.user','um')
->getQuery();
$patents = $qb->getArrayResult();
I am quite new to Symfony2 and Doctorine2. I read the whole documentation but could not find such complex example actually i am bit confused with how the querybuilder build the query. It would be great if you explain it a little in simple words
Thanks in advance

I found the solution to my problem. If u looks for the records of a specific user in the table then you need to add one where clause. This is how your query should looks like
$user_id = 1;
$repository = $this->getDoctrine()->getRepository('MunichInnovationGroupBundle:SvPatents');
$qb = $repository->createQueryBuilder('sv')
->select('sv')
->leftJoin('sv.patentgroup','pm')
->leftJoin('pm.portfolio_id','pp')
->leftJoin('pp.user','um')
->where('pp.user = :user_id')
->setParameter('user_id', $user_id)
->getQuery();
$patents = $qb->getArrayResult();
Thats all :)

You're very close. You just have to add a ->select() query part:
$repository = $this->getDoctrine()->getRepository('MunichInnovationGroupBundle:SvPatents');
$qb = $repository->createQueryBuilder('sv')
->select('um.id, pm.id, pp.id') // add this part
->leftJoin('sv.patentgroup','pm')
->leftJoin('pm.portfolio_id','pp')
->leftJoin('pp.user','um')
->getQuery();
$patents = $qb->getArrayResult();

Related

Doctrine join entities

I'm trying to make some efficient improvements when querying for an entity which has a relationship to another entity.
Entity A:
/**
* #var integer
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #var string
* #ORM\Column(name="name", type="string", length=225)
* #Assert\NotBlank(message="Please enter a name for your profile.")
*/
protected $display_name;
/**
* #ORM\OneToOne(targetEntity="Posts", mappedBy="profile", cascade={"all"}, fetch="LAZY")
*/
protected $posts;
Entity B:
/**
* #var integer
* #ORM\Column(name="profile_id", type="integer")
* #ORM\Id
*/
protected $profile_id;
/**
* #ORM\OneToOne(targetEntity="Profile", inversedBy="posts")
* #ORM\JoinColumn(name="profile_id", referencedColumnName="id")
*/
protected $profile;
/**
* #var string
* #ORM\Column(name="content", type="string")
*/
protected $content;
When I count the number of queries being executed I get two, I guess being doctrine runs two separate queries for each entity even through they have a relationship.
I am currently fetching entity A like so:
public function fetchById($id)
{
return $this->createQueryBuilder('p')
->where('p.id = :id')
->setParameter('id', $id)
->getQuery()
->getOneOrNullResult();
}
And then calling entity B like so:
$profile = $profileRepository->fetchById($user->getUserId());
$lastpost = $profile->getPosts()[0];
But I want to be able to join the second entity in this query so that I can just call one query rather than two. I was hoping to do something like this:
public function fetchById($id)
{
return $this->createQueryBuilder('p')
->select('p','pp')
->leftJoin(Posts::class, 'pp', \Doctrine\ORM\Query\Expr\Join::WITH, 'p.id = pp.profile_id')
->where('p.id = :id')
->setParameter('id', $id)
->getQuery()
->getResults();
}
However the left join returns an array of two entities. This is not what I am wanting, because I want to still be able to call for example the getPosts() method in entity A.
I essentially want to populate entity A, including all the related entities. But by only executing one query rather than two, is this possible in doctrine?
Some time ago I had a similar scenario (not with 1-1 but 1-n though), which I solved like this:
// ...Repository
public function findAllForUserWithAll(\AppBundle\Entity\User $u)
{
$query = $this->getEntityManager()->createQueryBuilder('ca1')
->add('select', 'c, s, i, m')
->add('from', 'AppBundle:Contact c')
->leftJoin('c.skills', 's')
->leftJoin('c.interests', 'i')
->leftJoin('c.metAt', 'm')
->where('c.user = :user')
->orderBy('c.lastname', 'ASC')
->setParameters([
'user' => $u,
])
->getQuery();
return $query->getResult();
}
This was in Symfony 2.8.* with Doctrine ^2.4.8 - and the result is one query with 3 joins (instead of 4 queries). Probably not the best code and honestly all the magic happened under the hood. Also your code looks alot alike. But maybe this is expected due to fetch="LAZY" (on entity A) - which my entity does not have?

createQueryBuilder / Join Column - Symfony

I have a trouble to implement this SQL Request :
SELECT *
FROM project
LEFT JOIN user
ON project.idAuthor=user.id
WHERE project.isVisible = 1 AND
user.role = 'agency'
To a simple Symfony Query Builder :
$query = $this->createQueryBuilder('p')
->leftJoin('WebAwardsBundle:User', 'u')
->where('p.isVisible = 1')
->andwhere("u.role = 'agency'")
->orderBy('p.id', 'DESC')
->getQuery();
The response of this query give me all the project, include the project when role !== agency ...
I don't know where I can put the ON project.idAuthor=user.id
Mapping :
Project :
/**
* Project
*
* #ORM\Table(name="project")
*#ORM\Entity(repositoryClass="WebAwardsBundle\Repostory\ProjectRepository")
*/
class Project
{
...
/**
* #var int
*
* #ORM\ManyToOne(targetEntity="User", inversedBy="projects")
* #ORM\JoinColumn(name="idAuthor", referencedColumnName="id")
*/
private $idAuthor;
...
User :
/**
* User
*
* #ORM\Table(name="user")
* #ORM\Entity(repositoryClass="WebAwardsBundle\Repository\UserRepository")
*/
class User implements UserInterface, \Serializable{
...
/**
* #ORM\OneToMany(targetEntity="Project", mappedBy="idAuthor")
*/
private $projects;
...
The correct answer to implement this SQL Request :
SELECT *
FROM project
LEFT JOIN user
ON project.idAuthor=user.id
WHERE project.isVisible = 1 AND
user.role = 'agency'
is to join the correct column of the entity (project.idAuthor to user in this case) :
$query = $this->createQueryBuilder('p')
->join('p.idAuthor', 'u')
->where('p.isVisible = 1')
->andWhere("u.role = 'agency'")
->orderBy('p.id', 'DESC')
->getQuery();

Doctrine Query Builder - Search in collection

I'm trying to create fulltext search in Doctrine Query Builder. But I have here one problem.
In Post Entity I have this:
/**
* #ORM\Column(type="string")
*/
protected $title;
/**
* #ORM\ManyToMany(targetEntity="Tag", inversedBy="posts")
* #ORM\JoinTable(
* name="posts_tags",
* joinColumns={
* #ORM\JoinColumn(name="post_id", referencedColumnName="id")
* },
* inverseJoinColumns={
* #ORM\JoinColumn(name="tag_id", referencedColumnName="id")
* }
* )
*/
protected $tags;
And I'm searching in post with this code:
$searched = $this->em->createQueryBuilder();
$searched->select('p')
->from(Post::getClassName(), 'p');
$searched->where(
$searched->expr()->like('p.title', $searched->expr()->literal('%' . $values->search . '%'))
);
But now I don't know how to search in "tags" because it's collection. Can somebody put me on right journey? Thank you very much.
Try an inner join:
$qb = $em->createQueryBuilder();
$posts = $qb->select('p')
->from(Post::getClassName(), 'p')
->innerJoin('p.tags', 't')
->where($qb->expr()->like('p.title', ':postTitle'))
->andWhere('t.name = :tagName')
->setParameter("postTitle", "%{$values->search}%")
->setParameter("tagName", "foobar")
->getQuery()->getResult();
Some variable names are made up, as I don't know your exact schema, but this should get you going.

How to query doctrine for dependent entities in Symfony2 controller

I don't find an example here in SO, so i post my question:
I have a group entity, a shop entity, and a transaction entity.
A group has many shops, and a shop can belong to many groups. In Group.php:
/**
* #ORM\ManyToMany(targetEntity="Shop", inversedBy="groups")
* #ORM\JoinTable(name="group_shop",
* joinColumns={#ORM\JoinColumn(name="group_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="shop_id", referencedColumnName="id")}
* )
**/
private $shops;
And in Shop.php
/**
* #ORM\ManyToMany(targetEntity="Group", mappedBy="shops")
*/
private $groups;
Then, a shop makes transactions. In Transaction.php:
/**
* #ORM\ManyToOne(targetEntity="Shop", inversedBy="transactions")
* #ORM\JoinColumn(name="shop_id", referencedColumnName="id")
* */
private $shop;
And in Shop.php:
/**
* #ORM\OneToMany(targetEntity="Transaction", mappedBy="shop")
**/
private $transactions;
What I want to query is all transactions from a group. This must be very simple, buy i'm blinded.
What I have:
$query4 = $em->createQuery("SELECT t FROM MGFAppBundle:Transaction t
WHERE t.date > :from AND t.date < :to AND t.shop IN (/* HERE I'M STUCK */)")- >setParameters(array(
'from' => $from
'to' => $to
));
I don't know if this is the correct approach or... well, dql is kinda hard to get for me.
How do I write this dql query?
Thanks in advance.
In your repository do something like this:
public function findTransactionsByGroup(GroupInterface $group)
{
return $this->createQueryBuilder('g')
->select('s.transactions')
->leftJoin('g.shops','s')
->where('s.group = :groupid')
->setParameter('groupid', $group->getId())
->getQuery()
->getResult();
}

DQL many to many and count

I'm using Symfony 2 with Doctrine, and I've got two entities joined in a many to many association.
Let's say I have two entities: User and Group, and the related tables on db are users, groups and users_groups.
I'd like to get the top 10 most populated groups in DQL, but I don't know the syntax to perform queries on the join table (users_groups). I already looked on the Doctrine manual but I didn't found the solution, I guess I still have a lot to learn about DQL.
In plain sql that would be:
select distinct group_id, count(*) as cnt from users_groups group by group_id order by cnt desc limit 10
Can you please help me to translate this to DQL?
Update (classes):
/**
* Entity\E_User
*
* #ORM\Table(name="users")
* #ORM\Entity
*/
class E_User
{
/**
* #ORM\ManyToMany(targetEntity="E_Group", cascade={"persist"})
* #ORM\JoinTable(name="users_groups",
* joinColumns={#ORM\JoinColumn(name="user_id", referencedColumnName="id", onDelete="cascade")},
* inverseJoinColumns={#ORM\JoinColumn(name="group_id", referencedColumnName="id", onDelete="cascade")}
* )
*/
protected $groups;
/**
* #var integer $id
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string $name
*
* #ORM\Column(name="name", type="string", length=255)
*/
private $name;
/* ... other attributes & getters and setters ...*/
}
/**
* Entity\E_Group
*
* #ORM\Table(name="groups")
* #ORM\Entity
*/
class E_Group
{
/**
* #var integer $id
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string $name
*
* #ORM\Column(name="text", type="string", length=255)
*/
private $name;
/* ... other attributes & getters and setters ...*/
}
It's not easy without seeing the actual classes, but by guessing you have a many-to-many bidirectional relationship:
$dql = "SELECT g.id, count(u.id) as cnt FROM Entity\Group g " .
"JOIN g.users u GROUP BY g.id ORDER BY cnt DESC LIMIT 10;";
$query = $em->createQuery($dql);
$popularGroups = $query->getArrayResult();
UPDATE:
You don't have to use a bidirectional relationship, you can query the other way around:
$dql = "SELECT g.id, count(u.id) as cnt FROM Entity\User u " .
"JOIN u.groups g GROUP BY g.id ORDER BY cnt DESC LIMIT 10;";
For those who want to build the query with Doctrine's QueryBuilder instead of using DQL directly take this solution.
Please note that my problem wasn't to get the top user groups, but technically the problem is pretty similar to mine. I work with posts (like articles/blog posts) and tags that are added to posts. I needed to determine a list of related posts (identified by same tags). That list had to be sorted by relevance (the more same tags another post has the more relevant it is).
This is the method of my PostRepository class:
/**
* Finds all related posts sorted by relavance
* (from most important to least important) using
* the tags of the given post entity.
*
* #param Post $post
*
* #return POST[]
*/
public function findRelatedPosts(Post $post) {
// build query
$q = $this->createQueryBuilder('p')
->addSelect('count(t.id) as relevance')
->innerJoin('p.tags', 't')
->where('t.id IN (:tags)')
->setParameter('tags', $post->getTags())
->andWhere('p.id != :post')
->setParameter('post', $post->getId())
->addGroupBy('p.id')
->addOrderBy('relevance', 'DESC')
->getQuery();
// execute query and retrieve database result
$r = $q->execute();
// result contains arrays, each array contains
// the actual post and the relevance value
// --> let's extract the post entities and
// forget about the relevance, because the
// posts are already sorted by relevance
$r = array_map(function ($entry) {
// first index is the post, second one
// is the relevance, just return the post
return reset($entry);
}, $r);
// array of posts
return $r;
}
Thank you #Tom Imrei for you solution. Also the answer #26549597 was very helpful.
To improve Tom's answer, you could use DQL's HIDDEN keyword. This way, the result doesn't contain the useless cnt column and Arvid's array_map solution isn't needed (which could speed up the result significantly for larger queries).
And the OP's question was to get the top 10 groups, not just their IDs.
It would look something like this:
$query = 'SELECT g, HIDDEN COUNT(u.id) AS cnt FROM Entity\Group g LEFT JOIN g.users u ORDER BY cnt DESC';
$groups = $em->createQuery($query)->setMaxResults(10)->getResult();
Also, note the use of LEFT JOIN that ensures that empty groups are not dismissed.

Categories