orderBy attributes of another table in Laravel - php

On my website I allow users to upload images/like images/favorite images/etc.
Therefore, I've got a table for images, likes, favorites, etc.
I can get and display these images just fine, and sort them as I like
$images = Images::orderBy('id', 'desc')->Paginate(50);
I can also display how much likes/favorites an image has.
$favCount = Favorite::where('image_id', $image->id)->count();
However, what would I do to sort images by, say, how many favorites it they have? I have a favorite model and an image model but I'm not sure how I would go about it.
EDIT:
Current query for the answer given:
$images = Images::join('favorites', 'favorites.image_id', '=', 'images.id')
->select('images.link', DB::raw('count(favorites.id) as favs'))
->orderBy('favs', 'desc')
->groupBy('images.link')
->take(10)
->get();
And the error is:
SQLSTATE[42S22]: Column not found: 1054 Unknown column 'images' in 'field list' (SQL: select images, count(favorites.id) as favs from images inner join favorites on favorites.image_id = images.id group by images.link order by favs desc limit 10)

You can user query builder like this:
$images = DB::table('images')
->join('favorites', 'favorites.image_id', '=', 'images.id')
->select('images.link', DB::raw('count(favourites.id) as favs'))
->orderBy('favs', 'desc') //order in descending order
->groupBy('images.link')
->take(10) //limit the images to Top 10 favorite images.
->get();
The same could be accomplished with Eloquent.

Your query should be.
$images = Images::join('favorites', 'favorites.image_id', '=', 'images.id')
->orderBy('favs', 'desc')
->groupBy('images.link')
->take(10)
->get(['images.link', DB::raw('count(favorites.id) as favs')]);

Related

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 .

Laravel join and select from multiple tables

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

Ordering a pagination by a relationship (Laravel 5.3)

