Symfony joining DQL expressions with "NOT IN" - php

I'm using Symfony2 and i'm trying to get an array of languages not associated to a specified client.
I have a Client entity indicating the Client, ClientLanguage that has the following structure:
id_menu_language PRIMARY KEY
language the association with the Language entity
client the association with the Client entity
sequence tells the order the language should be shown (not used here)
and Language Entity.
To get an array of languages not associated to the client i want to proceed in the following way:
Get the languages that the client has already associated (and I'm getting the correct DQL in the $clientLanguagesDQL variable)
Retrieve a list of all the available languages
Exclude from that list all the languages already associated to the client (by using NOT IN (...) ).
This is the function I wrote to accomlish that:
<?php
namespace AppBundle\Repository;
use AppBundle\Entity\Client;
use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\Query\Expr\Join;
class ClientRepository extends EntityRepository
{
/**
* #param $client Client to check
* #return array
*/
public function getLanguagesNotAssociatedToClient($client)
{
$qb = $this->getEntityManager()->createQueryBuilder();
$clientLanguagesDQL = $qb
->select('lang')
->from('AppBundle:Language', 'lang')
->join('AppBundle:ClientLanguage', 'languages_assoc', Join::WITH, 'languages_assoc.language = lang')
->join('AppBundle:Client', 'client', Join::WITH, 'languages_assoc.client = client')
->where('client.idClient = :client_id')
->getQuery()
->getDQL();
$languages = $qb->select('language')
->from('AppBundle:Language', 'language')
->where($qb->expr()->notIn('language', $clientLanguagesDQL))
->setParameter('client_id', $client->getIdClient())
->getQuery()
->getResult();
return $languages;
}
}
However, when I run this, Symfony complains about a : [Semantical Error] line 0, col 293 near 'lang INNER JOIN': Error: 'lang' is already defined. It also tells me that there is a QueryException, and it shows me the following query:
SELECT language
FROM AppBundle:Language lang
INNER JOIN AppBundle:ClientLanguage languages_assoc
WITH languages_assoc.language = lang
INNER JOIN AppBundle:Client client
WITH languages_assoc.client = client, AppBundle:Language language WHERE language NOT IN(
SELECT lang
FROM AppBundle:Language lang
INNER JOIN AppBundle:ClientLanguage languages_assoc
WITH languages_assoc.language = lang
INNER JOIN AppBundle:Client client
WITH languages_assoc.client = client
WHERE client.idClient = :client_id
)
And this is definitely not what I want to do. Why there appeared to be two joins with AppBundle:ClientLanguage and AppBundle:Client? I use this association only in my first subquery.
If it can help, when I run this:
$clientLanguagesDQL = $qb
->select('lang')
->from('AppBundle:Language', 'lang')
->join('AppBundle:ClientLanguage', 'languages_assoc', Join::WITH, 'languages_assoc.language = lang')
->join('AppBundle:Client', 'client', Join::WITH, 'languages_assoc.client = client')
->where('client.idClient = :client_id')
->getQuery()
->getDQL();
This is the returned DQL stored in $clientLanguageDQL:
SELECT lang
FROM AppBundle:Language lang
INNER JOIN AppBundle:ClientLanguage languages_assoc
WITH languages_assoc.language = lang
INNER JOIN AppBundle:Client client
WITH languages_assoc.client = client
WHERE client.idClient = :client_id
What's wrong with this query?

My mistake was very stupid, all I had to do is creating a new query builder for each query instead of reusing the first one:
$qb1 = $this->getEntityManager()->createQueryBuilder();
$clientLanguagesDQL = $qb1
->select('lang')
->from('AppBundle:Language', 'lang')
->join('AppBundle:ClientLanguage', 'languages_assoc', Join::WITH, 'languages_assoc.language = lang')
->join('AppBundle:Client', 'client', Join::WITH, 'languages_assoc.client = client')
->where('client.idClient = :client_id')
->getQuery()
->getDQL();
$qb2 = $this->getEntityManager()->createQueryBuilder();
$languages = $qb2->select('language')
->from('AppBundle:Language', 'language')
->where($qb2->expr()->notIn('language', $clientLanguagesDQL))
->setParameter('client_id', $client->getIdClient())
->getQuery()
->getResult();
return $languages;

Related

Doctrine2 - double left join query issue

I am using Symfony v3.4 branch with Doctrine.
I am having trouble translating SQL query to Doctrine ORM query.
I have 3 tables.
Shop
Firm
User
User --> 1:1 --> Firm --> 1:1 --> Shop
(Symfony developer tool bar reports that associations in tables are made correctly).
I want to get Shop data that corresponds to cretain User in one query.
My SQL, that get a result:
SELECT *
FROM mp2_fos_user as u
LEFT JOIN mp2_firm AS f ON u.id = f.firmUserId
LEFT JOIN mp2_shop AS s ON f.id = s.shopFirmId
WHERE u.id = 1
My Doctrine ORM query
$query = $em->createQueryBuilder()
->select('u, f, s')
->from('App:User', 'u')
->leftJoin('u.userFirm WITH u.id = f.firmUserId', 'f')
->leftJoin('f.firmShop WITH f.id = s.shopFirmId', 's')
->where('u.id = :user_id')
->setParameter('user_id', $user_id)
->getQuery();
at the moment running the code results in an error
[Syntax Error] line 0, col 57: Error: Expected end of string, got 'u'
What would be best practice for my issue?
Help would be much appreciated,
Thank you!
UPDATE
tried:
$query = $em->createQueryBuilder()
->select('s.id')
->from('App:User', 'u')
->leftJoin('u.userFirm WITH f.firmUser = u', 'f')
->leftJoin('f.firmShop WITH s.shopFirm = f', 's')
->where('u.id = :user_id')
->setParameter('user_id', $user_id)
->getQuery();
got [Syntax Error] line 0, col 54: Error: Expected end of string, got 'f'
There is no need to use WITH clause if you have defined mapping in your entities, WITH clause is used when you want to join your entities with additional matching criteria
class User
{
/**
* #ORM\YourRelationShipNature(targetEntity="App\Entity\Firm", mappedBy="user")
*/
private $userFirm;
}
class Firm
{
/**
* #ORM\YourRelationShipNature(targetEntity="App\Entity\Shop", mappedBy="firm")
*/
private $firmShop;
}
class Shop
{
//.....
}
And then your could simple use properties to join your entites
$query = $em->createQueryBuilder()
->select('u, f, s')
->from('App:User', 'u')
->leftJoin('u.userFirm', 'f')
->leftJoin('f.firmShop', 's')
->where('u.id = :user_id')
->setParameter('user_id', $user_id)
->getQuery();
I was thinking of something more along the lines of this (assuming the entity names are correct):
$query = $em->createQueryBuilder()
->select('u, f, s')
->from('App:User', 'u')
->leftJoin('App:UserFirm f WITH f.firmUser = u')
->leftJoin('App:FirmShop s WITH s.shopFirm = f')
->where('u.id = :user_id')
->setParameter('user_id', $user_id)
->getQuery();

Create a group by query with inner join using Doctrine Query Builder

I struggle to create a query to get information from two tables. I want to count rows and group it by category and type.
My normal PostgresSQL query looks like this:
SELECT c.name AS category_name, i.type, count(i) AS number_of_items
FROM item i
INNER JOIN category c
ON i.category_id = c.id
GROUP BY c.name, i.type
How do I build this query using Doctrine Query Builder?
What I have tried:
$qb->select(['c.name', 'i.type', 'count(i)'])
->from('AppBundle:Item', 'i')
->innerJoin('i', 'AppBundle:Category', 'c', 'i.category_id = c.id')
->groupBy('c.name')
->groupBy('i.type');
return $qb->getQuery()->getResult();
But this give me an error:
[Semantical Error] line 0, col 80 near 'i AppBundle:Category':
Error: Class 'i' is not defined.
I'm trying to follow the principle in the documentation found here: http://doctrine-orm.readthedocs.io/projects/doctrine-dbal/en/latest/reference/query-builder.html#join-clauses
Any help would be appreciated.
Using doctrine and without defining any mapping between your related entities is not a good practice you should start from Association Mapping
Once you have defined mappings in your entities you can simply join your main entity using the properties which holds the reference of linked entities, doctrine will automatically detects the join criteria you don't need to specify in query builder, sample mapping for your entities can be defined as
class Item
{
/**
* #ORM\ManyToOne(targetEntity="Category", inversedBy="items")
* #ORM\JoinColumn(name="category_id", referencedColumnName="id")
*/
private $category;
}
-
use Doctrine\Common\Collections\ArrayCollection;
class Category
{
/**
* #ORM\OneToMany(targetEntity="Item", mappedBy="category")
*/
private $items;
public function __construct()
{
$this->items = new ArrayCollection();
}
}
And then your query builder will look like
$qb->select('c.name', 'i.type', 'count(i)'])
->from('AppBundle:Category', 'c')
->innerJoin('c.items','i')
->groupBy('c.name')
->addGroupBy('i.type');
Relationship Mapping Metadata
Or if you still don't want to have mappings and use the other approach you have to use WITH clause in doctrine
$qb->select(['c.name', 'i.type', 'count(i)'])
->from('AppBundle:Item', 'i')
->innerJoin('AppBundle:Category', 'c', 'WITH' , 'i.category_id = c.id')
->groupBy('c.name')
->addGroupBy('i.type');
you don't need to use array inside select try this:
$qb->select('c.name', 'i.type', 'count(i)')
->from('AppBundle:Item', 'i')
->innerJoin('i', 'AppBundle:Category', 'c', 'i.category_id = c.id')
->groupBy('c.name')
->groupBy('i.type');
return $qb->getQuery()->getResult();
Tried to use full expression for your entity Like this :
->from('AppBundle\Entity\Item','i')

