Eloquent With Nested Where Clauses - php

I am curious if there is a way using Eloquent's query builder to nest where clauses or if I should just run a raw DB query.
Here is the raw query:
SELECT * FROM `inventory` WHERE (`sold_date` > '2020-12-31' OR `sold_date` IS NULL) AND (`removed_date` > '2020-12-31' OR `removed_date` IS NULL) AND `category` <> 1 AND `purchased_date` <= '2020-12-31'

In Laravel Eloquent you can use the below query:
$inventory = Inventory::where(function($query) {
$query->where('sold_date', '>', '2020-12-31')->orWhereNull('sold_date');
})->where(function($query) {
$query->where('removed_date', '>', '2020-12-31')->orWhereNull('removed_date');
})->where('category', '<>', 1)->where('purchased_date', '<=', '2020-12-31')
->order('id', 'DESC')
->get();

Yes you can pass an array with conditions into to Eloquent's where() and have multiple where()s.
See this answer (including the comments) for how you could build your query: https://stackoverflow.com/a/27522556/4517964

You can try this
Inventory ::where(function($query) use ($d1){
$query->where('solid_date','=',$d1)
->orWhereNull('solid_date');
})->where(function($query2) use ($da1){
$query2->where('removed_date','=',$da1)
->orWhereNull('removed_date');
})->where(function($query3) use (){
$query2->where('category','<>',1)
->where('purchased_date','=','2020-12-31');
}}->get();
I perfer to use parameter in few functions may be you will need it otherwise you can hard code it like I did in the last function

Related

Laravel query builder join after where clause

I am using laravel 8. I have this mysql command which I want to convert into laravel query builder style:
select allocation.*, leav_leave_types.leave_type_code
from (
select * from leav_employee_annual_leave_allocations
where leave_year_id = $year_id and employee_id = $user_id
) as allocation
left join leav_leave_types on (leav_leave_types.id = allocation.leave_type_id)
Actually I want to apply a where clause first and then perform a left join for better performance.
How can I convert it into query builder style?
The only thing from your query that is not currently in the documentation is using a subquery as the main table.
This can be done by passing either a Closure or a Builder instance to the table() or from() method.
DB::table(closure, alias)
DB::table(builder, alias)
DB::query()->from(closure, alias)
DB::query()->from(builder, alias)
Using a Closure:
DB::table(function ($sub) use ($user_id, $year_id) {
$sub->from('leav_employee_annual_leave_allocations')
->where('leave_year', $year_id)
->where('employee_id', $user_id);
}, 'allocation')
->select('allocation.*', 'leav_leave_types.leave_type_code')
->leftJoin('leav_leave_types', 'leav_leave_types.id', 'allocation.leave_type_id')
->get();
DB::query()
->select('allocation.*', 'leav_leave_types.leave_type_code')
->from(function ($sub) use ($user_id, $year_id) {
$sub->from('leav_employee_annual_leave_allocations')
->where('leave_year', $year_id)
->where('employee_id', $user_id);
}, 'allocation')
->leftJoin('leav_leave_types', 'leav_leave_types.id', 'allocation.leave_type_id')
->get();
Using a Builder instance
$sub = DB::table('leav_employee_annual_leave_allocations') // or DB::query()->from('leav_employee_annual_leave_allocations')
->where('leave_year', $year_id)
->where('employee_id', $user_id);
DB::table($sub, 'allocation')
->select('allocation.*', 'leav_leave_types.leave_type_code')
->leftJoin('leav_leave_types', 'leav_leave_types.id', 'allocation.leave_type_id')
->get();
// personally my favorite way. I find it very readable.
$sub = DB::table('leav_employee_annual_leave_allocations') // or DB::query()->from('leav_employee_annual_leave_allocations')
->where('leave_year', $year_id)
->where('employee_id', $user_id);
DB::query()
->select('allocation.*', 'leav_leave_types.leave_type_code')
->from($sub, 'allocation')
->leftJoin('leav_leave_types', 'leav_leave_types.id', 'allocation.leave_type_id')
->get();
The generated SQL looks like this
select "allocation".*, "leav_leave_types"."leave_type_code" from (
select * from "leav_employee_annual_leave_allocations"
where "leave_year" = ? and "employee_id" = ?
) as "allocation"
left join "leav_leave_types" on "leav_leave_types"."id" = "allocation"."leave_type_id"
If you want a parenthesis around your join condition to be generated, you should use one of the following notations instead.
leftJoin('leav_leave_types', ['leav_leave_types.id' => 'allocation.leave_type_id'])
leftJoin('leav_leave_types', function ($join) {
$join->on(['leav_leave_types.id' => 'allocation.leave_type_id']);
})
leftJoin('leav_leave_types', function ($join) {
// will generate a parenthesis if there's more than one condition
$join->on('leav_leave_types.id', 'allocation.leave_type_id')
->on(...) // and condition
->orOn(...); // or condition
})
Alternatively, you could turn the SQL around to
select *,
( SELECT leave_type_code
FROM leav_leave_types
WHERE id = allocation.leave_type_id
) AS leave_type_code
FROM leav_employee_annual_leave_allocations AS allocation
where leave_year_id = $year_id and employee_id = $user_id
(This might be more efficient.)
In either case leav_employee_annual_leave_allocations would benefit from INDEX(employee_id, leave_year_id).

