SELECT
posts.id,
(select count(*) from post_likes where post_id = 13 and user_id = 12) as post_like
FROM
posts
LIMIT 5
How to write this query in Laravel query builder?
If your ORM models are defined (and you have both Post and PostLike models), create a relationship in your Post.php model (if not already), like:
public function likes(){
return $this->hasMany(PostLike::class);
}
Then if you only need the count, try something like:
$userId = 12;
$postList = Post::query()
->whereId(13)
->withCount(['likes', 'likes AS post_like' => function ($query) use($userId) {
$query->where('user_id', '=', $userId);
}])
->limit(5)
->get();
// Then do something with result.
foreach ($postList as $post) {
$count = $post['post_like'];
}
Note that above we use post_like alias, and limit to user_id, just to much OP requirements; Else we could simply set likes_count to the number of relations, like:
->withCount('likes')
But you could use relationship for subquery with the whereHas(...) eloquent method, like:
Post::query()->whereHas('likes', function($query){
$query->where(...your statements for sub query go there);
}, '>', 4)->limit(5)->get(); //Select where more than 4 relation found with given parameters
For more see: https://laravel.com/docs/8.x/eloquent-relationships#querying-relationship-existence
Related
Here is the query I tried to write and gives an error
$users =User::has('subscriptions', function (Builder $q) {
$q->whereNotNull('ends_at');
})->get();
Getting this error
SQLSTATE[42601]: Syntax error: 7 ERROR: SELECT * with no tables specified is not valid LINE 1: ...sers"."id" = "subscriptions"."user_id") = (select * where "e... ^ (SQL: select * from "users" where (select count(*) from "subscriptions" where "users"."id" = "subscriptions"."user_id") = (select * where "ends_at" > now) and "users"."deleted_at" is null)
When I write this code I get results but need to filter result to get a list of subscribed users without calling User::all() then loop to filter.
User::has('subscriptions')->get();
use
$users = User::with(['subscriptions' => static function ($query) {
$query->whereNotNull('ends_at');
}])->get();
Read Constraining Eager Loads
To query, you need to load the subscriptions relationship first.
Solution is here 😁
$users = User::whereHas('subscriptions', function (Builder $q) {
return $q->active();
})->get()->toArray();
Have a good day
Do it like this
$users = User::with(['subscriptions' => static function ($query) { $query->whereNotNull('ends_at'); }])->get();
$users = User::with(['subscriptions' => function ($query) {
return $query->whereNotNull('ends_at');
}])->get();
Or,
$users = User::with('subscriptions')->whereHas('subscriptions', function ($query) {
return $query->whereNotNull('ends_at');
})->get();
This will give you only subscribed users. But if you fetch all the users and then apply filter to the fetched result, then every row will be fetched from the database, which is not a good practice.
How to do this query in Laravel Eloquent?
SELECT * FROM `ftm_users`, `ftm_students`, `ftm_user_verification` WHERE `ftm_users`.`user_group` = 3 AND `ftm_user_verification`.`verification_status` = 1 AND `ftm_users`.`uid` = `ftm_students`.`uid` AND `ftm_users`.`uid` = `ftm_user_verification`.`uid`
I already set the relationship in Model and I have tried with this
$userStudent = User::where('user_group', '=', 3)->with(array('userVerification' => function($query) {
$query->where('verification_status', '=', 1);
}, 'student', 'studentParents'))->simplePaginate(20);
but the query is using separated select statement to get data from different table.
Thanks.
Assuming that you defined the relationships between ftm_users and ftm_user_verification, you can use the whereHas method to filter related models
$userStudent = User::where('user_group', '=', 3)->whereHas('userVerification', function ($query) {
$query->where('verification_status', '=', 1);
})->get();
Check Querying Relationship Existence in the docs
I have a main table, "help_pages" which stores id's of either articles or videos. There are two other tables: "articles" and "videos."
In the example below, $portalType is either 'reseller' or 'global' for the purposes of my application. All was well as long as I didn't have a flag on videos to be unpublished. Now I'm having trouble filtering the scope function by BOTH articles and videos that are published.
This is in my models/HelpPage.php:
public function scopeByPortalType($query, $portalType) {
return $query->where('portal_type', '=', $portalType)
->leftjoin('articles', 'content_id', '=', 'articles.id')
->where('articles.published', '=', '1');
}
what I wish I could do is just add a second
->leftJoin('videos', 'content_id', '=', videos.id')
->where('videos.published', '=', '0');
but this returns too many rows. I tried creating a temp table with a UNION for both articles and videos:
CREATE TEMPORARY TABLE IF NOT EXISTS tmp_assets AS
(SELECT id, 'Article' AS content_type FROM articles WHERE published = 1)
UNION
(SELECT id, 'Video' AS content_type FROM videos WHERE published = 1)
and then joining that, but no dice. This might be a smaller deal than I'm making it, but I'm lost now!
Laravel version 4.2
Well, it's not pefect, but it does what I need it to do:
public function scopeByPortalType($query, $portalType) {
$q = $query
->whereRaw("
(id IN (SELECT help_pages.id FROM help_pages INNER JOIN articles ON content_id=articles.id WHERE articles.published='1' AND content_type='Article')
OR
id IN (SELECT help_pages.id FROM help_pages INNER JOIN videos ON content_id=videos.id WHERE videos.published='1' AND content_type='Video'))")
->where('portal_type', '=', $portalType);
return $q;
}
I wish I could've figured out a way to do it using actual Eloquent, but every step led me down a rabbit hole with a dead end. This worked!
The Eloquent way to do this would be setting up the relationships so you could utilize eager loading.
Relationships: http://laravel.com/docs/4.2/eloquent#relationships
Eager Loading: http://laravel.com/docs/4.2/eloquent#eager-loading
Setup relations:
class HelpPage extends Eloquent {
public function articles()
{
return $this->hasMany('Article');
}
public function videos()
{
return $this->hasMany('Video');
}
}
Then you can utilize eager loading like this:
$help_pages = HelpPage::with(array('articles' => function($query)
{
$query->where('published', '=', 1);
},
'videos' => function($query)
{
$query->where('published', '=', 1);
}
))->get();
You might be able to then leverage whereHas and orWhereHas on the actual query rather than my example above.
I'm trying to use the orWhereHas method in Laravel with nested relations, but I am not getting the result I expect. Am I misunderstanding how to use this method?
My relationships are set up as follows:
Product
-> hasOne uniqueItem
-> hasMany fulfillmentCenterUniqueItems
-> hasMany skus
-> hasOne uniqueItem
-> hasMany fulfillmentCenterUniqueItems
I'm trying to test out the whereHas and orWhereHas methods by retrieving products from the database that contain uniqueItems that have a uniqueItemFulfillmentCenter with id = 7089 OR products that contain a sku, that contains a uniqueItem, that has a uniqueItemFulfillmentCenter with id = 7412.
Based on the data in my database, the result of this query should be two products. Product IDs 105 and 239.
Here's the Eloquent code I'm using:
$product = Spire_models\Product
::whereHas('uniqueItem.fulfillmentCenterUniqueItems', function ($query)
{
$query->where('id', 7089);
})
->orWhereHas('skus.uniqueItem.fulfillmentCenterUniqueItems', function ($query)
{
$query->where('id', 7412);
})
->get()->toArray();
For some reason, this is only returning product ID 105, instead of 105 and 239. The generated sql from this function is:
select * from `products` where `products`.`deleted_at` is null and (select count(*) from `unique_items` where `products`.`unique_item_id` = `unique_items`.`id` and (select count(*) from `fulfillment_center_unique_items` where `fulfillment_center_unique_items`.`unique_item_id` = `unique_items`.`id` and `id` = 7089 and `fulfillment_center_unique_items`.`deleted_at` is null) >= 1 and `unique_items`.`deleted_at` is null) >= 1 and (select count(*) from `skus` where `skus`.`product_id` = `products`.`id` and (select count(*) from `unique_items` where `skus`.`unique_item_id` = `unique_items`.`id` or (select count(*) from `fulfillment_center_unique_items` where `fulfillment_center_unique_items`.`unique_item_id` = `unique_items`.`id` and `id` = 7412 and `fulfillment_center_unique_items`.`deleted_at` is null) >= 1 and `unique_items`.`deleted_at` is null) >= 1 and `skus`.`deleted_at` is null) >= 1
Is this sql being generated incorrectly, or am I misusing the orWhereHas method? To me it does not look like the OR statement is being placed correctly in the sql.
If I remove the orWhereHas method, things works as expected. For example, if I run this:
$product = Spire_models\Product
::whereHas('uniqueItem.fulfillmentCenterUniqueItems', function ($query)
{
$query->where('id', 7089);
})
->get()->toArray();
I correctly get back product ID 105. If I run this:
$product = Spire_models\Product
::whereHas('skus.uniqueItem.fulfillmentCenterUniqueItems', function ($query)
{
$query->where('id', 7412);
})
->get()->toArray();
I correctly get back product ID 239. So the individual pieces of the query work correctly, but it seems when I try to combine these with an orWhereHas, I get unexpected results. Any idea why?
EDIT
As per the comments, it looks like this is a bug. I was able to temporarily work around it by rewriting the code to use where and orWhere. Here's the temporary solution:
$product = Spire_models\Product
::where(function ($query)
{
$query->whereHas('uniqueItem.fulfillmentCenterUniqueItems', function ($query)
{
$query->where('id', 7089);
});
})
->orWhere(function ($query)
{
$query->whereHas('skus.uniqueItem.fulfillmentCenterUniqueItems', function ($query)
{
$query->where('id', 7412);
});
})
->get()->toArray();
It was a bug and is fixed by now with this PR https://github.com/laravel/framework/pull/8171
It's been OK since version 5.0.21
I have 3 Models... Category, Post, Vote
When viewing a category, I am showing an index of all Posts in that category. So I'm doing something like foreach ($category->posts as $post) in my view.
The question I have now is how can I order the posts based on the sum of votes they have?
I have standard relationships setup, so that a post hasMany votes.
You can do it either by defining a helper relation on the Post model and sorting the collection after the relation is loaded OR simply by joining the votes and ordering in the query.
1 RELATION
// Post model
public function votesSum()
{
return $this->hasOne('Vote')->selectRaw('post_id, sum(votes) as aggregate')->groupBy('post_id');
}
// then
$category->posts->load('votesSum'); // load relation on the collection
$category->posts->sortByDesc(function ($post) {
return $post->votesSum->aggregate;
});
// access votes sum like this:
$category->posts->first()->votesSum->aggregate;
2 JOIN
$category->load(['posts' => function ($q) {
$q->leftJoin('votes', 'votes.post_id', '=', 'posts.id')
->selectRaw('posts.*, sum(votes.votes) as votesSum')
->groupBy('posts.id')
->orderBy('votesSum', 'desc');
}]);
// then access votes sum:
$category->posts->first()->votesSum;
You can use scope for that:
// Post model
public function scopeOrderByVotes($query)
{
$query->leftJoin('comments','comments.post_id','=','posts.id')
->selectRaw('posts.*,sum(comments.id) as commentsSum')
->groupBy('posts.id')
->orderBy('commentsSum','desc');
}
// then
$category = Category::with(['posts' => function ($q) {
$q->orderByVotes();
}])->whereSlug($slug)->firstOrFail();