I have a similar problem and DB structure:
Doctrine2 association mapping with conditions
But, I need collections of Article with approved comments:
How to do assotiation mapping, that do without N+1 ?
$articles = $repository->findAllArticlesWithApprovedComments();
Foreach($articles as $article){
$article->getTitle();
foreach($article->getAprovedComments() as $comment){
$comment->getText();
}
}
Criteria worked, if i use lazy load, but its N+1 problem. If I use Eager load (join and addSelect ) - Criteria not work.
If I use this code:
$articles = $em->createQueryBuilder()
->from(Article::class,'article')
->leftJoin('article.comments','comments')
->addSelect('article')
->addSelect('comments')
->andWhere('comments.approved= :ap ')
->setParameter('ap',true)
->getQuery()->getResult();
I will receive articles with approved comments, but if the article has 0 comments, it will not fall into the collection of articles.
How to get articles with approved comments, but if there are no comments in the article, the article remains in the collection?
Example:
I have in DB:
Article1: [approvedComment, nonAprovedComment]
Article2: [nonAprovedComment]
Article3: [approvedComment]
I need result (with doctrine, non filter in code):
Article1: [approvedComment]
Article2: []
Article3: [approvedComment]
You can use a join instead of a where condition to filter your collection on the database level.
Your query would then look like this:
$articles = $em->createQueryBuilder()
->from(Article::class, 'article')
->leftJoin('article.comments', 'comments', 'WITH', 'comments.approved = :ap')
->addSelect('article')
->addSelect('comments')
->setParameter('ap', true)
->getQuery()->getResult();
Since this is a left join, it will return all articles, even if they do not have any approved comment.
Related
I have comments table where has parent_id
This is Comment table sub_comments relation.
public function sub_comments()
{
return $this->hasMany(self::class, 'parent_id');
}
This code return all comments with related all sub-comments
Comment::with('sub_comments')->get();
But I want to get all comments also sub-comments when sub-comments is single. That mean if comment have 2 or more comments for that comment I did not want get that sub-comments.
Now I use this code
$oneSubcommentCommentIds = Comment::has('sub_comments', '=', 1)->pluck('id');
Comment::with([
'sub_comments' => function ($q) use ($oneSubcommentCommentIds) {
$q->whereIn('parent_id', $oneSubcommentCommentIds);
}
])->get();
but this make one additional query.
Try this:
Comment::with('sub_comments')->has('sub_comments', '=', 1)->get();
Update
Your question wasn't clear, I can't imagine another way to doing this without previosly loaded the relationship or the count of the relationship.. so I'd do this:
// First get all your comments with an aditional count field
$comments = Comments::withCount('sub_comments')->get();
// separate the ones with just one sub_comment from the rest
list($oneSubComment, $theRest) = $collection->partition(function ($comment) {
return $comment->sub_comments_count == 1;
});
// Then load the relationship on just the selected elements
$oneSubComment->load('sub_comments');
// re-join the collection
$comments = $oneSubComment->union($theRest);
What am I doing here?
Adding an additional field to each $comment with the relationship count (it should be something like sub_comments_count)
Partition the resulting collection in two parts: the ones with one comment and the rest. Using the partition() method.
Lazy eager loading the collection.
Re-joining the two collections using the union() method.
My product can have many categories. In one part of the object however, I need to get a specific Category. So instead of getting all the categories and then in a for loop search for specific one, I need to get only this specific category. For that I am using query builder.
public function findProduct($id) {
$qb = $this->createQueryBuilder('p')
->addSelect(array('p', 'cat')) // many to many table
->addSelect(array('p', 'category')) // the category entity
->leftJoin('p.category', 'cat')
->leftJoin('cat.category', 'category')
->andWhere("category.id = 15") // error here
->SetParameter('id', $id);
return $qb->getQuery()->getOneOrNullResult();
}
With this query I am easily able to do $product->getCategory[0]([] since array) and get only the category that I need(in this example category with id=15)
THE PROBLEM:
However if the product doesnt have a category with a specific id.. It returns whole product null..
So If i do:
$product = $em->getRepository('MpShopBundle:Product')->findProduct($id); = null
But instead it should be like this:
$product = $em->getRepository('MpShopBundle:Product')->findProduct($id); = object
$product->getCategory() = null
How can I make this work in query builder? Is that even possible?
This should work. Instead of constraining your whole query (which is what that does) just constrain the join).
leftJoin('cat.category', 'category', 'WITH', 'category.id = 15')
This way you should get your product always & category only if id == 15.
When I execute something like this in Doctrine:
$qb = $doctrine
->getRepository('EntityA')
->createQueryBuilder('a')
->addSelect('b')
->join('EntityB', 'b', 'WITH', 'a.b = b')
->getQuery()
->getResult()
I get an array that looks like this:
array(0 => EntityA,
1 => EntityB,
2 => EntityA,
4 => EntityB)
In fact, I get 2 result rows, but an array which has a size of 4. This makes iterating over it for displaying in templates nearly impossible.
I would like a result like this:
array(0 => array(EntityA, EntityB),
1 => array(EntityA, EntityB))
Of course I could create a mapping on EntityA that references EntityB. But even with the possibility to change the loading behavior, LAZY, EAGER etc., it could be desirable to have the possibility to create such relations on the fly.
For example:
In overviews with large amounts of Entities, EAGER loading is needed to prevent excessive amounts of queries. But when I want to display only one Entity and do not need the extra data, LAZY loading is more desirable.
Since you have EntityB mapped as EntityA.b, as indicated by this relation:
...join('EntityB', 'b', 'WITH', 'a.b = b')...
Thus you do not need in fact to add b to the select as you are loading it either eagerly or lazily.
Remove ->addSelect('b') from your query builder and use EntityB trough your EntityA.b mapping.
Example:
$as = $doctrine
->getRepository('EntityA')
->createQueryBuilder('a')
->join('EntityB', 'b', 'WITH', 'a.b = b')
->getQuery()
->getResult();
foreach($as as $a){
echo "EntityA property".$a->id;
echo "EntityB property".$a->b->id;
}
This query will return an ArrayCollection of EntityA.
Note: The example above assumes that EntityA.b is a public property.
Update:
After some digging I found this older post: Doctrine 2 QueryBuilder add multiple select elements /parameters?. According to it you can get the result you wan by directly separarating the entities with commas instead of using the ->addSelect() method.
Try the following DQL:
$query = $em->createQuery("SELECT a, b FROM EntityA a JOIN a.b b")
The query above will dynamically eagerly fetch b.
I have two tables in DB with OneToMany relations. Using 'createQueryBuilder()' method in 'EntityRepository' i try to select some objects with conditions. There is my method:
$query = $this->getEntityManager()->createQueryBuilder();
$query->select("parent")
->from('TestAppBundle:Parent', 'parent')
->leftJoin("parent.children", 'child', 'WITH', 'child.IdP = parent.id')
->where('child.date < :date')
->andWhere('child.status = :null')
->setParameter('date', new DateTime())
->setParameter('null', 0);
And it works almost good. I get Parent Objects with Children Objects in ArrayCollections. Methods select the Parent Objects with conditions but the problem is I get also Children Objects which don't keep the conditions.
I want to get only Parent Objects which keep conditions and Children Objects which also keep conditions. At this time a have to filter results after query and remove Children Object manualy.
I hope you will understand the problem :)
Basically, if you don't select the entities at query time you'll lazy load all children for the parent when you call getChildren(). Select both children and parents like this to avoid lazy loading:
$query->select("parent, child")
For more information please see my answer to a similar question.
Could you try something like this:
$query = $this->getEntityManager()->createQueryBuilder();
$query->select("parent")
->from('TestAppBundle:Parent', 'parent')
->leftJoin("parent.children", 'child', 'WITH', 'child.status = 0');
As far as I understand your needs you should not use leftJoin - with this field you will get parents that don't have children. Use innerJoin instead.
Assuming that child.IdP refers to the parent.id in your model definition you don't have to use WITH clause in this way. So the queryBuilder would be:
$query = $this->getEntityManager()->createQueryBuilder();
$query->select("parent")
->from('TestAppBundle:Parent', 'parent')
->innerJoin("parent.children", 'child')
->where('child.date < :date and child.status = :null')
->setParameter('date', new DateTime())
->setParameter('null', 0);
Regards.
I have a problem with the toArray() method in Doctrine. Its doesn't get my relations:
First query :
$q = Doctrine::getTable('posts')->find(1);
debug($q->toArray(true));
Print the postid=1 with out the relations
$q = Doctrine::getTable('posts')->find(1);
$q->Tags->toArray();
debug($q->toArray(true));
...prints the results with tag relation.
But i want to do:
Doctrine::getTable('posts')->findAll()->toArray(true);
...and get all of relations of posts , instead I got an array of post row.
Any idea about how to make it work with the relations?
(notice i added toArray(true) for deep property.
thanks for any help
You could create named query for this table with all relations attached:
Doctrine::getTable('posts')->addNamedQuery('get.by.id.with.relations', 'DQL here...');
And then just use something like this:
Doctrine::getTable('posts')->find('get.by.id.with.relations', array(123));
I beleive you need to do a Join with the query. Otherwise it doesnt hydrate the realated data.
$q = Doctrine_Query::create()
->from('Post p')
->leftJoin('p.RelatedModel1 rm1')
->leftJoin('p.RelatedModel2 rm2');
$q->findAll()->toArray(true);
$q = Doctrine_Query::create()
->from('Post p')
->leftJoin('p.RelatedModel1 rm1')
->leftJoin('p.RelatedModel2 rm2');
$q->findAll()->toArray(true);
Can i Add ->limit()->offset()
to the query ?
I guss that if i first create the query then findAll will act the same as execute right ?