Laravel join and select from multiple tables - php

So I have Laravel 5.2 and in my SQL database I have several tables:
books with fields title, author and year,
journals with fields title, year and price
newspapers with fields title, town, year
I need to get a list of all titles from all three tables, where the year is 1994. I've tried to do the following
$titles = DB::table('books')->where('books.year', 1994)->leftjoin('journals as journals', 'books.year', '=', 'journals.year')->leftjoin('newspapers as newspapers', 'books.year', '=', 'newspapers.year')->select('books.title', 'journals.title', 'newspapers.title')->get();
But with this query I get entries full of nulls, and only newspapers are filled in. What am I doing wrong?

In this case (if the tables are not related) you should use union, no join.
$books = DB::table('books')->select('title')->where('year', 1994);
$journals = DB::table('journals')->select('title')->where('year', 1994);
$titles = DB::table('newspapers')->select('title')->where('year', 1994)->union($books)->union($journals)->get();

You need to specify year column :
$titles = DB::table('books')
->where('books.year', 1994)
->leftjoin('journals as journals', 'books.year', '=', 'journals.year')
->leftjoin('newspapers as newspapers', 'books.year', '=', 'newspapers.year')
->select('books.title', 'journals.title', 'newspapers.title')
->get();

Related

Laravel OrderBy by related table column

I have a table (A) that has a One to Many relation with another table (B).
I want to query Table A and eager load Table B with the Table A results - but I also want to sort Table A by a value in Table B.
I have tried using OrderBy in the query and also trying SortBy on the resultant collection but cannot get the Table A data to be sorted by the value found in Table B.
Example of what I have tried:
$query = ModelA::with("ModelB"])->get()->sortByDesc('ModelB.sortValue');
Keep in mind, I am only interested in the LATEST record from Table B. So I need to query Table A and sort by a value in the LATEST records of Table B.
How can I achieve this?
EDIT:
The below (as suggested by #ljubadr) works pretty close, but the issue is that there are many record in Table B which means that it doesn't reliably sort as it doesn't seem to sortby the latest records in Table B. Can I have the join return ONLY the latest record for each ID?
$query = ModelA::select('TableA.*')
->join('TableB', 'TableA.id', '=', 'TableB.col_id')
->groupBy('TableA.id')->orderBy('TableB.sortCol', 'desc')
->with(['x'])
->get();
EDIT 2:
#Neku80 answer has gotten me closest but it seems to not sort the column with the greatest accuracy.. I'm sorting a Decimal column and for the most part it is in order but in some places the items are out of order..
$latestTableB = ModelB::select(['TableA_id', 'sortByColumnName'], DB::raw('MAX(created_at) as created_at'))
->groupBy('TableA_id');
$query = ModelA::select('TableA.*')
->joinSub($latestTableB, 'latest_TableB', function ($join) {
$join->on('TableA.id', '=', 'latest_TableB.TableA_id');
})
->orderBy('latest_TableB.sortByColumnName')
->get();
For example, the ordering is like:
0.0437
0.0389
0.0247 <-- -1
0.025 <-- +1
0.0127
When I delete all rows except for the 'latest' rows, then it orders correctly, so it still must be ordering with old data...
I have found a solution:
ModelA::select('TableA.*', 'TableB.sortByCol as sortByCol')
->leftJoin('TableB', function ($query) {
$query->on('TableB.TableA_id', '=', 'TableA.id')
->whereRaw('TableB.id IN (select MAX(a2.id) from TableB as a2 join TableA as u2 on u2.id = a2.TableA_id group by u2.id)');
})
->orderBy('TableB.sortByCol')
->get();
Another alternative to order is like this:
$users = User::orderBy(
Company::select('name')
->whereColumn('companies.user_id', 'users.id'),
'asc'
)->get();
Here we are ordering in asc order by company name field.
In this article it is explained in detail.
You can simply execute a left join query:
ModelA::query()->leftJoin('model_b_table', 'model_a_table.primary_key', '=', 'model_b_table.foreign_key')->orderBy('model_a_table.target_column')->get();
This should work if you only need TableB's ID and created_at columns:
$latestTableB = ModelB::select('TableA_id', DB::raw('MAX(created_at) as created_at'))
->groupBy('TableA_id');
$query = ModelA::select('TableA.*')
->joinSub($latestTableB, 'latest_TableB', function ($join) {
$join->on('TableA.id', '=', 'latest_TableB.TableA_id');
})
->orderBy('latest_TableB.created_at')
->get();

Using Laravel's WhereIn to search multiple tables

I have 3 SQL tables.
clients
events
client_events
Because a client can have multiple events, I made the third table to show those relationships. I am using the following code to retrieve all of the clients that have the have a record matching this event, but it is only returning 1 record when there are multiple.
$eventHosts = DB::table('clients')->whereIn('id', function($query) {
$query->select('client_id')->from('client_events')->where('event_id', '=', explode('/', $_SERVER['REQUEST_URI'])[2]);
})->get();
What am I overlooking?
You can fetch the ids first, then pass to the whereIn query.
$clientIds = DB::table('client_events')
->where('event_id', explode('/', $_SERVER['REQUEST_URI'])[2])
->pluck('client_id')
->toArray();
$eventHosts = DB::table('clients')->whereIn('id', $clientIds)->get();
To get the results in a single query and more efficeintly, try join
$eventHosts = DB::table('clients') //select your main table
->join('client_events','client_events.client_id','=','clients.id') //join it on related columns
->where('client_events.client_id',explode('/', $_SERVER['REQUEST_URI'])[2])) //apply your condition on client_events
->get();