Laravel Query Builder returns no results while generated SQL works perfect

I am actively working on a laravel project and have ran into some issues with the Query Builder. I am trying to avoid using DB::Raw but it is looking like I may need to.
$query = app($this->model());
$query = $query->select(['last_name', 'first_name', 'birthday'])
->distinct()
->leftJoin('enrollments', 'students_meta.uid', '=', 'enrollments.student_uid')
->whereIn('enrollments.type', $types)
->where('enrollments.startdate', '<=', "'{$today}'")
->where(function ($join) use ($today) {
$join->where('enrollments.dropdate', '>=', "'{$today}'")
->orWhereNull('enrollments.dropdate');
});
// todo: add viewBy filter
$query = $query->where('birth_month', '=', Carbon::today()->month);
$query = $query->orderBy('last_name')->orderBy('first_name');
$models = $query->get();
The above query builder generates the following SQL :
SELECT distinct `last_name`, `first_name`, `birthday`
FROM `students_meta`
LEFT JOIN `enrollments` ON `students_meta`.`uid` = `enrollments`.`student_uid`
WHERE `enrollments`.`type` IN ('ACTIVE', 'active')
AND `enrollments`.`startdate` <= '2019-10-29'
AND (`enrollments`.`dropdate` >= '2019-10-29' OR `enrollments`.`dropdate` IS NULL)
AND `birth_month` = 10
ORDER BY `last_name` asc, `first_name` asc;
The generated SQL is perfect based on the old code I'm moving from and produces the expected results. If I move some things around, it seems I can get the query builder to return results, but they're not the correct ones. I've looked at other questions/answers about this kind of problem and tried multiple scenarios of moving the join/changing the where's around, still no luck.
Any suggestions? (other than taking the generated sql and running it in DB::Raw()
$query = $query->orderBy('last_name')->orderBy('first_name')->get();
Your query worked fine but you didn't output it.
You can also use ->get()->toArray(); to retrieve the data in array format
Remove your quotes from around $today. The third (or second, if you eliminate the comparison operator) parameter of the where clause sends the values as a parameter in a prepared statement . So
"'{$today}'"
would look like this in a straight query:
where enrollments.startdate <= "'2019-10-29'"
So change your query to
->where('enrollments.startdate', '<=', $today)
Make sure you remove the quotes from all instances like this in your query.

Laravel: Filter while querying a DB or after the query?

I am a JS developer but trying to help out my team with some Laravel.
I have his query below:
$customers = ShopUser::selectRaw('shop_users.name, shop_users.email, shop_users.unique_id, shop_users.created_at, SUM(orders.total) AS total_spent, MIN(orders.created_at) AS first_purchase, MAX(orders.created_at) AS last_purchase, count(orders.id) AS total_orders')
->leftJoin('orders', 'orders.customer_id', 'shop_users.id')
->groupBy('shop_users.id')
->get();
I want to then put the unique_id of users of this query who have a total over $1,000 spent in an array. Is there a way to do that in the query above or should I make a separate iteration after this to sort that?
I think you can use use havingRaw
ShopUser::selectRaw('shop_users.name, shop_users.email, shop_users.unique_id, shop_users.created_at, SUM(orders.total) AS total_spent, MIN(orders.created_at) AS first_purchase, MAX(orders.created_at) AS last_purchase, count(orders.id) AS total_orders')
->leftJoin('orders', 'orders.customer_id', 'shop_users.id')
->havingRaw('total_spent > ?', [1000])
->groupBy('shop_users.id')
->get();
If you want to return the result as an array, you can use pluck
ShopUser::selectRaw('shop_users.name, shop_users.email, shop_users.unique_id, shop_users.created_at, SUM(orders.total) AS total_spent, MIN(orders.created_at) AS first_purchase, MAX(orders.created_at) AS last_purchase, count(orders.id) AS total_orders')
->leftJoin('orders', 'orders.customer_id', 'shop_users.id')
->havingRaw('total_spent > ?', [1000])
->groupBy('shop_users.id')
->get()
->pluck('unique_id')
->all()
I haven't tested it, but I hope this helps
Since there are lot of approaches to achieve this goal in Laravel, I prefer the Eloquent Query Builder approach rather than Row Queries.
I assume that all you need is a list of unique ids that have a total over 1,000. Ex: [233123, 434341, 35545123].
So any other selections, groupings and filters you have written in your code is ignored.
&shopUserIds = ShopUser::whereHas('orders', function ($query) {
$query->where(DB::row('SUM(total)'), '>', '1000');
})
->get()
->map->id
->toArray();

Using subqueries in Eloquent/Laravel

Here's the query in raw SQL:
SELECT *
FROM (
SELECT `characters`.`id`,`characters`.`refreshToken`,
`characters`.`name`,max(`balances`.`created_at`) as `refreshDate`
FROM `characters`
INNER JOIN `balances` ON `characters`.`id` = `balances`.`character`
WHERE `characters`.`refreshToken` IS NOT NULL
GROUP BY `characters`.`id`
) AS `t1`
WHERE `refreshDate` < '2017-03-29';
I've tested this in phpMyAdmin and it returns the expected results. However I'm using the Eloquent and Laravel libraries in my PHP app and I'm not sure how to approach this. How exactly do subqueries work in this case?
You can do a subquery as a table but need to create the subquery first and then merge the bindings into the parent query:
$sub = Character::select('id', 'refreshToken', 'name')
->selectSub('MAX(`balances`.`created_at`)', 'refreshDate')
->join('balances', 'characters.id', '=', 'balances.character')
->whereNotNull('characters.refreshToken')
->groupBy('characters.id');
DB::table(DB::raw("($sub->toSql()) as t1"))
->mergeBindings($sub)
->where('refreshDate', '<', '2017-03-29')
->get();
If that is your entire query you can do it without the subquery and use having() instead like:
Character::select('id', 'refreshToken', 'name')
->selectSub('MAX(`balances`.`created_at`)', 'refreshDate')
->join('balances', 'characters.id', '=', 'balances.character')
->whereNotNull('characters.refreshToken')
->groupBy('characters.id')
->having('refreshDate', '<', '2017-03-29');
You can use subqueries in Eloquent by specifying them as a closure to the where method. For example:
$characters = Character::where(function ($query) {
// subqueries goes here
$query->where(...
...
->groupBy('id');
})
->where('refreshDate', '<', '2017-03-29')
->get();
You have to chain your methods to the $query variable that is passed to the closure in the above example.
If you want to pass any variable to the subquery you need the use keyword as:
$characterName = 'Gandalf';
$characters = Character::where(function ($query) use ($characterName) {
// subqueries goes here
$query->where('name', $characterName)
...
->groupBy('id');
})
->where('refreshDate', '<', '2017-03-29')
->get();

Convert SQL query to Eloquent Laravel

I have a sql query:
select `id` from `users`
where (
select count(*)
from `user_event` as `uev`
where `uev`.`leader_id` = `users`.`id`
) > 1
How can I convert it to Eloquent Laravel?
Assuming you have set up the relationship you can use the has() method for that:
$users = User::select('id')->has('events', '>', 1)->get();
If you want an array of users ids (since you're only selecting the id) you can also use lists():
$ids = User::has('events', '>', 1)->lists('id');
Since you asked, this would be an alternative method (not tested though)
User::where(DB::raw('1'), '<', function($q){
$q->from('user_event')
->where('user_event.leader_id', 'users.id');
})->get();

Categories