How to get 'most commented posts' in Doctrine? - php

I have Post entity. Post entity have one Category, many Tags and many Comments.
How to get 'most commented posts' ordered by most commented?
When I am doing this:
$query = $this->createQueryBuilder('post')
->addSelect('COUNT(comments.id) AS HIDDEN comments_num')
->innerJoin('post.comments', 'comments')
->groupBy('post.id')
->orderBy('comments_num', 'DESC')
->addOrderBy('post.created', 'DESC')
->getQuery()
;
it is working but Doctrine makes so many queries, but the problem is the more comments the more queries *times something... It cant be that way. On the page with this query results I have 71 queries and the number of them grows with commented posts number.
When I am doing the same but adding left joining and selecting category, tags and comments there... queries number is low and independent from comments number but when I pass that query to Paginator it gets kind of empty pages after few pages with results...
$query = $this->createQueryBuilder('post')
->addSelect('category, tags, comments')
->addSelect('COUNT(c) AS HIDDEN comments_num')
->innerJoin('post.comments', 'c')
->innerJoin('post.comments', 'comments')
->leftJoin('post.category', 'category')
->leftJoin('post.tags', 'tags')
->groupBy('post.id')
->addGroupBy('tags.id')
->addGroupBy('comments.id')
->orderBy('comments_num', 'DESC')
->addOrderBy('post.created', 'DESC')
->getQuery()
;
query will be passed to Paginator so it must be build in DQL or using QueryBuilder.
edit:
I think there is a bug in Paginator, because... When I dump that second query results I am getting good results in array collection of 12. But when I pass that query to Paginator and then call Paginator.count() I get results: 55 :D when there are 44 posts in test database :)

In The first moment Write your query just with SQL, after do you can execute pure SQL in doctrine.
After you try convert your SQL for DQL

Related

Laravel where with 2 tables

I'm trying to check the another table to remove the matches from the results but unable to figure this out.
$value = people::select(array('people.blog_id'))
->join('blocks', 'people.user_id', '=', 'blocks.blocker')
->where('people.user_id', $user->id)
->where('blocks.blocker', '!=', 'people.user_id')
->get()
->toArray();
What I am trying to achieve, is to strip away the results when getting user_id from people where blocker is found as well in the blocks table, but the following returns an empty array.
As per laravel doc
You may use the table method on the DB facade to begin a query. The table method returns a fluent query builder instance for the given table, allowing you to chain more constraints onto the query and then finally get the results using the get method.
Change your query statement like bellow-
$articles = DB::table('people')
->join('blocks', 'people.user_id', '=', 'blocks.blocker')
->where('blocks.blocker', '<>', 'people.user_id')
->select('people.blog_id')
->get();
I think you should use right join here instead of simple join,
You can do it like.
$value = people::select(array('people.blog_id'))
->join('blocks', 'people.user_id', '=', 'blocks.blocker', 'right')
->where('people.user_id', $user->id)
->get()->toArray();
Please notice the fourth parameter in the join statement, this will include only the results where blocks will find.
Hope this will help

Prevent getting a duppicate data from the database in CodeIgniter

I'm trying to get data from the database filtered by some categories
This is my code in CodeIgniter
$this->db
->select('*')
->from($this->table)
->join('sites','sites.id = categories_by_site.site_id')
->where('categories_by_site.category_id', $categories[0])
->or_where('categories_by_site.category_id', $categories[1])
->order_by('id', 'ASC')
->get()
->result();
I simplify my code for the sake of this question, the above query take the categories as a search filter and used it to get result from the database.
There can be many categories filter to search at the same time, that's why I am using or_where() method.
The problem with this, when I got the result data, it has duplicate row of entries in object array.
Anyone can suggest how to prevent from getting a duppicate data from the database using above query?
Thanks
You can use group_by to solve this issue
Replace your code with
$this->db
->select('*')
->from($this->table)
->join('sites','sites.id = categories_by_site.site_id')
->where('categories_by_site.category_id', $categories[0])
->or_where('categories_by_site.category_id', $categories[1])
->order_by('id', 'ASC')
->group_by('categories_by_site.category_id')
->get()
->result();
You can eleminate duplicate values using distinct or group by
As you select all fields a group by is better in my opinion. Example to group by category_id
$this->db->group_by('category_id');

