Symfony2 QueryBuilder join ON and WITH difference - symfony

I'm new with Symfony2 and I built successfully my first join through QueryBuilder and Doctrine 2.
Probably this is a stupid question but both on-line and in the Symfony2's methods I was unable to find anything for understanding the difference between the join clauses "WITH" and "ON".
For example this is my join code:
->leftJoin('EcommerceProductBundle:ProductData', 'pdata', 'WITH', 'prod.id = IDENTITY(pdata.product)')
It works good but if I put ON instead of WITH I get the following error:
[Syntax Error] line 0, col 200: Error: Expected
Doctrine\ORM\Query\Lexer::T_WITH, got 'ON'
Why? I've seen among the objects that there are both the T_ON and T_WITH like join clauses, but which is their usage difference? What is their uses like?

#florian gave you the correct answer but let me try to explain it on example:
In sql, joins are done like this:
SELECT * FROM category
LEFT JOIN product ON product.category_id = category.id
(or something like this)
Now in Doctrine, you don't need to use ON clause because doctrine knows that from relations annotations in your entities. So above example would be:
// CategoryRepository.php
public function getCategoriesAndJoinProducts()
{
return $this->createQueryBuilder("o")
->leftJoin("o.products", "p")->addSelect("p")
->getQuery()->getResult() ;
}
Both would fetch all categories and join products associated with them.
Now comes the WITH clause. If you want to join only products with price bigger than 50, you would do this in SQL:
SELECT * FROM category
LEFT JOIN product ON product.category_id = category.id AND product.price>50
In Doctrine:
// CategoryRepository.php
public function getCategoriesAndJoinProductsWithPriceBiggerThan($price)
{
return $this->createQueryBuilder("o")
->leftJoin("o.products", "p", "WITH", "p.price>:price")
->setParameter("price", price)->addSelect("p")
->getQuery()->getResult() ;
}
So, in reality you should never, ever use ON if you are using Doctrine. If you have a need for something like that, you can be almost sure that you screwed something else.

In theory, ON permits you to give the full join criterias, while WITH permits to add additional criterias to the default ones (IMHO).
But, what DQL permits is to avoid giving the JOIN criterias:
You just have to say: $qb->leftJoin('prod.pdata', 'pdata');
And doctrine2 will handle the join correctly.
Here is a related question about that: Can I use "ON" keyword in DQL or do I need to use Native Query?

Related

Drupal entityQuery multiple node types and join

