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
Related
On my website, users can post images.
Images can have tags.
There's 4 tables for this, the images table, the images_tag pivot table, the tag table, and of course the users table.
A user can have multiple images with the same tag(s).
I can pull up the tags a user has used across all his images with this query:
$userTags = Tag::whereHas('images', function($q) use($user) {
$q->where('created_by', $user->id);
})->get();
However, I want to make it so that I can order these tags based on how frequently a user uses them. In other words, I want to order by duplicates. Is this possible?
To achieve this, you're going to need to join the images_tags and images tables, count the number of tags, and order by those tags.
$tags = Tag::selectRaw('tags.*, COUNT(images.id) AS total')
->join('images_tags', 'tags.id', '=', 'images_tags.tag_id')
->join('images', 'images.id', '=', 'images_tags.image_id')
->where('images.created_by', $user->id)
->groupBy('tags.id')
->orderBy('total', 'desc')
->get();
The above query will only work in MySQL if the only_full_group_by option is disabled. Otherwise, you're going to need to either rewrite this to use a sub query, or do the ordering in the returned Laravel Collection. For example:
$tags = Tag::selectRaw('tags.*, COUNT(images.id) AS total')
->join('images_tags', 'tags.id', '=', 'images_tags.tag_id')
->join('images', 'images.id', '=', 'images_tags.image_id')
->where('images.created_by', $user->id)
->groupBy('tags.id')
->get();
$tags = $tags->sortByDesc(function ($tag) {
return $tag->total;
});
If you want to add this to your user model, per your comment, create a function similar to the following:
public function getMostUsedTags($limit = 3)
{
return Tag::selectRaw('tags.*, COUNT(images.id) AS total')
->join('images_tags', 'tags.id', '=', 'images_tags.tag_id')
->join('images', 'images.id', '=', 'images_tags.image_id')
->where('images.created_by', $this->id)
->groupBy('tags.id')
->orderBy('total', 'desc')
->limit($limit)
->get();
}
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 .
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')]);
I am new to Laravel and building a small Laravel 5.3 app offering free content files as well as files for purchase.
I want users to automatically have access to the free files (content which may be added periodically).
I have a products, purchases (pivot) and users table.
When a user is logged in, how can I query the products table like the following: select all free products (price=0) or join on purchases where users.user_id = purchases.user_id and products.id = purchases.product_id?
Any ideas, or is there a better way to accomplish the same thing?
Thanks
How about if you use following query:
$purchasedProducts = DB::table('purchases')
->join('products', 'products.id', '=', 'purchases.product_id')
->select('purchases.*', 'products.*')
->where([
['purchases.user_id', '=', $loggedinUserId],
['products.price', '=', 0],
])
->get();
If you have defined the relations between user and product then you can query it using orWhereHas as:
Product::where('price', 0)
->orWhereHas('users', function ($q) user($user) {
$q->where('user_id', $user->id);
})
->get();
Assuming your relation name is users.
I have 2 tables: users and articles. To fetch all columns from the articles table and only user_name column from the users table, I use this code:
$articles = Article::join('users', 'articles.user_id', '=', 'users.user_id')
->get(array('articles.*', 'users.user_name'));
and it works fine, but when I use paginate() method like this:
$articles = Article::join('users', 'articles.user_id', '=', 'users.user_id')
->paginate(10);
it fetches all columns from both tables, which I don't want. My question is: How can I select columns that will be returned in the result if I use paginate() method in Laravel framework?
The select function does this.
$articles = Article::join('users', 'articles.user_id', '=', users.user_id')
->select('articles.*', 'users.user_name')
->paginate(10);