Doctrine QueryBuilder groupBy relation not working

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.

Symfony Doctrine Query Builder Where last in arraycollection

I want to use symfony's query builder and add a where to the last item in an array collection
$query = $em->getRepository('RlBookingsBundle:Booking')->createQueryBuilder('b')
->select('b, v, c, ca, q')
->leftJoin('b.vehicle', 'v')
->leftJoin('b.customer', 'c')
->leftJoin('c.address', 'ca')
->leftJoin('b.quote', 'q')
->leftJoin('b.history', 'h') //This is an array collection
->orderBy('b.edited', 'DESC')
;
I want to use only the latest value from history as it is a log but only the most recent entry is valid
->where('h.status IN (:status)')
->setParameter('status', [7]);
Will return all results with h.status = 7 but I would like it to only query the most recent result. Is there anyway to do this?
I tried a groupby on the history field but this seems to groupby with data from the first entry, even if I add an orderby to it.
If the results you get are already ok, but you only want the first, you could just use
...
->setMaxResults(1)
...
If you want to order by history ID desc, you may want to add another orderBy clause before the existing one
...
->orderBy('h.id', 'DESC')
->orderBy('b.edited', 'DESC')
...
If it's more complex than that, I strongly suggest you perform a separate query to get the desired record(s) from history, and THEN use it as a filter, instead of the leftJoin.

Only get the first row while doing a leftjoin

Is it possible to limit the result while doing a leftjoin?
(Laravel 4.2) - Querybuilder
I've got the following query with laravel's querybuilder:
DB::table('part')
->leftjoin('model', 'model.model_id', '=', 'part.model_id')
->leftjoin('make', 'model.make_id', '=', 'make.make_id')
->leftJoin('photo', 'photo.part_id', '=', 'part.part_id')
->select( 'part.part_id',
'part.model_id',
'make.desc as make_desc',
'model.desc as model_desc',
'photo.local as local_img',
'photo.cdn as cdn_img')
->take(8)->get();
Every part has more then 4 photos, but i only want the first photo to be included in the join. The problem is that when i use this query, i get 8 part objects (results). But the 8 results are not 8 parts, but 2 parts. This query creates 4 of the same part objects, with the only difference being the photo (the join includes every photo).
I tried things like:
->select( '(photo.local LIMIT 1) as local_img',
'(photo.cdn LIMIT 1) as cdn_img')
But this doesn't work. I also tried to do raw query's. Also i tried to use the '->take(1)' in a leftjoin closure, like this:
->leftjoin('photo', function($q){
$q->on('photo', 'photo.part_id', '=', 'part.part_id')->take(1);
});
But this is not possible.
I`m searching for a solution to only include the first photo row in a leftjoin.
Edit: Following up on mgrueter's answer. I know that a groupby would do the trick, but this makes the query very slow. So i want to do it in a different way so the query doesn't get to slow.
Group the results by 'part.id':
DB::table('part')
->leftjoin('model', 'model.model_id', '=', 'part.model_id')
->leftjoin('make', 'model.make_id', '=', 'make.make_id')
->leftJoin('photo', 'photo.part_id', '=', 'part.part_id')
->select( 'part.part_id',
'part.model_id',
'make.desc as make_desc',
'model.desc as model_desc',
'photo.local as local_img',
'photo.cdn as cdn_img')
->group_by('part.part_id')
->take(8)->get();
And also order the results by 'photo.created' or whatever column you have to determine, which is the first photo.

Categories