Looking to join multiple nodes via a query and join. Let's say one node has a matching ID of another field in another node. I would like to join them into an array so that I can output other fields with them indexed together on the same ID. Example: node1.nid and node2.field.target_id
How do I do this?
$nids = \Drupal::entityQuery('node')->accessCheck(FALSE)
->join('node2', 'n2', 'n.nid = n2.field.target_id');
->condition('type', 'node1', 'node2')
->sort('title')->execute();
$nodes = \Drupal\node\Entity\Node::loadMultiple($nids);
$response = array();
This is definitely doable and it looks like you're almost there, but it looks like you're combining the Database API with the Entity API, specifically trying to run Database joins on EntityQuery fetches.
Now, if you want to continue the Database query route, which may make it a little easier to join the values of multiple entities and output them, this is what I would recommend:
The first thing that I notice is that you're attempting to chain the join. Unfortunately, according to the Database API Docs
Joins cannot be chained, so they have to be called separately (see Chaining).
I have done similar to this with a join between two custom content types. Here is the code I used:
$connection = Database::getConnection();
if ($connection->schema()->tableExists('companies') && $connection->schema()->tableExists('users')) {
$query = $connection->select('companies', 'c');
$query->join('users', 'u', 'c.sourceid1 = u.sourceid1');
$results = $query->fields('u', ['destid1', 'sourceid1'])
->fields('c', ['destid1'])
->execute()
->fetchAll();
return $results;
}
Now this assumes that you've brought in Drupal\Core\Database\Database, but let's walk through the process.
$connection = Database::getConnection();
This is fetching the database connection for your site
if ($connection->schema()->tableExists('companies') && $connection->schema()->tableExists('users')) {
This is a check I always do to make sure the tables I'm working with exist.
$query = $connection->select('companies', 'c');
One of your tables needs to be the "base" table. In this case, I chose companies.
$query->join('users', 'u', 'c.sourceid1 = u.sourceid1');
This line is where the join actually happens. Notice that it's not chained, but it's own command. We're joining these tables, users and companies, on this sourceid1.
$results = $query->fields('u', ['destid1', 'sourceid1'])
->fields('c', ['destid1'])
->execute()
->fetchAll();
This is where I'm pulling whichever fields I want from both entities. In this case I want destid1 and sourceid1 from my user table and destid1 from my company table.
The ->execute() call actually runs the query, and ->fetchAll() returns an array of all matching results. This will get you to being able to JSONify all the things.
However, if you want to stick with the entityQuery route, that's also viable. My personal preference is to use a regular query for more complex things and an entityQuery if I just need to get a value from a single field or return a single entity.
For an entityQuery, well, that's a bit different. I wouldn't really recommend using an entityQuery for a join here. Instead, using entityTypeManager if you know or can get the matching value from both tables.
For example, let's assume that field_content_type_a_id on content type A matches field_related_type_a_id on content type B.
You could load one, let's go with type A for now:
// Get the current NID somehow... that's up to you
$entity_a = \Drupal::entityTypeManager()->getStorage('node')->load($nid);
// Get the value of the field you want to match to the other content type
$matching_value = $entity_a->get('field_content_type_a_id')->getValue();
// Get the entities with matching type A values
$entity_b_array = \Drupal::entityTypeManager()->getStorage('node')
->loadByProperties(['field_related_type_a_id' => $matching_value]);
// Do something with $entity_b_array.

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

Doctrine duplicating query results?

I have the following code
$this->em = $this->container->get('doctrine.orm.entity_manager');
$qb = $this->em->getRepository('CoreBundle:ServiceProvider')->createQueryBuilder('c');
$qb->select('count(venue.id) as vencount');
$qb->from('CoreBundle:ServiceProvider','venue');
$count = $qb->getQuery()->getOneOrNullResult()['vencount'];
which is write its returning a number of venues but the problem is that this number is mistaken because in the ServiceProvider table i have only 5 records but this query is returning 25. I tried to add a new record so they are 6 and yes the result was 36.
So I added group by the id and it fixed the issue anyone can tell me why is this happening ?
It's because when you create a query from a repository Doctrine assumes its a select and inject the select and from clausule's for you.
This is the sql you get from using the getRepository method:
SELECT count(i0_.id) AS sclr_0 FROM Entity i1_, Entity i0_
(Note that the entity is twice in the FROM).
Using just:
$qb = $this->em->createQueryBuilder();
$qb->select('count(venue.id) as vencount');
$qb->from('CoreBundle:ServiceProvider','venue');
You get:
SELECT count(i0_.id) AS sclr_0 FROM Entity i0_
Which is probably what you are looking for.
Another alternative is to get it from the repository but clear the sql parts with:
$qb = $this->em->getRepository('CoreBundle:ServiceProvider')->createQueryBuilder('c')->resetDQLParts();
But this way you lost the very purpose of using the repository in the first place.

Cannot select entity through identification variables without choosing at least one root entity alias

Ads entity is described by geographic information: Country> Region>County. The Ads entity is only linked with County. Consequently, retrieving Ads by countries will require us joining entities twice.
My goal is counting the number of Ads for a given country. For that, I tried this DQL query but without success:
public function getMotorsAdsCountByCountry($slug){
$qb = $this->_em->createQueryBuilder()
->select("m.id, COUNT(m.id) AS cnt")
->from("MinnAdsBundle:MotorsAds", "m")
->join("m.county","county")->addSelect("county")
->join("county.region","region")->addSelect("region")
->join("region.country","country")->addSelect("country")
->where("country.slug=:slug")
->setParameter(":slug", $slug);
return $qb->getQuery()->getSingleScalarResult();
}
The error I got is:
[Semantical Error] line 0, col -1 near 'SELECT m.id,': Error: Cannot
select entity through identification variables without choosing at
least one root entity alias.
I have even seen a post regarding the same error in this link but without success.
I found the solution:
$qb = $this->_em->createQueryBuilder()
->select("COUNT(m.id) AS cnt")
->from("MinnAdsBundle:MotorsAds", "m")
->join("m.county","county")
->join("county.region","region")
->join("region.country","country")
->where("country.slug=:slug")
->setParameter(":slug", $slug);
I have just remove the addSelect() in addition to the modification of the select().
try changing
->select("m.id, COUNT(m.id) AS cnt")
to
->select("m, COUNT(m.id) AS cnt")
or change hydration to array
this is known as Doctrine limitation you should first join with MotorAds Entity like this
from('MinnAdsBundle','mi')->leftJoin(MotorsAds::class,'m','WITH','mi.motorsAds = m');
then you can select form MotorsAds directly
refer to this answer

Resources