How to do a LEFT JOIN ON OR in Doctrine

I'm using Doctrine's querybuilder in Symfony2 to create a query to fetch entities.
I'm trying to get this result in MySQL :
SELECT u0_.id AS id0
FROM user u0_
LEFT JOIN rva_victims r3_ ON u0_.id = r3_.user_id
INNER JOIN rva r1_ ON r1_.id = r3_.rva_id or u0_.id = r1_.declarant_id
I've tried both ON and WITH conditionType in $qb->join(), but nothing work.
Any ideas to solve my problem?
The solution should be pretty straightforward. In your case it should look like this, assuming you're in the repository class:
$this
->createQueryBuilder('u')
->leftJoin(Victims::class, 'v', Query\Expr\Join::WITH, 'v.user = u.id')
->join(Rva::class, 'r', Query\Expr\Join::WITH, 'r.id = v.rva OR u.id = r.declarant');
This should work fine, I am just assuming the class names right now. Please also take in account that all the conditions are done on class attribute names (DQL), not column names.
You can do this by creating a query builder in entity repository
$qb = $this->createQueryBuilder('u')
->leftJoin('yourbundle:Entityname', 'ye', 'WITH', 'u0_.id = r3_.user_id')
->innerJoin('yourbuncle:entityname', 'yen', 'WITH', 'r1_.id = r3_.rva_id', 'OR', 'u0_.id = r1_.declarant_id');

