flattening joins in eloquent - laravel

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()

Related

Append Relation Count while using select in eloquent query

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');

Counting data before sending to view in Laravel

I have two tables, products and product_images, now I want to show product which has at least one image.
In controller I have a simple function in ProductController to fetch all the products:
public function products(){
$allProducts = $this->product->paginate(15);
return view('frontend.pages.products',compact('allProducts'));
}
But, I want to send the products which has at least one image of each product.
What should I do to achieve that?
Edit:
I have created relationship between tables, now how can I get my desired answer?
I have written this in the Controller:
$allProducts = $this->product->whereHas('product_images', function ($query){
$query->where();
})->get();
Assuming table schema
product
-id
-name
product_image
-id
-product_id
-url //any columns you needed
$product_ids = DB::table('product')
->join('product_image','product_image.product_id','=','product.id')
->select('product.id','roduct.name')
->groupBy('product.id')
->get();
$product_count = count($product_ids);
Eloquent has this built in.
Example
// Retrieve all posts that have three or more comments...
$posts = App\Post::has('comments', '>=', 3)->get();
In your case
In your case you could change the $allProducts line to be
$allProducts = $this->product()->has('product_image', '>=', 1)->paginate(15);
I didn't test the code above.
See documentation for more information on this topic.
https://laravel.com/docs/6.x/eloquent-relationships#querying-relationship-existence
You can use selectRaw:
$products = Product::leftJoin('product_images',function ($join){
$join->on('products.product_id','=','product_images.product_id');
})->selectRaw("products.product_id i, count(product_images.id) c")
->groupBy('products.product_id')
->where('c','>=',3)
->get();
Getting answer querying realtionship:
The query will look like following:
$allProducts = $this->product->has('Images')->paginate(15);
But, make you sure you have created relation in Model like the following:
public function Images(){
return $this->hasMany('\App\Models\ProductImages');
}
I have used hasMany relationship because one product can have multiple images.

Laravel Eloquent complex join statement

My database structure is the following:
So I've got a Bloggers table (sorry for the typo in the image), which has 3 topic fields, all 3 are foreign keys to the topics' table id.
I made a Blogger view front-end with a simple table showing all blogger columns. At the moment, the id's of the topics are being shown instead of the names though.
How can I change this?
I've already tried the following in my Controller, but that just creates duplicates in the view with different values in the main_topic column.
$bloggers= DB::table('bloggers')
->join('topics', function ($join) {
$join
->on('bloggers.main_topic', '=', 'topics.id')
->orOn('bloggers.subtopic1', '=', 'topics.id')
->orOn('bloggers.subtopic2', '=', 'topics.id');
})
->select('bloggers.*', 'topics.name as main_topic')
->get();
return view('pages.bloggers', compact('bloggers'));
The answer to my specific question was a little different from the answers given, so I'll post it here in case anyone needs it in the future.
So I've got 3 tables: bloggers, topics and a pivot table called blogger_topic. bloggers contains 3 foreign keys (integers) to topics called main_topic, subtopic1, subtopic2. The pivot table contains a blogger_id, and a topic_id.
What I ended up with:
Blogger.php model:
public function mainTopic()
{
return $this->belongsTo('App\Models\Topic', 'main_topic', 'id');
}
public function subtopicOne()
{
return $this->belongsTo('App\Models\Topic', 'subtopic1', 'id');
}
public function subtopicTwo()
{
return $this->belongsTo('App\Models\Topic', 'subtopic2', 'id');
}
Topic.php model:
public function bloggers()
{
return $this->belongsToMany('App\Models\Blogger');
}
View (blogger.blade.php):
#foreach($bloggers as $blogger)
{{ $blogger->mainTopic }}
{{ $blogger->subtopicOne }}
{{ $blogger->subtopicTwo }}
#endforeach
I think you should change your database tables if possible as below (sample link), by creating a pivot table. And then I think you should make relationships in Blogger and Topic model files with many-to-many. Then it would be very easy to fetch all related topics. For eg,
$topics = $blogger->topics;
Here you can reference the sample db table designs
I kindly recommend you to use models in Laravel coz they'll make your life more simple. Hope this help.
If you cant change your database, you could use group_concat on the results. Not sure if it will work but something like:
DB::table('bloggers')
->join('topics', function ($join) {
$join
->on('bloggers.main_topic', '=', 'topics.id')
->orOn('bloggers.subtopic1', '=', 'topics.id')
->orOn('bloggers.subtopic2', '=', 'topics.id');
})
->groupby('bloggers.id')
->select('bloggers.*', 'topics.name as main_topic', DB::raw('group_concat(topics.name)'))
->get();

