Perform advanced where clause Eloquent - php

I would know how to perform an 'Advanced Where'.
I didn't find anything in the documentation that explain what I want.. even there.
(cf: http://laravel.com/docs/4.2/queries#advanced-wheres).
Post::whereHas('international_post_en', function($q) {
$q->where('is_published', 1);
})->whereHas('categories', function($q) {
$q->where('name', 'test-one');
})->orWhereHas('subcategories', function($q) {
$q->where('name', 'test-two');
})->with('categories', 'subtags')
->get();
My query look like this:
select * from `posts` where `posts`.`deleted_at` is null and (select count(*) from `international_posts_en` where `international_posts_en`.`posts_id` = `posts`.`id` and `is_published` = ?) >= 1 and (select count(*) from `categories` inner join `posts_has_categories` on `categories`.`id` = `posts_has_categories`.`categories_id` where `posts_has_categories`.`posts_id` = `posts`.`id` and `name` = ?) >= 1 or (select count(*) from `subcategories` inner join `posts_has_subcategories` on `subcategories`.`id` = `posts_has_subcategories`.`subcategories_id` where `posts_has_subcategories`.`posts_id` = `posts`.`id` and `name_en` = ?) >= 1
But I want my query to look like this:
select * from `posts` where `posts`.`deleted_at` is null and (select count(*) from `international_posts_en` where `international_posts_en`.`posts_id` = `posts`.`id` and `is_published` = ?) >= 1 and [(](select count(*) from `categories` inner join `posts_has_categories` on `categories`.`id` = `posts_has_categories`.`categories_id` where `posts_has_categories`.`posts_id` = `posts`.`id` and `name` = ?) >= 1 or (select count(*) from `subcategories` inner join `posts_has_subcategories` on `subcategories`.`id` = `posts_has_subcategories`.`subcategories_id` where `posts_has_subcategories`.`posts_id` = `posts`.`id` and `name_en` = ?) >= 1[)]
(Sorry It is not very readable)
You can see the changes within the brackets.
So, I want the whereHas and the orWhereHas clause to be grouped within parenthesis.
Is it possible with the Laravel Query Builder or should I make a handmade query?
Thank you in advance.

Post::whereHas('international_post_en', function($q) {
$q->where('is_published', 1);
})->where(function ($q) {
$q->whereHas('categories', function($q) {
$q->where('name', 'test-one');
})->orWhereHas('subcategories', function($q) {
$q->where('name', 'test-two');
});
})->with('categories', 'subtags')->get();
In fact this is the very first example on the page you linked. However that example is pretty inaccurate, since you wouldn't group and wheres against or where, but the other way around..

Related

Return only values that have all the necesary relations [duplicate]

In Laravel 4.2, I am trying to achieve a query that returns all users, that have all of certain activities. As of now, I have a query that returns all users that have one of many activities:
//$selectedActivities being an array
$userByActivities = User::with('activities')
->whereHas('activities', function($query) use($selectedActivities){
$query->whereIn('id', $selectedActivities);
})->get();
To be more clear: given activities a,b,c. I am looking for all users that have activity a AND b AND c. My query returns all users that have activity a OR b OR c.
Thank you for your help.
EDIT:
The solution offered by lukasgeiter results in following query:
select * from `users` where
(select count(*) from `activities` inner join `activity_user` on `activities`.`id` = `activity_user`.`activity_id` where `activity_user`.`user_id` = `users`.`id` and `id` = '7') >= 1
and (select count(*) from `activities` inner join `activity_user` on `activities`.`id` = `activity_user`.`activity_id` where `activity_user`.`user_id` = `users`.`id` and `id` = '3') >= 1
and (select count(*) from `activities` inner join `activity_user` on `activities`.`id` = `activity_user`.`activity_id` where `activity_user`.`user_id` = `users`.`id` and `id` = '1') >= 1
and (select count(*) from `activities` inner join `activity_user` on `activities`.`id` = `activity_user`.`activity_id` where `activity_user`.`user_id` = `users`.`id` and `id` = '2') >= 1
Whereas the solution offered by Jarek Tkaczyk:
$userByActivities = User::with('activities')
->whereHas('activities', function($query) use($selectedActivities) {
$query->selectRaw('count(distinct id)')->whereIn('id', $selectedActivities);
}, '=', count($selectedActivities))->get();
for a similar request, results in following query:
select * from `users` where (select count(distinct id) from `activities`
inner join `activity_user` on `activities`.`id` = `activity_user`.`activity_id`
where `activity_user`.`user_id` = `users`.`id` and `id` in ('7', '3', '1', '2')) = 4
You'll have to add multiple whereHas for that:
$query = User::with('activities');
foreach($selectedActivities as $activityId){
$query->whereHas('activities', function($q) use ($activityId){
$q->where('id', $activityId);
});
}
$userByActivities = $query->get();
If you are getting Cardinality violation: 1241 Operand should contain 2 column(s) the problem is the nested selectCount adds to the normal select count(*) instead of overriding the existing select, so changing to $query->distinct()->whereIn('id', $selectedActivities); did the trick for me, or changing to $query->select(DB::raw(count(distinct id)))

Laravel - query builder - sort results by amount of tags

I have query designed like that:
$tags = auth()->user()->tags->toArray();
return $this->posts($request)->whereHas('tags', function ($q) use ($tags) {
$q->where(function ($q) use ($tags) {
foreach ($tags as $tag) {
$q->orWhere('tags.tag', 'like', $tag["tag"]);
}
})->select(DB::raw('count(distinct tags.id)'));
})->paginate(perPage());
The SQL can be:
select * from `posts`
where `posts`.`deleted_at` is null
and `expire_at` >= '2017-03-26 21:23:42.000000'
and (
select count(distinct tags.id) from `tags`
inner join `post_tag` on `tags`.`id` = `post_tag`.`tag_id`
where `post_tag`.`post_id` = `posts`.`id`
and (`tags`.`tag` like 'PHP' or `tags`.`tag` like 'pop' or `tags`.`tag` like 'UI')
) >= 1
But I need order the results by number of tags in posts, then should be something like that:
select p.*
from posts p
join (
select pt.post_id,
count(distinct t.id) as tag_count
from tags t
inner join post_tag pt on t.id = pt.tag_id
where t.tag in ('PHP', 'pop', 'UI')
group by pt.post_id
) pt on p.id = pt.post_id
where p.deleted_at is null
and p.expire_at >= '2017-03-26 21:23:42.000000'
order by pt.tag_count desc;
Is it possible create it in Laravel? How can be the query done?
Use the withCount() method:
->withCount(['tags' => function($q) {
q->where.... // Put conditionals here if needed.
}])
->orderBy('tags_count', 'desc')
If you want to count the number of results from a relationship without actually loading them you may use the withCount method, which will place a {relation}_count column on your resulting models

OrderBy is not working while applying GroupBy Laravel

I am using following query to retrieve the user feeds but ordering latest update is not working when we applying order created_at time by desc
Here i have given SQL query. Kindly suggest me solution to solve my problem
select `users`.`id` as `uid`, `updates`.`post_id`, `updates`.`id` as `upid`, `friends`.`id` as `fid`, `updates`.`user_id`, `updates`.`privacy`, `updates`.`updated_at`, `updates`.`created_at`, `friends`.`first_user_id`, `friends`.`second_user_id`, `friends`.`friend_status`, `updates`.`update_type` from `updates`
inner join `users` on `updates`.`user_id` = `users`.`id`
inner join `friends` on
CASE
WHEN friends.first_user_id = 1
THEN friends.second_user_id = updates.user_id
WHEN friends.second_user_id= 1
THEN friends.first_user_id = updates.user_id
END
where `friends`.`friend_status` > 0
and `updates`.`privacy` in (1,2)
and `updates`.`user_id` != 1 and
`updates`.`deleted_at` is null
group by `updates`.`post_id`
order by `updates`.`created_at` desc
Laravel Code
Update::select(
'users.id as uid', 'posts.id as pid', 'updates.post_id', 'updates.id as upid',
'friends.id as fid', 'updates.user_id', 'updates.privacy', 'updates.updated_at', 'updates.created_at', 'posts.deleted_at', 'friends.first_user_id',
'friends.second_user_id', 'friends.friend_status', 'updates.update_type', 'posts.wall_user_id'
)->join('users', 'updates.user_id', '=', 'users.id')
->join('posts', 'posts.id', '=', 'updates.post_id')
->join('friends', function($join) use ($userId){
$join->on(DB::raw('CASE
WHEN friends.first_user_id = '.$userId.'
THEN friends.second_user_id = updates.user_id
WHEN friends.second_user_id= '.$userId.'
THEN friends.first_user_id = updates.user_id
END'
), DB::raw(''), DB::raw(''));
})->where('friends.friend_status', '>', 0)
->whereIn('updates.privacy', [1,2])
->where('updates.user_id', '!=', Auth::user()->id)
->groupBy('updates.post_id')
->orderBy('updates.created_at', 'DESC')
->get();
select * from(select `users`.`id` as `uid`, `updates`.`post_id`, `updates`.`id` as `upid`, `friends`.`id` as `fid`, `updates`.`user_id`, `updates`.`privacy`, `updates`.`updated_at`, `updates`.`created_at`, `friends`.`first_user_id`, `friends`.`second_user_id`, `friends`.`friend_status`, `updates`.`update_type` from `updates`
inner join `users` on `updates`.`user_id` = `users`.`id`
inner join `friends` on
CASE
WHEN friends.first_user_id = 1
THEN friends.second_user_id = updates.user_id
WHEN friends.second_user_id= 1
THEN friends.first_user_id = updates.user_id
END
where `friends`.`friend_status` > 0
and `updates`.`privacy` in (1,2)
and `updates`.`user_id` != 1 and
`updates`.`deleted_at` is null order by `updates`.`created_at` desc) as tmp_table
group by `updates`.`post_id`

How can I use whereHas on a relation to the same table with Eloquent Query Builder?

I'm trying to run this query:
$products = $this->product_model->where(function($query) use ($key) {
$query->whereHas('categories', function ($category) use ($key) {
$category->where('key', $key);
});
$query->orWhereHas('parent.categories', function ($category) use ($key) {
return $category->where('key', $key);
});
});
The parent relation is another product, so it's from the same table. The problem I'm having is in the query that this produces:
SELECT *
FROM `products`
WHERE (
(SELECT count(*)
FROM `categories`
INNER JOIN `category_product` ON `categories`.`id` = `category_product`.`category_id`
WHERE `category_product`.`product_id` = `products`.`id`
AND `key` = 'mens'
) >= 1
OR
(SELECT count(*)
FROM `products` AS `self_30ec5d4782a83841add518f618b9f59e`
WHERE `self_30ec5d4782a83841add518f618b9f59e`.`id` = `products`.`parent_product_id`
AND
(SELECT count(*)
FROM `categories`
INNER JOIN `category_product` ON `categories`.`id` = `category_product`.`category_id`
WHERE `category_product`.`product_id` = `products`.`id`
AND `key` = 'mens'
) >= 1
) >= 1
)
In the sub-query after the OR I need this line:
WHERE `category_product`.`product_id` = `products`.`id`
To be this:
WHERE `category_product`.`product_id` = `self_30ec5d4782a83841add518f618b9f59e`.`id`
When I run this SQL on the database I get the correct result:
SELECT *
FROM `products`
WHERE (
(SELECT count(*)
FROM `categories`
INNER JOIN `category_product` ON `categories`.`id` = `category_product`.`category_id`
WHERE `category_product`.`product_id` = `products`.`id`
AND `key` = 'mens'
) >= 1
OR
(SELECT count(*)
FROM `products` AS `self_30ec5d4782a83841add518f618b9f59e`
WHERE `self_30ec5d4782a83841add518f618b9f59e`.`id` = `products`.`parent_product_id`
AND
(SELECT count(*)
FROM `categories`
INNER JOIN `category_product` ON `categories`.`id` = `category_product`.`category_id`
WHERE `category_product`.`product_id` = `self_30ec5d4782a83841add518f618b9f59e`.`id`
AND `key` = 'mens'
) >= 1
) >= 1
)
But I'm not sure how to make it do that in my PHP code. Also, is this the intended SQL output? Shouldn't it be doing what I want it to do? Since the sub-query is within the whereHas?
This is a bug in Laravel. has()/whereHas() does not handle conditions on self relationships or nested self relationships correctly.
I have submitted a pull request to correct this issue. You can view it here.
I don't know if it will be accepted, but you can take a look at it to see the code that was changed, and make the decision as to whether you want to manually update Laravel yourself or not. You can also follow it and wait to see the outcome.

Get all objects from database that are not in a relationship yet

I have the following query and it's doing it's job fine:
SELECT exams.id, exams.date FROM exams
WHERE exams.modul_id = (SELECT questions.modul_id FROM questions where questions.id = 5)
AND NOT EXISTS (
SELECT * FROM exam_question, questions
WHERE questions.id = 5
AND questions.id = exam_question.question_id
AND exam_question.exam_id = exams.id
)
It returns me all exams that belong to the same model as exam but not connteced to exam yet.
I want to use this query in Laravel but I always get an empty result back (it shouldn't be empty)
DB::table('exams')
->select(['id', 'date'])
->whereRaw('modul_id = '.$question->modul_id)
->whereNotExists(function ($query) use ($question) {
$query->select(DB::raw(1))
->from('questions as q')
->join('exam_question as eq', 'q.id', '=', 'eq.question_id')
->join('exams as e', 'eq.exam_id', '=', 'e.id')
->whereRaw('q.id = '.$question->id);
})
->get();
The output of the laravel expression is:
select `id`, `date` from `exams` where modul_id = 1 and not exists (select 1 from `questions` as `q` inner join `exam_question` as `eq` on `q`.`id` = `eq`.`question_id` inner join `exams` as `e` on `eq`.`exam_id` = `e`.`id` where q.id = 5)
I see problem in your sql query dump in section and not exists (... you select starts with select 1 from it will always select number 1 from your table according to selected rows count
select `id`, `date` from `exams`
where modul_id = 1
and not exists (
select 1 from `questions` as `q`
inner join `exam_question` as `eq` on `q`.`id` = `eq`.`question_id`
inner join `exams` as `e` on `eq`.`exam_id` = `e`.`id` where q.id = 5)
You have to use in query builder instead $query->select(DB::raw(1)) ... -> $query->select('*') ...
DB::table('exams')
->select(['id', 'date'])
->whereRaw('modul_id = '.$question->modul_id)
->whereNotExists(function ($query) use ($question) {
$query->select('*')
->from('questions as q')
->join('exam_question as eq', 'q.id', '=', 'eq.question_id')
->join('exams as e', 'eq.exam_id', '=', 'e.id')
->whereRaw('q.id = '.$question->id);
})
->get();
If the rest logic which you used to create your query is correct it will start to work

Categories