WHERE ... IN query with sub-query in Doctrine queryBuilder or equivalent - symfony

In my Symfony 4 project I have a User entity and a UserRepository.
I'm trying to implement the equivalent of this SQL query in the QueryBuilder(Doctrine 2) or even in DQL.
SELECT * FROM user WHERE account_manager_id IN (SELECT id FROM user WHERE account_manager_id = :managerAdminId AND roles LIKE '%ROLE_MANAGER%')
Or maybe use a different syntax.
I tried different things, but couldn't figure out how to write the WHERE ... IN with the sub-query.
This is all I could come up with, which I don't like because it fires multpiple queries for something I could do with a single one:
//App\Repository\UserRepository
public function getPublishersOfManagers($managerAdminId)
{
//SELECT * FROM user WHERE account_manager_id IN (SELECT id FROM user WHERE account_manager_id = :managerAdminId AND roles LIKE '%ROLE_MANAGER%')
$managerIds = $this->createQueryBuilder('u')
->select('u.id')
->where('u.roles LIKE :role')
->setParameter('role' , '%ROLE_MANAGER%')
->andWhere('u.accountManager = :managerAdminId')
->setParameter('managerAdminId' , $managerAdminId)
->getQuery()->getArrayResult();
$publishers = [];
foreach ($managerIds as $id) {
$publishers[] = $this->createQueryBuilder('u')
->select('u')
->where('u.roles LIKE :role')
->setParameter('role' , '%ROLE_PUBLISHER%')
->andWhere('u.accountManager = :managerAdminId')
->setParameter('managerAdminId' , $id)
->getQuery()->getResult();
}
return $publishers;
}

your query can be turned into something without a sub-query, but with a join instead, which should be equivalent (and should have the same runtime/complexity)
SELECT u
FROM user u
LEFT JOIN user am ON (am.id=u.accountManager)
WHERE am.roles LIKE '%ROLE_MANAGER%'
AND am.accountManager=:managerAdminId
AND u.roles LIKE '%ROLE_PUBLISHER%'
which can be translated into querybuilder accordingly (I have to assume, that you did not define your associations ... which I find disturbing, but you probably have your reasons):
return $this->createQueryBuilder('u')
->leftJoin('App\Entity\User', 'am', 'WITH', 'am.id=u.accountManager')
->andWhere('am.roles LIKE :role')
->setParameter('role', '%ROLE_MANAGER%')
->andWhere('am.accountManager = :managerAdminId')
->setParameter('managerAdminId', $managerAdminId)
->andWhere('u.roles LIKE :role2')
->setParameter('role2', '%ROLE_PUBLISHER%')
->getQuery()->getResult();
there is also the options of actually using sub-queries, but using sub-queries imho is always inconvenient - and ugly.
(you might have a look into writing just plain DQL queries, you might feel more at home ...?)

According to DQL query examples section within Doctrine's DQL documentation you need to either use EXISTS keyword within DQL query or use exists() method of Expr class.

Related

How to combine queryBuilder and raw queries on join table from many to many relation?