Laravel return latest histories from all users

I have login history table where i save each user login/logout, therefore each user can have many rows.
I am trying to make filter to get last row of each user at once (getting latest row of all users) but not sure how to.
Code
controller
$histories = LoginHistory::with(['user','user.roles' => function($q) {
return $q->latest()->first();
}])->get();
this return all rows from all users instead of only latest rows.
screenshot
table
In this case my function should return 3 results as my user with id 1 has 2 rows (i only need to get latest one row 2 ) but yet i get all 4 rows with my function.
Any idea?
The raw MySQL query you might use here would look something like this:
SELECT lh1.*
FROM login_history lh1
INNER JOIN
(
SELECT user_id, MAX(login) AS max_login
FROM login_history
GROUP BY user_id
) lh2
ON lh1.user_id = lh2.user_id AND lh1.login = lh2.max_login
Your updated Laravel/Eloquent code:
$subquery = DB::table('login_history')
->select(DB::raw("user_id, MAX(login) AS max_login"))
->groupBy('user_id');
$rs = LoginHistory::joinSub($subquery, 'lh2', function($join) {
$join->on('login_history.user_id', '=', 'lh2.user_id');
$join->on('login_history.login', '=', 'lh2.max_login');
})
->select(['login_history.*'])
->get();

I want to find top 10 favorite trails in laravel queries

I have two tables named 'favorites' and 'trails'. I want to show top 10 favorites trails for users. I made 'many to many' relationship between them. But, I am not sure how to make the query.What should be the right query.Would someone help me for the right one. Without relationship, I tried something like this-
$favorites = DB::table('favorites')
->join('trails', 'trails.id', '=', 'favorites.trail_id')
->select('favorites.trail_id', 'trails.name')
->get();
First one is 'trails' table and another one is 'favorites' bellow -
To get the top 10 trails you need to use aggregate function to count no of users for each trail and order your results based on the result of count and then select only 10
$favorites = DB::table('trails as t')
->select('t.id', 't.name')
->join('favorites as f', 't.id', '=', 'f.trail_id')
->groupBy('t.id')
->groupBy('t.name')
->orderByRaw('COUNT(DISTINCT f.user_id) DESC')
->limit(10)
->get();

Laravel mysql Inner join and also select a specific field from another table

I have the following query:
Ratings::join('users', 'movieratings.rated_by', '=', 'users.usr_id')
->where('rated_on', $movieId)
->orderBy('rated_at', 'desc')
->select('comment', 'rating', 'rated_as', 'rated_at', 'username')
->paginate(20);
This will get all the feedback ratings for a specific movie.
But I have another table which contains the total good and bad ratings for a specific movie movie, the only problem is that I cant get it to work to query that table as well at the same time.
If I do another query I would simply write: Movie::where('movie_id', $movieId)->select('total_good_ratings', 'total_bad_ratings')->get(); this would output eg "22, 15" but is it possible to only fetch two columns from a specific row then do a inner join between two tables and paginate the result?
thanks
You can do a leftJoin with the table that contains the good and bad ratings, where the join condition will be the id of the movie.
Ratings::join('users', 'movieratings.rated_by', '=', 'users.usr_id')
->leftJoin('movie', 'movie.id', '=', 'movieratings.rated_on')
->where('rated_on', $movieId)
->orderBy('rated_at', 'desc')
->select('comment', 'rating', 'rated_as', 'rated_at', 'username', 'total_good_ratings', 'total_bad_ratings')
->paginate(20);
I think you can try this:
Ratings::leftJoin('users', 'users.usr_id', '=', 'movieratings.rated_by')
->leftJoin('movie', 'movie.id', '=', 'movieratings.rated_on')
->where('movieratings.rated_on', $movieId)
->orderBy('movie.rated_at', 'desc')
->select('movieratings.comment', 'movieratings.rating', 'movieratings.rated_as', 'movie.rated_at', 'users.username', 'movieratings.total_good_ratings', 'movieratings.total_bad_ratings')
->paginate(20);
Hope this help for you !!!
In case this may be of help:
Assuming:
class Rating extends Model {
public users() {
$this->belongsTo(User::class, 'usr_id');
}
public movie() {
$this->belongsTo(Movie::class, 'rated_on'); //Name looks odd, it should be movie_id if you are following standard conventions
}
}
Then you can lazy/eaher load them:
$ratings = Ratings::with([ "movie" => function ($query) {
$q->select('total_good_ratings', 'total_bad_ratings');
}])->where('rated_on', $movieId)
->orderBy('rated_at', 'desc')
->select('comment', 'rating', 'rated_as', 'rated_at', 'username',"rated_on")
->paginate(20);
You can get the movie info via $ratings[X]->movie->total_good_ratings (in a loop that would be $rating->movie->total_good_ratings
A bit of critique though:
total_good_ratings looks like it's a derived attribute so it should not have been stored in the first place. It's appears to be a count of the good ratings.
You should use the standard conventions when naming columns and tables e.g. a foreign key is usually called <foreign table name in singular>_<foreign field name> example user_id or movie_id .

Categories