How to join three tables in laravel eloquent - php

I want to join three tables:
sections table:
sectionid
text
formid
questions table:
questionid
question
formid
sectionid
options table:
optionid
option
questionid
I want to fetch all sections where formid=x (for example) and questions related to sectionid and options related to questionid.
$data=Section::with('questions','options')
->where('formId',$formId)
->orderBy('sectionid')
->get()
->toJson();

I've created two relationships in my Section model. One to joining Section model to Question model and other one to join Section Model to Option model through Question model.
class Section extends Model
{
use HasFactory;
protected $primaryKey='sectionid';
public function options(){
return $this->hasManyThrough(Option::class,Question::class,'sectionid','questionid');
}
public function questions(){
return $this->hasMany(Question::class,'sectionid');
}
}
My Controller:
$data=Section::with('questions','options')
->where('formId',$formId)
->orderBy('sectionid')
->get()
->toJson();
It works properly :)

Laravel docs: https://laravel.com/docs/8.x/queries#joins
According to what you need:
$data = Section::where('formid', x)
->join('questions', 'sections.sectionid', '=', 'questions.sectionid')
->join('options', 'options.optionid', '=', 'questions.optionid')
->select('sections.*', 'questions.question as question', 'options.option as option')
->get();
Notes:
Do not fetch * from all tables, for most cases you may get ambiguous column error
Your coding style does not follow convention which is really bad practice. Following convention helps in many ways.

Related

Order parent model based on child model creation date in eager load laravel eloquent

I have a Conversation model like this :
class Conversation extends Model
{
protected $primaryKey = 'conversation_id';
public function questions (){
return $this->hasMany('App\Question','conversation_id','conversation_id');
}
public function latestQuestion ()
{
return $this->hasOne('App\Question','conversation_id','conversation_id')
->orderBy('created_at', 'desc');
}
}
And a Question model like this :
class Question extends Model
{
protected $primaryKey = 'question_id';
public function conversation ()
{
return $this->belongsTo('App\Conversation', 'conversation_id', 'conversation_id');
}
}
As you can see each Conversation can have some Question.
Furthermore, Conversation model has a latestQuestion relationship that fetches latest Question for that Conversation.
Now I want to list all Conversations with their latest question.
For that I wrote :
$conversations =
Conversation::
with('latestQuestion')
->orderBy('created_at','desc')
->get();
above code, orders conversations by their created_at field.
But I want to make a list of Conversations that conversations with new question be on top of list. in fact I want to order Conversations based On latest Question created_at field But I do not know How can I do that?
You need to use Join Query. Because Eager Loading will just bring the base table and fetch the referenced table records later
Conversation::join('questions', 'conversations.conversation_id', '=', 'questions.conversation_id')
->orderBy('questions.created_at','Desc')
->groupBy('conversations.conversation_id')
->get();

How do you order a Laravel Builder by its one-to-one relationship?

