Append Relation Count while using select in eloquent query - laravel

So i have a query where i want to select specific columns and relation count on a model.
Something like this
$posts = Post::withCount('comments')->select('title', 'content')->get();
foreach ($posts as $post) {
$post->comments_count // this is not available because of select in query
}
Now when I use select in the query comments_count is no longer available. There is $appends option on the model where I can do something like $appends = ['comments_count'] on the Post model but it will not work. Any idea how to append the data and use the select on model while querying using eloquent.
there is $withCount option on the model as well but it will lazy load while querying comments with post (i.e. inverse relation query).

The problem if that withCount('comments') will generate the comments_count for each post.
Also, the select() overrides the previous withCount(). Simply change the order.
$posts = Post::select('title', 'content')->withCount('comments')->get();
foreach ($posts as $post) {
echo $post->comments_count;
}
If you want the global count, you'll need to use a collection method to sum all the counts.
echo $posts->sum('comments_count');

Related

Laravel Eloquent Lazy Eager Load Count

I'm ideally looking for a function like
load('relationship')
but which loads a count in the same way
withCount('relationship')
works for eager loading.
I'm thinking it is going to be called loadCount('relationship')
loadCount() is available since Laravel 5.8
$post->loadCount('comments');
$post->comments_count;
Docs
As of Laravel 5.2, this functionality is built-in.
Provided you have a hasMany relationship between Post and Comment, do:
<?php
$posts = App\Post::withCount('comments')->get();
foreach ($posts as $post) {
echo $post->comments_count;
}
You can even eager load relationships count by default by declaring this in your model:
<?php
// Post model
protected $withCount = ['comments'];
This solution works great for me:
Create a new hasOne relationship on the related model and add a raw select to the query for the count. For example, if you want to eager load the number of tasks for a given user, add this to the User Model:
public function taskCount()
{
return $this->hasOne('App\Task')
->selectRaw('user_id, count(*) as count)
->groupBy('user_id');
}
And then eager load the count like this:
$user = User::where('email', $email)->with('taskCount')->first();
And access the count like this:
$taskCount = $user->task_count->count;

Laravel 5.0 how to order an eloquent with() query

I have two models: item and faq. The are in a belongsToMany with each other with a correctly created join table: item_faq (singular of both). My join table has an additional field on it for order.
In my view I get all the faq's and if they have a pivot table record I output "checked" on a checkbox. I also have drag and drop ordering on the checkbox list and that works well.
A few code notes:
// ITEMS MODEL
public function faqs(){
return $this->belongsToMany('App\Faq');
}
// FAQ MODEL
public function items(){
return $this->belongsToMany('App\Item');
}
public function hasItem($item) {
$items = $this->items->lists('id');
return in_array($item, $items);
}
Schema of join table:
item_id
faq_id
order
timestamps
My issue is that they faq's don't load sorted by the order column on the pivot table.
I am using a very simple:
$faqs = \App\Faq::with('items')->get();
To retrieve the FAQ's and this works at getting all the faq's and if they are related, it checks the checkbox.
How can I order these by the order column on the join table?
Have a look at Eager Load Constraints and I think it will help provide a solution. From the docs:
Of course, eager loading Closures aren't limited to "constraints". You may also apply orders:
$users = User::with(['posts' => function($query) {
$query->orderBy('created_at', 'desc');
}])->get();

flattening joins in eloquent

If I wanted to JOIN two tables I could do this:
$books = App\Book::with('author')->get();
The problem with that is that the results are kinda nested. eg. If I wanted to get the DOB of the author I'd have to do this:
foreach ($books as $book) {
echo $book->author->dob;
}
But what if I, instead, wanted to be able to do this?:
foreach ($books as $book) {
echo $book->dob;
}
How might I go about doing that? I suppose there could be a conflict dob was a column in the book table and the author table and that doing author_dob would be better but let's assume there isn't a conflict.
I guess a view could do the trick but is there a way to do that in Laravel without having to create a view?
$books = DB::table('books')
->join('authors', 'authors.id', '=', 'books.author_id')->get()
Actually you can do the same using eloquent. This way, you'll have access to all functions and extra parameters in your Book model file
App\Book::join('authors', 'authors.id', '=', 'books.author_id')->get()
You might want to use leftJoin() instead of join()

Laravel - Eloquent "Has", "With", "WhereHas" - What do they mean?

I've found the concept and meaning behind these methods to be a little confusing, is it possible for somebody to explain to me what the difference between has and with is, in the context of an example (if possible)?
With
with() is for eager loading. That basically means, along the main model, Laravel will preload the relationship(s) you specify. This is especially helpful if you have a collection of models and you want to load a relation for all of them. Because with eager loading you run only one additional DB query instead of one for every model in the collection.
Example:
User > hasMany > Post
$users = User::with('posts')->get();
foreach($users as $user){
$users->posts; // posts is already loaded and no additional DB query is run
}
Has
has() is to filter the selecting model based on a relationship. So it acts very similarly to a normal WHERE condition. If you just use has('relation') that means you only want to get the models that have at least one related model in this relation.
Example:
User > hasMany > Post
$users = User::has('posts')->get();
// only users that have at least one post are contained in the collection
WhereHas
whereHas() works basically the same as has() but allows you to specify additional filters for the related model to check.
Example:
User > hasMany > Post
$users = User::whereHas('posts', function($q){
$q->where('created_at', '>=', '2015-01-01 00:00:00');
})->get();
// only users that have posts from 2015 on forward are returned
The documentation has already explained the usage, so I will use SQL to explain the methods.
Example:
Assuming there is an Order (orders) has many OrderItem (order_items) and you already built the relationship between them:
// App\Models\Order:
public function orderItems() {
return $this->hasMany('App\Models\OrderItem', 'order_id', 'id');
}
These three methods are all based on a relationship.
with
Result: with() return the model object and its related results.
Advantage: It is eager-loading which can prevent the N+1 problem.
When you are using the following Eloquent Builder:
Order::with('orderItems')->get();
Laravel change this code to only two SQL:
// get all orders:
SELECT * FROM orders;
// get the order_items based on the orders' id above
SELECT * FROM order_items WHERE order_items.order_id IN (1,2,3,4...);
And then Laravel merges the results of the second SQL query with the results of the first SQL by foreign key, finally returning the collection results.
So if you selected columns without the foreign_key in a closure, the relationship result will be empty:
Order::with(['orderItems' => function($query) {
// $query->sum('quantity');
$query->select('quantity'); // without `order_id`
}
])->get();
#=> result:
[{ id: 1,
code: '00001',
orderItems: [], // <== is empty
},{
id: 2,
code: '00002',
orderItems: [], // <== is empty
}...
}]
has
Has will return the model's object when its relationship is not empty.
Order::has('orderItems')->get();
Laravel changes this code to one SQL query:
select * from `orders` where exists (
select * from `order_items` where `orders`.`id` = `order_items`.`order_id`
)
whereHas
The methods whereHas and orWhereHas put where conditions on your has queries. These methods allow you to add customized constraints to a relationship constraint.
Order::whereHas('orderItems', function($query) {
$query->where('status', 1);
})->get();
Laravel changes this code to one SQL query:
select * from `orders` where exists (
select *
from `order_items`
where `orders`.`id` = `order_items`.`order_id` and `status` = 1
)

