How to count items join it and paginate? - php

I'm trying to count some items from the table and join it with the another table so I use the following code
Article::join("article_comments", "article_comments.article_id", "=","articles.id")->
select(["articles.title", "articles.content", "articles.created_at", DB::raw('count(article_comments.id) as commentsCount')])->paginate(10) ;
But I always get only first item

Shouldn't there be a group by if you use aggregates?
Something like:
Article::join("article_comments", "article_comments.article_id", "=","articles.id")->
select(["articles.title", "articles.content", "articles.created_at", DB::raw('count(article_comments.id) as commentsCount')])->
groupBy('articles.id')->
paginate(10);
Also since you're no using your joined table for filtering you might wanna consider eager loading. However in your case you only get articles with comments. When using eager loading you'd get all articles (including the ones without comments).
Something like that should work:
Article::with([
'comments' => function ($query) {
$query
->select('article_id', DB::raw('COUNT(`id`) AS `commentCount`'))
->groupBy('article_id')
}
])
->paginate(10);
Now you should be able to access the count like this:
echo $article->comments->first()->commentCount;
Did not test the solution. You might wanna check if $article->comments->first() exists.
Or if you wanna expand your model a little here are some even better thoughts

Related

Adding custom collection to the eloquent within Laravel Query Builder

I have a query and I want to add a collection using Laravel query builder.
Hotel::addSelect([
'selectableLocations' => AllLocations::orderBy('name')->get()
])
->with('location')
->get();
Well, this returns:
SQLSTATE[HY093]: Invalid parameter number: mixed named and positional parameters (SQL: select [{"id":1,"name":"John Avenue"},{"id":4,"name":"Ontirio Village"},{"id":2,"name":"Rovie"},{"id":3,"name":"Movie Lot"}] from dogs limit 100 offset 0)
I know this may seem like an anti-pattern but the reason I need this is because I have a datatable and want to show a select (dropdown) with AllLocations so that user can change in the datatable.
My idea is that I can do $dog->selectableLocations to see all locations. Because if I don't do it like this, it will query for each row individually.
Is there a way to do that?
If I could achieve it like this, that'd be perfect.
$selectableLocations = AllLocations::get();
$hotels = Hotel::addSelect([
'selectableLocations' => $selectableLocations
])
->with('location')
->get();
EDIT:
Because if I don't do it like this, it will query for each row individually.
Since your primary concern is multiple queries, you could avoid db calls if you implement some sort of caching. Why not include the list as a custom attribute, load collection from cache? Like so:
public function getAllLocationsAttribute()
{
return Cache::remember('all_locations', 30, function(){ //add remove seconds as required
return AllLocatiobs::get();
});
}
How about getting merging the two collections?
$hotels = Hotel::get();
$selectableLocations = AllLocations::get();
$hotels->put('selectableLocations', $selectableLocations);
Now, $hotels->selectableLocations will be a collection of AllLocations.

how to get none related objects of Many to Many relationship

I have user and news table and then a middle table called news_user, the middle table determines which news has been seen by the user. I can easily get objects that have been seen but, but now I need to show the user objects that have not been seen.
I did a workaround with putting all the seen news id in an array and look for news where id differs from the array. but I don't consider it as the healthiest solution.
This is what I did:
$seenNewses = DB::table('news_user')->where('user_id', Auth::id())
->pluck('news_id')->toArray();
$notSeenNewses = News::whereNotIn('id', $seenKeepers)
->orderBy('id', 'asc')->first();
Is there a way I can do this with a single query?
P.S: I have seen similar questions but they got little confused. Any answer is appreciated.
First in your News Model you need to make relationship like this
public function users()
{
return $this->belongsToMany(News::class,'news_user','news_id','user_id');
}
Below command will give you all the news which are not seen by any users yet
$notSeenNewses = News::doesntHave('users')->get();
If you want to put any condition then also check for
$notSeenNewses = News::whereDoesntHave('users', function ($query) {
$query->where('YOUR CONDITION');
})->get();
It's not tested but you can modify as per your need.

Returning all Laravel eloquent models tagged with a specific set of tags

I've got an Eloquent model Post with a belongsToMany(Tag::class) relationship. Now in the situation where I want to return or get all posts with a given set of tags, what's the most efficient Laravel-esq way of performing such a query?
For example; get all posts that have the tag bbq, or all posts that have the tags bbq AND beef.
I'd like to simply pass an array of tags in which can be of any count, if possible. I have tried the following and a number of different combinations with no luck, granted my SQL kung-fu isn't the greatest.
$posts = Post::whereHas('tags', function ($query) {
$query->whereIn('tag_types.name', ['bbq', 'beef']);
})->get();
Eventually found a solution. In this case it requires knowing the ID's of the tags in question, which is easy enough to query for. In this case I store the tag ID's in an array called $tag_ids and use the following Eloquent query (tag_types being the pivot table):
$posts = Post::join('tag_types', function ($join) use ($tag_ids) {
$join
->on('posts.id', '=', 'tag_types.post_id')
->whereIn('tag_types.tag_id', $tag_ids);
})
->groupBy('posts.id')
->havingRaw('count(distinct tag_types.tag_id) = ' . count($tag_ids))
->lists('post_id');
$posts now contains the ID's of all the posts that contain every tag ID listed in $tag_ids.
Hope this helps anyone facing a similar dilemma.

