I have relations Article + Comment.
This:
$queryBuilder = $em->createQueryBuilder();
$queryBuilder->select('a, c')
->from(Article::class, 'a')
->leftJoin('a.comments', 'p');
$articles = $queryBuilder->getQuery()->getResult();
works correctly, but I would like less data, so I am trying:
$queryBuilder = $em->createQueryBuilder();
$queryBuilder->select('a.name, c')
->from(Article::class, 'a')
->leftJoin('a.comments', 'p');
$articles = $queryBuilder->getQuery()->getResult();
and I have error:
[Semantical Error] line 0, col -1 near 'SELECT a.name,': Error: Cannot select entity through identification variables without choosing at least one root entity alias.
If I use:
$queryBuilder->select('a.name, c.title')
then I have categories with key 'title' from first comment of article.
If I use:
$queryBuilder->select('a.name, a.comments')
then I have error:
[Semantical Error] line 0, col 15 near 'comments FROM': Error: Invalid PathExpression. Must be a StateFieldPathExpression.
If your relation is set proper, you could get your comments by calling this command:
$article->getComments();
In your left join, you are aliasing comments to 'p', but you are trying to access the comments with 'c' in your select. If you ->leftJoin('a.comments', 'c') then you should be able to access 'c.title' in your select. If this does not work you may need to check that your doctrine relations are set up properly in your entitites.
Indeed, if you want to retain object-oriented data access, you need to have a root element (in your case Article) which is used to traverse to other objects in your structure. In your case, this is simple - you only have a Comment (that we know of)
So, if you want to load less data you either:
Switch to array loading getArrayResult() instead of getResult(). With that, you are free to choose which fields are being loaded, but your lose objects. Everything you do, you will have to do it via array traversal. Example:
echo $article['comments'][0]['title'];
Alternatively, you can choose to load PARTIAL objects. Pros are that you retain object access, but cons are that you need to alter your SQL a bit. For example:
$queryBuilder->select('PARTIAL a.{name}', 'c')
->from(Article::class, 'a')
->leftJoin('a.comments', 'c');
$data = $queryBuilder->getQuery()->getResult();
Be aware that non-loaded fields are set to NULL so that may lead to unexpected attempts to dereference nulls.
Hope this helps...
Related
I have the following query:
$query = $qb->select('p')
->from(get_class($page), 'p')
->innerJoin('p1.translations', 't')
->groupBy('p.id')
->addGroupBy('t.id')
->getQuery();
Doctrine returns the above like:
Page entity -> [translation 1, translation 2, translation 3]
But I want the result like:
Page entity 1 -> translation 1
Page entity 1 -> translation 2
Page entity 1 -> translation 3
Does anyone know how I can do this? I want to return a list of entities.
First of all, both groupBy are completely superfluous, assuming both id fields you are grouping by are the primary keys of their respective tables. For any given (p.id, t.id) combination there will only be at most one result, whether you are grouping or not.
Second, even though you are joining the the translations table, you are not fetching any data from it (t is absent from your ->select()). It just appears that way since doctrine "magically" loads the data in the background when you call $page->getTranslations() (assuming your getter is called that way).
Third, your issue isn't with the groupBy. You are completely misunderstanding what an ORM actually does when hydrating a query result. The SQL query that doctrine generates from your code will actually return results in a fashion just like you expect, with "Page entity 1" repeated multiple times.
However, now comes the hydration step. Doctrine reads the first result row, builds a "Page entity 1" and a "Translation 1" entity, links them and adds them to it's internal entity registry. Then, for the second result, Doctrine notices that it has already hydrated the "Page entity 1" object and (this is the crucial part!) reuses the same object from the first row, adding the second translation to the ->translation collection of the existing object. Even if you read the "Page entity 1" again in a completely different query later in your code, you still get the same PHP object again.
This behaviour is at the core of what Doctrine does. Basically, any table row in the database will always be mirrored by a single object on the PHP side, no matter how often or in what way you actually read it from the database.
To summarize, your query should look like this:
$query = $qb->select('p, t')
->from(get_class($page), 'p')
->innerJoin('p.translations', 't')
->getQuery();
If you really need to iterate over all (p, t) combinations, do so with nested loops:
foreach ($query->getResult() as $p) {
foreach ($p->getTranslations() as $t) {
// do something
}
}
EDIT: If your relationship is bidirectional, you could also turn your query around:
$query = $qb->select('t, p')
->from('My\Bundle\Entity\Translation', 't')
->innerJoin('t.page', 'p')
->getQuery();
Now in your example you actually get 3 results that you can iterate over in a single foreach(). You would still only get a single "Page entity 1" object, with all the translations in the query result pointing to it.
I have following code in my Repository
// ProductBundle/Repository/ProductRepository.php
$qb->where($qb->expr()->eq('afp.id', 15));
$qb->andWhere($qb->expr()->eq('afp.id', 14));
return $qb
->select('a', 'afp')
->leftJoin('a.productFields', 'afp')
->getQuery()
->getResult();
But I always get null return, but I want to get products which have both productFields (so orWhere is not good).
You want to use MEMBER OF instead of comparing id. Otherwise, you're looking for a record that has two different id values, which of course isn't possible.
This will do what you want:
$qb->where($qb->expr()->isMemberOf(15, 'a.productFields'));
$qb->andWhere($qb->expr()->isMemberOf(14, 'a.productFields'));
Try something like this (Symfony 5):
$qb->andWhere(':c MEMBER OF a.productFields');
$qb->setParameter('c', 15);
Using symfony2/doctrine2, I have a hard time defining an index for my query.
My code :
$queryBuilder = $this->_em
->createQueryBuilder()
->select('u, uis, cost, p, stock')
->from('AppBundle:FoodAnalytics\UserIngredient', 'u', 'p.id')
->leftJoin('u.product', 'p')
->leftJoin('u.numberObjects', 'stock')
->leftJoin('u.userIngredientSuppliers', 'uis')
->leftJoin('uis.numberObjects', 'cost')
->where('u.user = ?1')
->setParameter(1, $portfolioUser)
;
I get the following error :
[Semantical Error] line 0, col 110 near 'p LEFT JOIN u.numberObjects': Error: 'p' is already defined.
500 Internal Server Error - QueryException
1 linked Exception: QueryException »
[1/2] QueryException: SELECT u, uis, cost, p, stock FROM AppBundle:FoodAnalytics\UserIngredient u INDEX BY p.id LEFT JOIN u.product p LEFT JOIN u.numberObjects stock LEFT JOIN u.userIngredientSuppliers uis LEFT JOIN uis.numberObjects cost WHERE u.user = ?1 +
Using u.product I get the following error :
[Semantical Error] line 0, col 87 near 'product LEFT': Error: Invalid PathExpression. Must be a StateFieldPathExpression.
500 Internal Server Error - QueryException
1 linked Exception: QueryException »
Using just product, I get the following error:
[Semantical Error] line 0, col 85 near 'product LEFT': Error: 'product' does not point to a Class.
500 Internal Server Error - QueryException
1 linked Exception: QueryException »
It works fine if I use u.id
Can I only use index by a field from the same table ?
What can I do to make it work ?
Thansk a lot !
EDIT :
As a temporary fix I am using :
$result = $queryBuilder->getQuery()->getResult();
$result = array_combine(array_map(function(UserIngredient $userIngredient){
return $userIngredient->getProduct()->getId();
}, $result), $result);
return $result;
The attribute indexBy only apply to the entity you are currently selecting, as you have guessed (and AFAIK); so in your case you could only index by u.id.
This makes sense to me, since indexing from other entity really mess up the returned results. The only situation in which you'll get proper result is when:
all the join from the main entity and the "target" indexBy entity are one-to-one;
all the join are INNER JOIN;
the indexBy column of the resultset is unique.
In every other case you loose some instance reference during hydration.
In your example, you are using LEFT JOIN: what will happen to all entities without product? All discarded, so the LEFT JOIN would have worker like an INNER JOIN. Also, your workaround suggest that you want to combine entities with the same index, but this is not how Doctrine work: in Doctrine an indexBy column MUST be unique. See the official docs for more info.
Note that you could indexBy p.id in your JOIN clause, but this has different meaning. It means that when hydrating the UserIngredients instances, the collection of product should be indexed by id.
So, my suggestion is to follow one of this two solution:
give up on Doctrine indexBy, and use your workaraound;
If you have the inverse association Product -> UserIngredients, use Product as the main entity of the query (the from clause, and first to appear in select), and then index by p.id.
Which one to use depends on your business logic, but I generally prefer the second solution, since the first generate multiple-level array, which are better represented by entity relations.
Hope this help! :)
I have the following query. The query is inside my InstagramShopPicture
$queryBuilder = $this->createQueryBuilder('p')
->select('p.id, p.caption, p.price, p.lowresimageurl, p.medresimageurl, p.highresimageurl, p.numberoflikes, p.numberofdislikes, shop.id, shop.username, shop.fullname, contact, category')
->leftJoin('p.shop', 'shop')
->leftJoin('shop.contact', 'contact')
->leftJoin('p.category', 'category')
->leftJoin('category.pictureTopCategory', 'pictureTopCategory')
->leftJoin('category.pictureFirstLevelCategory', 'pictureFirstLevelCategory')
->leftJoin('category.pictureSecondLevelCategory', 'pictureSecondLevelCategory')
->where('p.isLocked = false')
->andWhere('p.isStyleInspiration = false')
->andWhere('shop.isLocked = false');
I am however getting the following error:
QueryException: [Semantical Error] line 0, col -1 near 'SELECT p.id,': Error: Cannot select entity through identification variables without choosing at least one root entity alias
any idea on how to solve this?
After some research here in SO I came to this solution. Try adding
->from('YourEntityNameForP', 'p')
->from('YourEntityNameForShop', 'shop')
to createQueryBuilder
Since I'm not familiar neither with Symfony 2, nor with Doctrine 2 and just trying to help!
Credits here: Doctrine: Cannot select entity through identification variables without choosing at least one root entity alias
Seems you are trying to make joins without using aliases for your tables.
This chould cause you a lot of problems, particularly when column names are identical
->leftJoin('p.category', 'category') // aliasing
->leftJoin('category.pictureTopCategory', 'pictureTopCategory') // table name
One time you are using table name, next you are using aliases !! Choose only one approach of them
I got two tables with an ManyToOne Relationship (Many "Entity1" to One "Entity2").
Now I need to get the first and the last Entity from Entity1 which belongs to Entity2. The amounts of "Entity1" for a "Entity2" will be counted in the "entity1_in_entity2_id" Column.
Also there is a entity2_id Column in Entity1 to indentify Entity2.
I know I could achieve this with an nativeQuery with Subselects and I already did this, but now I want to do this with the QueryBuilder but I don't know how. I want to try MAX() and MIN() to get the last and first one.
The var $id is set by the function which calls the function this snippet is from.
I tried some coding by myself, this I what I got so far:
$qb = $this->createQueryBuilder('e');
$qb->select()
->where('e.entity2_id = ' . $id)
->orwhere($qb->expr()->orX(
$qb->expr()->min('e.entity1_in_entity2_id'),
$qb->expr()->max('e.entity1_in_entity2_id')
));
return $qb->getQuery()->getResult();
By now I get the following error
[Syntax Error] line 0, col 114: Error: Expected =, <, <=, <>, >, >=, !=, got 'OR'
And don't worry about the names, I must use dummy data sadly, would be easier to explain if use the real table names :D
Based on doctrine documentation:
http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/query-builder.html#the-expr-class
It should be something like:
->add('where', $qb->expr()->orX(
$qb->expr()->eq('u.id', $qb->expr()->eq('e.entity1_in_entity2_id', $qb->expr()->min('e.entity1_in_entity2_id')),
$qb->expr()->eq('u.id', $qb->expr()->eq('e.entity1_in_entity2_id', $qb->expr()->max('e.entity1_in_entity2_id'))
))
if you really want to use expr, you can also just use
->addSelect('MAX(e.entity1_in_entity2_id) as maxid,MIN(e.entity1_in_entity2_id) as minid')
->having('e.entity1_in_entity2_id IN(minid,maxid)
or
->having('e.entity1_in_entity2_id = minid OR e.entity1_in_entity2_id = maxid')