I have got following query
SELECT news.*, count(*) AS common_tags
FROM news
JOIN news_news_tag ON news.id = news_news_tag.news_id
WHERE news_tag_id IN
(SELECT news_tag_id FROM news_news_tag WHERE news_id = 2 )
AND news.id != 2 GROUP BY news.id ORDER BY common_tags DESC
What I want to achieve is to get hydrated news objects ordered by number of common tags with provided news id. News and Tag are many to many relation with news_news_tag join table.
News entity have got much more other relations. This is why I don't want to create a native query by myself to handle all other relations.
I would like to convert above query to use it with query builder. I wasn't able to use a DQL because my where statement uses a join (junction) table and I also need to use a join on that table.
All in all I have got 2 problems:
How can I create DQL subquery to select something from many to many join table)? If I know that I could do something like: ->where($queryBuilder->expr()->in('u.id', $mySubQueryAsDQL))
How to add that join statement that I could use news_tag_id in where statement?
If it is not possible I think that I would need to create two bidirectional one-to-many and many-to-one relations instead of many-to-many and work on special joining entity.
I finally came up with following solution. I decided to split that query into two separate ones. In first I can use just simple raw query as I need results only to where statement. Second query can be build using query builder in a normal way.
You have to only get rid of an extra count column at the end.
However if someone knows the solution how to use my first raw query directly inside query builder as a raw subquery for where statement please share with me.
public function getRelatedNews($newsId, $limit = 6)
$connection = $this->getEntityManager()->getConnection();
$sql = 'SELECT news_tag_id FROM news_news_tag WHERE news_id = :newsId';
try {
$stmt = $connection->prepare($sql);
} catch (DBALException $e) {
return [];
}
$stmt->execute(['newsId' => $newsId]);
$newsTagId = $stmt->fetchAll();
if (empty($newsTagId)) {
return [];
}
$newsTagId = array_column($newsTagId, 'news_tag_id');
$query = $this->createQueryBuilder('n')
->addSelect('COUNT(n.id) as common_tags_count')
->innerJoin("n.tags", "t")
->andWhere('t.id IN(:tagsId)')->setParameter('tagsId', array_values($newsTagId))
->andWhere('n.id != :newsId')->setParameter('newsId', $newsId)
->orderBy('common_tags_count', 'DESC')
->setMaxResults($limit)
->groupBy('n.id')
;
$results = $query->getQuery()->getResult();
$news = [];
foreach ($results as $result) {
$news[] = $result[0];
}
return $news;
}

Doctrine2 QueryBuilder select entity and count of associated entities

I'm having a huge problem with ORM QueryBuilder. What I need to do is:
I need to fetch order with count of its products and plenty of associated entities (associated with order), but I assume they're not relevant here. I also need to order result by that count.
Could anyone give me an example of how this can be achieved? I would like to avoid "inline" DQLs if possible.
You can get data via Doctrine Query Builder.
You are supposed to left join products from Order and then group by order id. You can have COUNT(product.id) in your select statement and use the alias in order by clause to make your orders sorted. Below is a small code snippet from Repository.
/**
* #return \Doctrine\ORM\Query
*/
public function getHotelAndRoomType()
{
$qb = $this->createQueryBuilder('order')
->select('partial order.{id, orderId} as order, count(product.id) as total_products_in_order')
->leftJoin('AppBundle:Product', 'product', 'WITH', 'product.order = order.id')
->groupBy('order.id')
->orderBy('total_products_in_order', 'DESC')
;
return $qb->getQuery()->execute();
}
Note : Code not tested.

One of listeners must count and slice given target

I'm getting the above error when performing a search in a Symfony2 CRM I've been working on. According to Google searches it seems this is an issue relating to the KNP Paginator bundle, but I cannot seem to find a solid solution.
In this instance, I am using data from an OpenCart database, and I need to be able to search by Postcode and Company name, both of which exist in the address table which is joined via a mapped value within the Customer entity, defaultaddress.
To this end, I've had to write a custom query in a function called findCustomerByPostcode like so:
$this->getEntityManager()
->createQuery('SELECT c FROM AppBundle:Oc49Customer c JOIN AppBundle:Oc49Address a WITH c.defaultaddress = a.id WHERE a.postcode LIKE :postcode')
->setParameter('postcode','%'.$postcode.'%')
->getResult();
However, when I perform a search on postcode, I get the following error in the browser:
One of listeners must count and slice given target
which refers to the Paginator.php file within the KNP bundle. I have updated to the most recent version, 2.5 yet I cannot seem to shake this error, and to me it does not even make sense.
Any help is much appreciated, as I cannot think of another way of search via a value within a joined table.
Which returns results from the postcode search, and then in the Controller:
$customers = $customer_repository->findCustomerByPostcode($filter_value);
$paginator = $this->get('knp_paginator');
$pagination = $paginator->paginate(
$customers,
$request->query->get('page', 1),
20
);
You can use the queryBuilder with the left join
public function findCustomerByPostcode($postcode) {
$query = $this->entityManager->createQueryBuilder();
$query
->select('c', 'a')
->from('AppBundle:Customer', 'c')
->leftJoin('c.address', 'a')
->setParameter('postcode', $postcode)
return $query->getQuery()->getResult();
}