Eloquent - Updating all models in a collection

I want to set a certain attribute in all the models of a collection.
in plain SQL:
UPDATE table SET att = 'foo' WHERE id in (1,2,3)
the code i have:
$models = MyModel::findMany([1,2,3]);
$models->update(['att'=>'foo']);
taken from here
but doesn't work. I'm getting
Call to undefined method Illuminate\Database\Eloquent\Collection::update()
the only way i have found it's building a query with the query builder but i'd rather avoid that.
You are returning a collection, not keeping the query open to update. Like your example is doing.
$models = MyModel::whereIn('id',[1,2,3]);
$models->update(['att'=>'foo']);
whereIn will query a column in your case id, the second parameter is an array of the ids you want to return, but will not execute the query. The findMany you were using was executing it thus returning a Collection of models.
If you need to get the model to use for something else you can do $collection = $models->get(); and it will return a collection of the models.
If you do not just simply write it on one line like so;
MyModel::whereIn('id',[1,2,3])->update(['att'=>'foo']);
Another option which i do not recommend is using the following;
$models = MyModel::findMany([1,2,3]);
$models->each(function ($item){
$item->update(['att'=>'foo']);
});
This will loop over all the items in the collection and update them individually. But I recommend the whereIn method.
The best solution in one single query is still:
MyModel::whereIn('id',[1,2,3])->update(['att'=>'foo']);
If you already have a collection of models and you want to do a direct update you can use modelKeys() method. Consider that after making this update your $models collection remains outdated and you may need to refresh it:
MyModel::whereIn('id', $models->modelKeys())->update(['att'=>'foo']);
$models = MyModel::findMany($models->modelKeys());
The next example I will not recommend because for every item of your $models collection a new extra query is performed:
$models->each(function ($item) {
$item->update(['att'=>'foo']);
});
or simpler, from Laravel 5.4 you can do $models->each->update(['att'=>'foo']);
However, the last example (and only the last) is good when you want to trigger some model events like saving, saved, updating, updated. Other presented solutions are touching direct the database but models are not waked up.
Just use the following:
MyModel::query()->update([
"att" => "foo"
]);
Be mindful that batch updating models won't fire callback updating and updated events. If you need those to be fired, you have to execute each update separately, for example like so (assuming $models is a collection of models):
$models->each(fn($model) => $model->update(['att'=>'foo']) );

Laravel 4 - Eloquent way to attach a where clause to a relationship when building a collection

This may be a dupe but I've been trawling for some time looking for a proper answer to this and haven't found one yet.
So essentially all I want to do is join two tables and attach a where condition to the entire collection based on a field from the joined table.
So lets say I have two tables:
users:
-id
-name
-email
-password
-etc
user_addresses:
-address_line1
-address_line2
-town
-city
-etc
For the sake of argument (realising this may not be the best example) - lets assume a user can have multiple address entries. Now, laravel/eloquent gives us a nice way of wrapping up conditions on a collection in the form of scopes, so we'll use one of them to define the filter.
So, if I want to get all the users with an address in smallville, I may create a scope and relationships as follows:
Users.php (model)
class users extends Eloquent{
public function addresses(){
return $this->belongsToMany('Address');
}
public function scopeSmallvilleResidents($query){
return $query->join('user_addresses', function($join) {
$join->on('user.id', '=', 'user_addresses.user_id');
})->where('user_addresses.town', '=', 'Smallville');
}
}
This works but its a bit ugly and it messes up my eloquent objects, since I no longer have a nice dynamic attribute containing users addresses, everything is just crammed into the user object.
I have tried various other things to get this to work, for example using a closure on the relationship looked promising:
//this just filters at the point of attaching the relationship so will display all users but only pull in the address where it matches
User::with(array('Addresses' => function($query){
$query->where('town', '=', 'Smallville');
}));
//This doesnt work at all
User::with('Addresses')->where('user_addresses.town', '=', 'Smallville');
So is there an 'Eloquent' way of applying where clauses to relationships in a way that filters the main collection and keeps my eloquent objects in tact? Or have I like so many others been spoiled by the elegant syntax of Eloquent to the point where I'm asking too much?
Note: I am aware that you can usually get round this by defining relationships in the other direction (e.g. accessing the address table first) but this is not always ideal and not what i am asking.
Thanks in advance for any help.
At this point, there is no means by which you can filter primary model based on a constraint in the related models.
That means, you can't get only Users who have user_address.town = 'Smallwille' in one swipe.
Personally I hope that this will get implemented soon because I can see a lot of people asking for it (including myself here).
The current workaround is messy, but it works:
$products = array();
$categories = Category::where('type', 'fruit')->get();
foreach($categories as $category)
{
$products = array_merge($products, $category->products);
}
return $products;
As stated in the question there is a way to filter the adresses first and then use eager loading to load the related users object. As so:
$addressFilter = Addresses::with('Users')->where('town', $keyword)->first();
$users= $addressFilter->users;
of course bind with belongsTo in the model.
///* And in case anyone reading wants to also use pre-filtered Users data you can pass a closure to the 'with'
$usersFilter = Addresses::with(array('Users' => function($query) use ($keyword){
$query->where('somefield', $keyword);
}))->where('town', $keyword)->first();
$myUsers = $usersFilter->users;

Categories