Symfony2/Doctrine2 innerJoin using QueryBuilder

I'm trying to build a innerJoin query using Doctrine2/QueryBuilder.
$repo = $this->getDoctrine()
->getRepository('MyBundle:Models');
$query = $repo->createQueryBuilder('m')
->where('m.id = :id')
->setParameter('id', $id);
Doctrine says:
A join always belongs to one part of the from clause. This is why you
have to specify the alias of the FROM part the join belongs to as the
first argument.
As a second and third argument you can then specify the name and alias
of the join-table and the fourth argument contains the ON clause.
Ex.
$queryBuilder
->innerJoin('u', 'phonenumbers', 'p', 'u.id = p.user_id');
What I can't understand is that 'phonenumbers' table is referencing to Entity Name or DB Table Name.
What I actually want is, is there any way to explicitly refer to entity like
innerJoin('u', 'MyBundle:phonenumbers', 'p', 'u.id = p.user_id')?
It's a bit confusing when it joins just like that. Can please someone explain that to me?
Help!!
You are working on a DQL level with tables, which means that you actually joining a table, so you just need the table name, not the entity name. The table "phonenumbers might not even had an entity to begin with, this is why Doctrine requests a table name and not an entity name.
Edit
It is actually possible to work with entity names as well as follows (taken from my own code which is working as a charm):
$builder = $this->createQueryBuilder('m');
$builder->innerJoin(
'YourBundle:Category',
'c',
Join::WITH,
$builder->expr()->eq('m.id', 'c.mdl_id')
);
To use the constants from Join you should first:
use Doctrine\ORM\Query\Expr\Join;
But this should also work (taken from documentation which means should work like a charm):
$queryBuilder
->select('id', 'name')
->from('users', 'u')
->innerJoin('u', 'phonenumbers', 'p', 'u.id = p.user_id');
This is taken form: http://doctrine-orm.readthedocs.org/projects/doctrine-dbal/en/latest/reference/query-builder.html#join-clauses
In fact, you're already referencing explicitely your entity phonenumbers in your second argument of your innerJoin function.
You read the doc and i'll retake it point by point with your request :
Argument 1 : alias of the FROM part, here your m (alias of your models)
Argument 2 : The full name of your entity you want join with your models
Argument 3 : an alias to access it (just like the 'm' of models)
Argument 4 : The join condition. (like the ON part of a sql request)
But with symfony and if you have a relation between your two entities like that :
//AppBundle/Entity/Models
class Models {
/**
* ...
**/
private $id;
/**
* ...
* #ORM\ManyToOne(targetEntity="\AppBundle\Entity\Phone", inversedBy="models")
**/
private $phonenumbers;
...
}
you want to join you just can do :
$repo = $this->getDoctrine()
->getRepository('MyBundle:Models');
$query = $repo->createQueryBuilder('m')
->innerJoin('m.phonenumbers', 'p')
->where('m.id = :id')
->setParameter('id', $id);
To explain that : You just have to pass as first argument, the entity property you want to join (here the phone number of your model) and define it as alias (p for phonenumber to access it in a select)