In my app I have posts. I wish to show all the posts on the homepage, but order them higher if the post's user has uploaded an image. I can determine if the post's user has uploaded an image by checking if the user has a relationship with a row in the images table, like this:
$post->user->image
My code currently looks like this:
$posts = Post::where('subject_id', '=', $subject->id)
->approved()
->orderBy('created_at', 'desc')
->paginate(18);
Currently, I am simply ordering it by created at, but ideally all posts whose related user has an image will come first, then the rest. I've been looking for a way to do this efficiently and in a way that doesn't just work on the first page.
How should I go about this?
Try this:
$posts = Post::where('subject_id', '=', $subject->id)
->approved()
->select(['*', DB::raw('(SELECT count(images.id) FROM images INNER JOIN users ON users.image_id = images.id WHERE posts.user_id = users.id) as count_images'])
->orderBy('count_images', 'desc')
->orderBy('created_at', 'desc')
->paginate(18);
Try this:
$posts = Post::where('subject_id', '=', $subject->id)
->approved()
->with('user')
->join('users', 'users.id', '=', 'post.user_id')
->select(['post.*', 'users.avatar'])
->orderBy('image', 'desc')
->orderBy('created_at', 'desc') // ->latest() can be used for readability
->paginate(18);
This code eager loads users to reduce number of DB queries and doesn't use raw queries which should be avoided, because mistake can make your application vulnerable to SQL injection.
Explanation:
We eager load relationship using ->with('user') method, then join users table, select all fields from posts table and only image field from users table, then order results by image and paginate results.
Result should look like this:
App\Post:
id
title
...
image(from users table)
App\User (eager loaded relationship)
id
name
email
...
image
created_at

Laravel 5.1: handle joins with same column names

I'm trying to fetch following things from the database:
user name
user avatar_name
user avatar_filetype
complete conversation_messages
with the following query:
static public function getConversation($id)
{
$conversation = DB::table('conversation_messages')
->where('belongsTo', $id)
->join('users', 'conversation_messages.sender', '=', 'users.id')
->join('user_avatars', 'conversation_messages.sender', '=', 'user_avatars.id')
->select('users.name', 'conversation_messages.*', 'user_avatars.name', 'user_avatars.filetype')
->get();
return $conversation;
}
It works fine so far, but the avatar's column name is 'name' like the column name from the 'users' table.
So if I'm using this query the to get the output via $conversation->name, the avatar.name overwrites the users.name
Is there a way to rename the query output like the mysql "as" feature at laravel 5.1?
For example:
$conversation->avatarName
$conversation->userName
Meh okay.. i've found a simple solution here
->select('users.name as userName', 'conversation_messages.*', 'user_avatars.name as avatarName', 'user_avatars.filetype')
As you can mention I've added the requested "as-Feature" next to the table.columnName
Take a look at this example of trying to join three tables staffs, customers and bookings(pivot table).
$bookings = \DB::table('bookings')
->join('staffs', 'staffs.id' , '=', 'bookings.staff_id')
->join('customers', 'customers.id' , '=', 'bookings.customer_id')
->select('bookings.id', 'bookings.start_time', 'bookings.end_time', 'bookings.service', 'staffs.name as Staff-Name', 'customers.name as Customer-Name')
->orderBy('customers.name', 'desc')
->get();
return view('booking.index')
->with('bookings', $bookings);
I had the following problem, simplified example:
$result = Donation::join('user', 'user.id', '=', 'donation.user_id')->where('user.email', 'hello#papabello.com')->first();
$result is a collection of Donation models. BUT CAREFUL:
both tables, have a 'created_at' column. Now which created_at is displayed when doing $result->created_at ? i don't know. It seems that eloquent is doing an implicit select * when doing a join, returning models Donation but with additional attributes. created_at seems random. So what I really wanted, is a return of all Donation models of the user with email hello#papabello.com
solution is this:
$result = Donation::select('donation.*')->join('user', 'user.id', '=', 'donation.user_id')->where('user.email', 'hello#papabello.com')->first();
Yeah, simply rename the column on either table and it should work.
Also what you can do is, rename the user.name column to anything, also rename sender column of conversation_messages to id and perform a natural join.

Join Eloquent Model with condition

I have the three Models Post, Page and Category.
Each Page is assigned to one Category and each Post is assigned to one Page.
I have already defined the relationships in the models. What I want to do now is to use Eloquent ORM to get all Post-objects that are from a Page with a certain Category. So basically, in SQL I would need to do it like this
select p.* from posts p INNER JOIN pages pa ON pa.id = p.page_id where p.created_time > '2015-08-18 00:00:00' and pa.categories_id = 1 and p.isVisible = 1 order by p.total_count desc limit 100
I'm now somehow trying to do the same with Eloquent, but I'm stuck. My current code looks like this
// Getting all the top posts from facebook for today.
/** #var Builder $topPosts */
$topPosts = Post::where('created_time', '>', Carbon::today()->toDateTimeString());
if ($type !== null) {
$topPosts = $topPosts->where('type', $type);
}
return $topPosts->orderBy('total_count', 'desc')
->visible()
->take($limit)
->get();
Now, I wanted to add the category, but I don't know how to do it. I tried these steps here:
$topPosts = $topPosts->with(['page' => function($query) use($categoryId){
$query->where('page_categories_id', $categoryId);
}]);
and this one
$topPosts = $topPosts->with('page')->where('page_categories_id', $categoryId);
but none of them worked. How would I achieve that? I always get the error message
SQLSTATE[42S22]: Column not found: 1054 Unknown column 'categories_id' in 'where clause' (SQL: select * from posts where created_time > 2015-08-18 00:00:00 and categories_id = 1 and isVisible = 1 order by total_count desc limit 100)
It looks like you need to use whereHas() in place your with() statement there (http://laravel.com/docs/4.2/eloquent#querying-relations)
This should work, and is basically just querying Posts with associated Pages with the particular category_id. I haven't included your ordering and things..
$posts = Post::where('created_time', '>', Carbon::today()) // Eloquent shouldn't actually need the toDateTimeString()
->with('page') // leave this in to eager load the page
->whereHas('page', function($query) use ($categoryId) {
$query->where('page_categories_id', $categoryId);
})
->get();

Categories