I have two models, Book and Author. Book belongsTo Author (I've trimmed some of the crud from other parts):
class Book extends Model {
protected $table = 'books';
protected $fillable = ['title'];
public function author(){
return $this->belongsTo('Author', 'author');
}
}
class Author extends Model {
protected $table = 'authors';
protected $fillable = ['name'];
}
If I want to get all books ordered by their title, I would use this:
Books::with(['author'])->orderBy('title', 'asc')->get();
Is it possible to order those books by the author's name? I've tried many combinations:
Books::with(['author' => function($query){
$query->orderBy('name', 'asc');
}])->get();
Books::with(['author'])->orderBy('name')->get();
Books::with(['author'])->orderBy('author.name')->get();
Books::with(['author'])->orderBy('authors.name')->get();
But none worked. The first, using the with() query, orders all authors before joining them into the books collection, I think. The others threw 500 errors.
If this were plain MySQL, I'd write something like this:
select * from books join authors on books.author_id = authors.id order by authors.name asc;
Is this possible using Builder/Eloquent in laravel 5.1? Do I need to use a DB query?
Enabling eager load by calling with('author') will load authors in a separate query, that's why author's columns are not available and sorting by them does not work. Explicit join is needed.
In order to sort by a relation you need to join your books with their authors:
Book::select('books.*')
->join('authors', 'authors.id', '=', 'books.author_id')
->orderBy('authors.name')
->get();
eloquent return a collection its very powerful see the docs for things you can do with that
so to order the books by author name you can do:
function orderBooksByAutherName($books){
return $books->sortBy(function ($book, $key) {
return $book->author->name;
});
}
if you really want to use join the only option is to use the query builder

Laravel Eloquent retrieve data from multiple tables

I have four tables
**Articles table**
id
title
body
owner_id
category_id
**Favorite articles table**
id
user_id
article_id
**User table**
id
user_name
user_type
**Category table**
id
category_name
How to get list of favorite articles (article_name,owner_name,category_name) which related to currently logged user from db using laravel eloquent?
Is it possible to do it in single line request? e.g.:
$articles_data=Auth::user()->favorite_articles->article...
EDIT
For the moment i have to use statement below:
$articles_data = FavoriteArticle::where('user_id', Auth::id())->join('articles', 'articles.id', '=', 'favorite_articles.article.id')
->join('users', 'users.id', '=', 'favorite_articles.user_id')
->join('categories', 'categories.id', '=', 'articles.id')
->get()
Which looks a bit complicated and doesn't use eloquent relations.
Completing #zippo_ answer, in the Controller you must reference what tables you want, e.g.
User.php
use Article;
public function article()
{
return $this->hasOne('App\Article');
}
and in the e.g. UserController.php
$user = User::with('article')->get();
EDIT:
if you want to relate User to Article.Category, after create a relation with user and article
Article.php
use Category;
public function category()
{
return $this->hasOne('App\Category');
}
e.g. UserController.php
$user_articles_categories = User::with('article.category')->get();
You can take advantage of laravel eager loading, which are also called as Eloquent relationships.
Eloquent relationships are defined as functions on your Eloquent model classes.
Eg. In Article Model
public function article()
{
return $this->hasOne('App\Model\Category');
}
In this way, you need to define all the relationships in the respective Model classes.
for more info: http://laravel.com/docs/5.1/eloquent-relationships

Laravel Eager Load and Group Multiple Joins on Pivot

I have a Pivot table thats used to join two other tables that have many relations per hotel_id. Is there a way I can eagerload the relationship that pulls the results for both tables in one relationship? The raw SQL query, works correctly but when using belongsToMany the order is off.
Amenities Pivot Table
id
hotel_id
distance_id
type_id
Distance Table
id
name
Type Table
id
name
RAW Query (This works fine)
SELECT * FROM amenities a
LEFT JOIN distance d ON a.distance_id = d.id
LEFT JOIN type t ON a.type_id = t.id WHERE a.hotel_id = ?
My "Hotels" Model is using belongsToMany like so
public function distance() {
return $this->belongsToMany('Distance', 'amenities', 'hotel_id', 'distance_id');
}
public function type() {
return $this->belongsToMany('Type', 'amenities', 'hotel_id', 'type_id');
}
This outputs the collection, but they are not grouped correctly. I need to loop these into select fields side by side as entered in the pivot table, so a user can select a "type" and the "distance", but the order is off when using the collection. The raw query above outputs correctly.
Hotels::where('id','=','200')->with('distance', 'type')->take(5)->get();
Ok Solved it. So apparently you can use orderBy on your pivot table. Incase anyone else has this issue this is what I did on both relationships.
public function distance() {
return $this->belongsToMany('Distance', 'amenities', 'hotel_id', 'distance_id')->withPivot('id')->orderBy('pivot_id','desc');
}
public function type() {
return $this->belongsToMany('Type', 'amenities', 'hotel_id', 'type_id')->withPivot('id')->orderBy('pivot_id','desc');
}
It's not really a great practice to include other query building steps in the relationship methods on your models. The relationship method should just define the relationship, nothing else. A cleaner method is to apply eager load constraints. (scroll down a bit) Consider the following.
Hotels::where('id', 200)->with(array(
'distance' => function ($query)
{
$query->withPivot('id')->orderBy('pivot_id','desc');
},
'type' => function ($query)
{
$query->withPivot('id')->orderBy('pivot_id','desc');
},
))->take(5)->get();
If you find that you are eagerly loading this relationship in this way often, consider using scopes to keep things DRY. The end result will allow you to do something like this.
Hotels::where('id', 200)->withOrderedDistance()->withOrderedType()->take(5)->get();
P.S. Your models should be singular. Hotel, not Hotels. The model represents a single record.
Solved by using ->withPivot('id')->orderBy('pivot_id','desc');
Posted answer in the question.

Laravel Eloquent Join vs Inner Join?

So I am having some trouble figuring out how to do a feed style mysql call, and I don't know if its an eloquent issue or a mysql issue. I am sure it is possible in both and I am just in need of some help.
So I have a user and they go to their feed page, on this page it shows stuff from their friends (friends votes, friends comments, friends status updates). So say I have tom, tim and taylor as my friends and I need to get all of their votes, comments, status updates. How do I go about this? I have a list of all the friends by Id number, and I have tables for each of the events (votes, comments, status updates) that have the Id stored in it to link back to the user. So how can I get all of that information at once so that I can display it in a feed in the form of.
Tim commented "Cool"
Taylor Said "Woot first status update~!"
Taylor Voted on "Best competition ever"
Edit #damiani
So after doing the model changes I have code like this, and it does return the correct rows
$friends_votes = $user->friends()->join('votes', 'votes.userId', '=', 'friend.friendId')->orderBy('created_at', 'DESC')->get(['votes.*']);
$friends_comments = $user->friends()->join('comments', 'comments.userId', '=', 'friend.friendId')->orderBy('created_at', 'DESC')->get(['comments.*']);
$friends_status = $user->friends()->join('status', 'status.userId', '=', 'friend.friendId')->orderBy('created_at', 'DESC')->get(['status.*']);
But I would like them all to happen at once, this is because mysql sorting thousands of records in order is 100x faster then php taking 3 lists, merging them and then doing it. Any ideas?
I'm sure there are other ways to accomplish this, but one solution would be to use join through the Query Builder.
If you have tables set up something like this:
users
id
...
friends
id
user_id
friend_id
...
votes, comments and status_updates (3 tables)
id
user_id
....
In your User model:
class User extends Eloquent {
public function friends()
{
return $this->hasMany('Friend');
}
}
In your Friend model:
class Friend extends Eloquent {
public function user()
{
return $this->belongsTo('User');
}
}
Then, to gather all the votes for the friends of the user with the id of 1, you could run this query:
$user = User::find(1);
$friends_votes = $user->friends()
->with('user') // bring along details of the friend
->join('votes', 'votes.user_id', '=', 'friends.friend_id')
->get(['votes.*']); // exclude extra details from friends table
Run the same join for the comments and status_updates tables. If you would like votes, comments, and status_updates to be in one chronological list, you can merge the resulting three collections into one and then sort the merged collection.
Edit
To get votes, comments, and status updates in one query, you could build up each query and then union the results. Unfortunately, this doesn't seem to work if we use the Eloquent hasMany relationship (see comments for this question for a discussion of that problem) so we have to modify to queries to use where instead:
$friends_votes =
DB::table('friends')->where('friends.user_id','1')
->join('votes', 'votes.user_id', '=', 'friends.friend_id');
$friends_comments =
DB::table('friends')->where('friends.user_id','1')
->join('comments', 'comments.user_id', '=', 'friends.friend_id');
$friends_status_updates =
DB::table('status_updates')->where('status_updates.user_id','1')
->join('friends', 'status_updates.user_id', '=', 'friends.friend_id');
$friends_events =
$friends_votes
->union($friends_comments)
->union($friends_status_updates)
->get();
At this point, though, our query is getting a bit hairy, so a polymorphic relationship with and an extra table (like DefiniteIntegral suggests below) might be a better idea.
Probably not what you want to hear, but a "feeds" table would be a great middleman for this sort of transaction, giving you a denormalized way of pivoting to all these data with a polymorphic relationship.
You could build it like this:
<?php
Schema::create('feeds', function($table) {
$table->increments('id');
$table->timestamps();
$table->unsignedInteger('user_id');
$table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
$table->morphs('target');
});
Build the feed model like so:
<?php
class Feed extends Eloquent
{
protected $fillable = ['user_id', 'target_type', 'target_id'];
public function user()
{
return $this->belongsTo('User');
}
public function target()
{
return $this->morphTo();
}
}
Then keep it up to date with something like:
<?php
Vote::created(function(Vote $vote) {
$target_type = 'Vote';
$target_id = $vote->id;
$user_id = $vote->user_id;
Feed::create(compact('target_type', 'target_id', 'user_id'));
});
You could make the above much more generic/robust—this is just for demonstration purposes.
At this point, your feed items are really easy to retrieve all at once:
<?php
Feed::whereIn('user_id', $my_friend_ids)
->with('user', 'target')
->orderBy('created_at', 'desc')
->get();

Categories