Doctrine2 fetching rows that have manyToMany association by QueryBuilder

everyone.
I have 2 entities City and POI. Mapping looks like this:
class City {
/**
* #ORM\ManyToMany(targetEntity="POI", mappedBy="cities")
* #ORM\OrderBy({"position" = "ASC"})
*/
protected $pois;
and
class POI {
/**
* #ORM\ManyToMany(targetEntity="City", inversedBy="pois")
* #ORM\JoinTable(name="poi_cities")
*/
protected $cities;
I would like to fetch all POIs that have at least 1 association with some City using QueryBuilder. I should probably use exists() function but I don't quiet know how.
You'd have to Left join them and check if cities is null.
$qb->select('p', 'c')
->from('AcmeDemoBundle:POI', 'p')
->leftJoin('p.cities', 'c')
->where('c IS NOT NULL');
I haven't tested it, but I hope it gives you the general direction. You can read more about the QueryBuilder from here.
Docrine2 was changed in 2013, so the other solution displays error Error: Cannot add having condition on a non result variable. Now we cannot use joined alias just as a condition variable. We should use any of its properties like c.id
So you should fix the code to
$qb->select('p', 'c')
->from('AcmeDemoBundle:POI', 'p')
->leftJoin('p.cities', 'c')
->where('c.id IS NOT NULL');
$results = $qb->getQuery()->execute();
If you want to select entities that does not have any cities, use IS NULL.
$qb->leftJoin('p.cities', 'city')
->where('city.id IS NULL')
->getQuery()
->execute();
Description of a problem and link to the commit that responsible for that - http://www.doctrine-project.org/jira/browse/DDC-2780

Categories