Use Eloquent to attach array to query

I have a USER table, an ITEMS table, and a LIKES table. I'm randomly taking 9 items from the Items table (which is named 'categories') and I want to get an array of users which liked that item. I'm returning a JSON response so I can't use laravel's ORM relationships as far as I know. I want to be able to look through the results (with javascript) like so:
foreach item
item->price
....
foreach user
(this is how i wish to look through the users with js)
endforeach
endforeach
{{--Im trying to get an output that looks like so--}}
0: {
cost: 409
views: 0
...
user_like_id: {1,5,2,4,5}
}
EDIT: This is what I would like to attach the array to...
$likes = Category::orderByRaw("RAND()")
->leftJoin('likes', 'likes.item_id', '=', 'categories.id')
->take(9)
->get();
I'm relatively new to programming so please dont downnrate this question
Laravel provides in ways to make json from objects. The first and simplest is the eloquent toJson() method:
$likes = Category::orderByRaw("RAND()")
->leftJoin('likes', 'likes.item_id', '=', 'categories.id')
->take(9)
->get()
->toJson();
$likes is now json.
Second method:
If you return a model from a controller or a routecallback function, laravel returns it as json. So:
class MyController extends Controller{
public function getIndex(){
return Category::orderByRaw("RAND()")
->leftJoin('likes', 'likes.item_id', '=', 'categories.id')
->take(9)
->get();
}
}
This example returns a json response which is quite usefull!
I actually got exactly what I wanted with this:
$likes = Category::orderByRaw("RAND()")
->take(9)
->with(array('getLikes' => function($query)
{
$query->with('getUser');
}))
->get();
Works exactly how I want ito :) thanks for the suggestion tho

Laravel Relationships Conditions - 3 tables

I've got a situation where I've got Posts, Users and Comments.
Each comment stores a post_id and a user_id. What I want to do is get all of a user's comments on a particular post, so that I can do a call like this:
$comments = Auth::User()->comments(post_id=x)->text
(where I know what x is)
I have:
User->HasMany(comments)
Comments->HasOne(User)
Comments->HasOne(Project)
Project->HasMany(comments)
I feel like there needs to be a where or a has or a wherehas or something thrown in.. the best I can manage is that I pull Auth::User()->comments into an array and then search through the array until I find the matching post ID.. that seems wasteful.
with doesn't apply any join, so you can't reference other table.
You can use this:
// User model
public function comments()
{
return $this->hasMany('Comment');
}
// Comment model
public function scopeForPost($query, $postId)
{
$query->where('post_id', $postId);
}
// then you can do this:
Auth::user()->comments()->forPost($postId)->get();
Alternatively you can eager load comments with constraint:
User::with(['comments' => function ($q) use ($postId) {
$q->where('post_id', $postId);
}])->find($someUserId);
// or exactly the same as above, but for already fetched user:
// $user .. or
Auth::user()->load(['comments' => function ($q) use ($postId) {
$q->where('post_id', $postId);
}]);
// then you can access comments for $postId just like this:
Auth::user()->comments; // collection
When you need to filter your relations, you just have to do it in your Eloquent query:
$data = User::with('posts', 'comments')
->where('users.id', Auth::User()->id)
->where('posts.id', $postID)
->get();
Then you can
foreach($data->comments as $comment)
{
echo $comment->text;
}
Your Comments table would have foreign keys Post_Id and User_ID
To Access all the comments of a particular post from a particular user , can you try this way?
Comment::select('comments.*')
->where('comments.user_id', Auth::user()->id)
->leftJoin('posts','posts.id','=','comments.post_id')
->leftJoin('users','users.id','=','comments.user_id')
->get();
Am sure there is better way to achieve it, but this should give you desired results.
Note use aliases if you have conflicting column names
Let me know if this worked.

Resources