Data from Two tables duplicating on join - php

I have got two tables , I would use facebook POST as an example.
Post Table
Comment Table
My query
$result = DB::table('posts')
->join('comments', 'posts.post_ID', '=', 'comments.post_ID')
->get();
I will receive an array of posts and comments merge. For each comments that exist , they will have the posts data.
What i want is to be able to do something like
foreach($posts as $post){
foreach($post['comment']{
}
}
Any idea how i can do that?

Something like this should work:
$result = DB::table('posts')
->join('comments', 'posts.id', '=', 'comments.post_id')
->get();
In the view:
#foreach($posts as $post)
{{ $post->post_title }}
{{ $post->message }}
#endforeach
Make sure that, field names in ->join('comments', 'posts.id', '=', 'comments.post_id') are right or change accordingly. post_title/comment_text is used to demonstrate the example, change to it's original table's field name and {{ }} is used in the example to echo the data, if you are not using Blade then use echo $post->post_title instead.
Update::
If you use Eloquent then use:
// Assumed you have defined comments as relationship method
$posts = Post::with('comments')->get(); // it's faster and called eager loading
Then in the view:
#foreach($posts as $post)
{{ $post->post_title }}
#foreach($post->comments as $comment)
{{ $comment->message }}
#endforeach
#endforeach

I will receive an array of posts and comments merge. For each comments that exist , they will have the posts data.
This is correct SQL behavoir when using a join. You will get the contents of both the rows inside the posts and comments rows on your JOIN.
According to Laravel 4.2's documentation on the join method, the parameters of the join function are:
join($table, $one, $operator = null, $two = null, $type = 'inner', $where = false)
Using an INNER JOIN, you are only going to get rows returned to you with your query (using an inner join) if you have a comment for all of the posts that you want data from. Additionally, with your INNER JOIN, if there is no comment on your post, you will not get any posts returned to you.
Also, you are not going to be able to separate all of your comments from your posts, which may mean that you are getting results returned for posts that you
The simple way to solve this for you would be to make two eloquent models:
class Post extends Eloquent {
protected $table = 'posts';
public function getComments() {
return $this->hasMany('Comments', 'post_id');
}
}
class Comments extends Eloquent {
protected $table = 'comments';
}
From that you can query for all of the posts with eager loading:
$posts = Post::with('comments')->get();
and inside your view, you go:
foreach($posts as $post) {
// echo the $post? (title, author etc)
foreach($post->comments() as $comment) {
// echo the $comment? (author, content etc)
}
}

Related

Laravel 5.3 - HasMany Relationship not working with Join statement

I am trying to retrieve symbols with their comments using hasMany in laravel 5.3
Symbol.php
public function comments() {
return $this->hasMany('App\Comment');
}
Comment.php
public function symbol() {
return $this->belongsTo('App\Symbol');
}
when I run:
$symbols = Symbol::with('comments')->paginate(100);
I get the correct output (lists all symbols with their comments)
#foreach ($symbols as $s)
{{ $s->name }}
#foreach ($s->comments as $c)
{{ $c->body }}
#endforeach
#endforeach
but when I add a join to the statement:
$symbols = Symbol::with('comments')
->join('ranks', 'symbols.id', '=', 'ranks.symbol_id')
->join('prices', 'symbols.id', '=', 'prices.symbol_id')
->paginate(100);
The foreach loop has no comments for every symbol. Any idea why the join would be causing this?
When you are doing joins like this, attributes with the same names will be overwritten if not selected. So select the attributes you need for your code, and nothing else. As shown below.
$symbols = Symbol::with('comments')
->join('ranks', 'symbols.id', '=', 'ranks.symbol_id')
->join('prices', 'symbols.id', '=', 'prices.symbol_id')
->select('symbols.*', 'ranks.importantAttribute', 'prices.importantAttribute')
->paginate(100);
Basicly i think your ids are being overwritten, by the two joins because they also have id fields, i have had a similar problem doing joins, and it breaks relations if the id is overwritten.
And you have to be carefull, all fields who shares names can be overwritten and parsed wrong into the models.

How to manipulate an object in laravel

I have a laravel query as the one below:
$campaign = Campaign::with(array('tracks.flights' => function($q) use ($dates)
{
$q->whereRaw("flights.start_date BETWEEN '". $dates['start']."' AND '".$dates['end']."'")->orderBy('start_date')
->with('asset')
->with('comments');
}
))
->with('tracks.group')
->with('tracks.media')
->orderBy('created_at', 'desc')
->find($id);
I am very new to laravel and right now the response from this query returns all the data required including the comments with the comments attributes from the DB.
What i want to achieve is manipulate the comments object so it includes the user name from the users table as the comments table has the user_id only as an attribute.
How can I achieve this? I am very new to laravel.
Your Comment model must have a relationship with the User model, such as:
public function user()
{
return $this->belongsTo('App\User');
}
Now when you are iterating comments you are able to do $comment->user->name or in your case it will be something like this:
#if(!$campaign->tracks->flights->isEmpty())
#foreach($campaign->tracks->flights as $flight)
#if(!$flight->comments->isEmpty())
#foreach($flight->comments as $comment)
{!! $comment->user->name !!}
#endforeach
#endif
#endforeach
#endif
Once you can do this, next step is to understand eager load with eloquent.

How to find category name from category value which is in different table in Laravel 4.2?

There are two table category and content.
Content table has id,category_id
Category table has category name and id
I have to fetch all the content from content table with category name which is in Category Table.
I don't want to use JOIN.
Please suggest me other query to use.
Controller
$AllContent=Content::all();
return View::make('ALL/Contents')->with('AllContent',$AllContent);
I have no idea why you don't want to use an SQL JOIN, as it is almost certainly the most efficient thing to do when you have one table relating to another by its primary key. But hey, who am I to judge?
A way you can do this is by using a subquery. In plain old SQL:
SELECT
`content`.`id`,
(
SELECT `name`
FROM `category`
WHERE `id` = `content`.`category_id`
) AS `name`
FROM `content`
So, to do this using Laravel's query builder:
DB::table('content')
->select(['id'])
->selectSub(function ($query) {
return $query->from('category')
->where('id', '=', DB::raw('content.category_id'))
->select('name')
}, 'name')
->get();
I think that'll do what you want, anyway. But really, just use a JOIN.
If you do want to use a JOIN then you're somewhat better off. Again I'll give you the raw SQL first:
SELECT
`content`.`id`,
`category`.`name`
FROM `content`
LEFT JOIN `category`
ON `content`.`category_id` = `category`.`id`
And now in Laravel's query builder:
DB::table('content')
->join('category', 'content.category_id', '=', 'category.id')
->select(['content.id', 'category.name'])
->get();
And now using Eloquent models:
// app/Models/Content.php
namespace App\Models;
class Content
{
protected $table = 'content';
public function category()
{
return $this->belongsTo('App\Models\Category', 'category_id');
}
}
// app/Models/category.php
namespace App\Models;
class Category
{
protected $table = 'category';
public function content()
{
return $this->hasMany('App\Models\Content', 'category_id');
}
}
Now you can use this model to do all sorts. For example, to do what you were originally trying:
// some controller somewhere
use App\Models\Content;
class SomeController
{
public function index()
{
$content = Content::with('category')->get();
return View::make('ALL/Contents')->with('AllContent', $content);
}
}
// views/ALL/Contents.blade.php
<ul>
#foreach ($AllContent as $content)
<li>
{{ $content->id }} - {{ $content->category->name }}
</li>
#endforeach
</ul>
But alternatively you can return the content id/category name as a key=>value array:
// in the controller
$content = Content::with('category')->lists('category.name', 'id');
// in the view
<ul>
#foreach ($AllContent as $id => $category)
<li>
{{ $id }} - {{ $category }}
</li>
#endforeach
</ul>
This is all untested, but should work. If it doesn't, it should at least give you an idea of how it all goes together.

Ordering Related Models with Laravel/Eloquent

Is it possible to use an orderBy for an object's related models? That is, let's say I have a Blog Post model with a hasMany("Comments"); I can fetch a collection with
$posts = BlogPost::all();
And then run through each post, and display the comment's last edited date for each one
foreach($posts as $post)
{
foreach($post->comments as $comment)
{
echo $comment->edited_date,"\n";
}
}
Is there a way for me to set the order the comments are returned in?
This is the correct way:
BlogPost::with(['comments' => function ($q) {
$q->orderBy('whatever');
}])->get();
The returned object from the relationship is an Eloquent instance that supports the functions of the query builder, so you can call query builder methods on it.
foreach ($posts as $post) {
foreach ($post->comments()->orderBy('edited_date')->get() as $comment) {
echo $comment->edited_date,"\n";
}
}
Also, keep in mind when you foreach() all posts like this, that Laravel has to run a query to select the comments for the posts in each iteration, so eager loading the comments like you see in Jarek Tkaczyk's answer is recommended.
You can also create an independent function for the ordered comments like you see in this question.
public function comments() {
return $this->hasMany('Comment')->orderBy('comments.edited_date');
}
And then you can loop them like you did in your original code.
Create a JOIN and select just the column you want to order on:
$post = BlogPost::join('comments', function($j) {
$j->on('posts.id', '=', 'comments.post_id');
$j->select('comment_date_or_other_col');
})
->orderBy('comment_date_or_other_col', 'DESC')->first();
Yes:
$posts = BlogPost::with('comments')
->orderBy('comments_table_name.column_name')
->get();
And you can also set that in your relation:
public comments()
{
$this->hasMany("Comments")->orderBy('comments.column_name');
}

How to list out all items in a nested table in Laravel

I am using Laravel's Eloquent ORM and I'm having trouble eager loading items for display.
Here is the scenario:
Users follow Blogs
Blogs have Posts
I have a database table named Relationships, this table is used to store the User ID and the Blog ID to show which User is following which Blog. I have a table for Blogs describing the Blog and I have a table for Posts. The Relationships table would be my pivot table to connect the Users with the Blogs tables together. Now, I need to list out all the posts from all the Blogs the User follows in a list.
Here is my User model:
public function following() {
return $this->has_many_and_belongs_to('Blog', 'relationships', 'user_id', 'blog_id');
}
Here is my Blog model:
public function followers() {
return $this->has_many_and_belongs_to('User', 'relationships', 'blog_id', 'user_id');
}
public function posts() {
return $this->has_many('Post');
}
This is how I am trying to retrieve the posts in a list:
$posts = User::with(array('following', 'following.posts'))
->find($user->id)
->following()
->take($count)
->get();
This code only lists out the actual Blogs, I need their Posts.
Thank you for your help, please let me know if you need any more details.
SOLUTION:
I slightly modified the accepted answer below, I decided to use the JOIN to reduce the amount of SQL calls to simply 1 call. Here it is:
$posts = Post::join('blogs', 'posts.blog_id', '=', 'blogs.id')
->join('relationships', 'blogs.id', '=', 'relationships.blog_id')
->select('posts.*')
->where('relationships.user_id', '=', $user->id)
->order_by('posts.id', 'desc')
->take($count)
->get();
This is not achievable by native Eloquent methods. But you can use a bit of Fluent methods to join those tables. For instance:
Edit here: I've added the eager loading to Post query.
$user = User::find(1);
$posts = Post::with('blog') // Eager loads the blog this post belongs to
->join('blogs', 'blogs.id', '=', 'posts.blog_id')
->join('relationships', 'relationships.blog_id', '=', 'blogs.id')
->where('relationships.user_id', '=', $user->id)
->order_by('posts.id', 'desc') // Latest post first.
->limit(10) // Gets last 10 posts
->get('posts.*');
foreach ($posts as $post) {
print($post->title);
}
If you also need a list of all blogs that such user is following to show on a sidebar, for instance. You can DYI instead of relying on Eloquent, which should be faster and more customizable. For instance:
$user = User::with('following')->find(1);
// This creates a dictionary for faster performance further ahead
$dictionary = array();
foreach ($user->following as $blog) {
$dictionary[$blog->id] = $blog;
}
// Retrieves latest 10 posts from these blogs that he follows
// Obs: Notice the array_keys here
$posts = Post::where_in('blog_id', array_keys($blog_ids))
->order_by('posts.id', 'desc')
->limit(10)
->get();
// Hydrates all posts with their owning blogs.
// This avoids loading the blogs twice and has no effect
// on database records. It's just a helper for views.
foreach ($posts as $post) {
$post->relationships['blog'] = $dictionary[$post->blog_id];
}
On view:
foreach ($user->following as $blog) {
print($blog->title);
}
foreach ($posts as $post) {
print($post->title . ' #'. $post->blog->title);
}

Categories