Symfony2 Select one column in doctrine

I'm trying to refine the query trying to select fewer possible values ​​..
For example I have an entity "Anagrafic" that contains your name, address, city, etc.,
and a form where I want to change only one of these fields, such as address.
I have created this query:
//AnagraficRepository
public function findAddress($Id)
{
$qb = $this->createQueryBuilder('r')
->select('r.address')
->where('r.id = :id')
->setParameter('id', $Id)
->getQuery();
return $qb->getResult();
}
there is something wrong with this query because I do not return any value, but if I do the query normally:
//Controller
$entity = $em->getRepository('MyBusinessBundle:Anagrafic')->find($id);
Return the right value.
How do I do a query selecting only one column?
Since you are requesting single column of each record you are bound to expect an array. That being said you should replace getResult with getArrayResult() because you can't enforce object hydration:
$data = $qb->getArrayResult();
Now, you have structure:
$data[0]['address']
$data[1]['address']
....
Hope this helps.
As for the discussion about performance in comments I generally agree with you for not wanting all 30 column fetch every time. However, in that case, you should consider writing named queries in order to minimize impact if you database ever gets altered.
You can use partial objects to only hydrate one field and still return a object.
This worked for me:
$qb = $repository->createQueryBuilder('i')
->select('i.name')
->...
Use partial objects like this to select fields
$qb = $this->createQueryBuilder('r')
->select(array('partial r.{id,address}'))
...
Put your field names between the brackets

Doctrine2: using the querybuilder for many-to-many relationships

To search people in a system I have written a function in a repository.
This function receives an array with field names and criteria, and uses the querybuilder to formulate a valid DQL-statement.
Everything works fine, until the moment I want to make a search rule like:
"Find all people that belong to a certain domain."
...where the Person and Domain entities have a many-to-many relationship.
According to Symfony2 that relationship is defined correctly, and indeed everything works in Twig templates and controllers.
The function looks as follows:-
private function query($extra_conditions = null)
{
$qb = $this->_em->createQueryBuilder();
$query =
$qb->select('p')
->from('AppMainBundle:Person', 'p')
->leftjoin('AppMainBundle:Domain', 'd')
->where("p.firstname IS NOT NULL")
->andWhere("p.lastname IS NOT NULL")
->andWhere("p.function IS NOT NULL");
if ($extra_conditions!=null)
{
foreach ($extra_conditions as $condition)
{
#filter on firstname
if (key($condition)=='firstname')
$query = $query->andWhere('p.firstname LIKE ?1')
->setParameter(1, $condition['firstname']);
#filter on lastname
if (key($condition)=='lastname')
$query = $query->andWhere('p.lastname LIKE ?1')
->setParameter(1, $condition['lastname']);
#filter on gender
if (key($condition)=='gender')
$query = $query->andWhere('p.gender = '.$condition['gender']);
#filter on domain(s)
if (key($condition)=='domains')
$query = $query->andWhere('d.id IN ('.$condition['domains'].')');
#filter on town
if (key($condition)=='town')
$query = $query->andWhere('p.town LIKE ?1')
->setParameter(1, $condition['town']);
}
}
$query = $query->orderBy("p.lastname", "ASC");
$query = $qb->getQuery();
return $query->getResult();
}
There is no error, but whatever domains are given as search criteria; people from all domains are found and returned.
Domains are provided as an array like (2, 4).
What can I change to make it possible to find people belonging to certain domains?
After some trial-and-error I found two possible answers:
1) Apparently in the querybuilder you can refer to fields that live in join-tables, and are not actually part of an entity. Hence one solution would be:
#filter on domain(s)
if (key($condition)=='domains')
$query = $query->andWhere('p.domain_id IN ('.$condition['domains'].')');
...where domain_id lives in the join table "person_domain" created by Symfony2 unther the hood.
2) The inner join is now no longer needed. Using an inner join is possible as well, however it was written the wrong way and would not have worked anyway. It should have been written as:
->leftjoin('p:domain', 'd', 'ON p.id = d.id')
Hope that other people may benefit from the above.

Resources