Laravel 4 - Get Array of Attributes from Collection

I have a collection of objects. Let's say the objects are tags:
$tags = Tag::all();
I want to retrieve a certain attribute for each tag, say its name. Of course I can do
foreach ($tags as $tag) {
$tag_names[] = $tag->name;
}
But is there a more laravelish solution to this problem?
Something like $tags->name?
Collections have a lists method similar to the method for tables described by #Gadoma. It returns an array containing the value of a given attribute for each item in the collection.
To retrieve the desired array of names from my collection of tags I can simply use:
$tags->lists('name');
Update: As of laravel 5.2 lists is replaced by pluck.
$tags->pluck('name');
More specifically, the laravel 5.2 upgrade guide states that "[t]he lists method on the Collection, query builder and Eloquent query builder objects has been renamed to pluck. The method signature remains the same."
Yep, you can do it nice and easily. As the Laravel 4 Documentation states, you can do
Retrieving All Rows From A Table
$users = DB::table('users')->get();
foreach ($users as $user)
{
var_dump($user->name);
}
Retrieving A Single Row From A Table
$user = DB::table('users')->where('name', 'John')->first();
var_dump($user->name);
Retrieving A Single Column From A Row
$name = DB::table('users')->where('name', 'John')->pluck('name');
Retrieving A List Of Column Values
$roles = DB::table('roles')->lists('title');
This method will return an array of role titles.
You may also specify a custom key column for the returned array:
$roles = DB::table('roles')->lists('title', 'name');
Specifying A Select Clause
$users = DB::table('users')->select('name', 'email')->get();
$users = DB::table('users')->distinct()->get();
$users = DB::table('users')->select('name as user_name')->get();
EDIT:
The above examples show how to access data with the help of Laravel's fluent query builder. If you are using models you can access the data with Laravel's Eloquent ORM
Because Eloquent is internaly using the query builder, you can without any problem do the following things:
$tag_names = $tags->lists('tag_name_label', 'tag_name_column')->get();
which could be also done with:
$tag_names = DB::table('tags')->lists('tag_name_label', 'tag_name_column')->get();
Here are a few snippets from my own experimentation on the matter this morning. I only wish (and maybe someone else knows the solution) that the Collection had a $collection->distinct() method, so I could easily generate a list of column values based on an already filtered collection.
Thoughts?
I hope these snippets help clarify some alternative options for generating a list of unique values from a Table, Collection, and Eloquent Model.
Using a Collection (Happy)
/**
* Method A
* Store Collection to reduce queries when building multiple lists
*/
$people = Person::get();
$cities = array_unique( $people->lists('city') );
$states = array_unique( $people->lists('state') );
// etc...
Using an Eloquent Model (Happier)
/**
* Method B
* Utilize the Eloquent model's methods
* One query per list
*/
// This will return an array of unique cities present in the list
$cities = Person::distinct()->lists('city');
$states = Person::distinct()->lists('state');
Using an Eloquent Model PLUS Caching (Happiest)
/**
* Method C
* Utilize the Eloquent model's methods PLUS the built in Caching
* Queries only run once expiry is reached
*/
$expiry = 60; // One Hour
$cities = Person::remember($expiry)->distinct()->lists('city');
$states = Person::remember($expiry)->distinct()->lists('state');
I would love to hear some alternatives to this if you guys have one!
#ErikOnTheWeb
You could use array_column for this (it's a PHP 5.5 function but Laravel has a helper function that replicates the behavior, for the most part).
Something like this should suffice.
$tag_names = array_column($tags->toArray(), 'name');

Resources