Laravel: Eloquent select returning different results - php

I'm trying to understand how Eloquent select changes the results of a query. For example:
$startDate = Carbon::createFromFormat('Y-m-d H:i:s', '2023-01-26 00:00:00', 'America/Chicago')->timezone('UTC');
$endDate = Carbon::createFromFormat('Y-m-d H:i:s', '2023-01-26 23:59:59', 'America/Chicago')->timezone('UTC');
$data = Orders::where('canceled', 0)
->whereBetween('created_at', [$startDate->toDateTimeString(), $endDate->toDateTimeString()])
->where(function ($query) {
$query->where('is_branding', 1)
->orWhere('is_premium_branding', 1);
})
->get();
I have some other code running a foreach on this data to calculate and I end up with:
{
"branding_order_items_count": 12,
"branding_order_items_sales": 799.98,
"branding_order_items_margin": 169.71,
"branding_order_items_margin_percent": 0
}
However, if I run the same query but with an added select and calculate through the same foreach loop, I get a different result:
$startDate = Carbon::createFromFormat('Y-m-d H:i:s', '2023-01-26 00:00:00', 'America/Chicago')->timezone('UTC');
$endDate = Carbon::createFromFormat('Y-m-d H:i:s', '2023-01-26 23:59:59', 'America/Chicago')->timezone('UTC');
$data = Orders::where('canceled', 0)
->whereBetween('created_at', [$startDate->toDateTimeString(), $endDate->toDateTimeString()])
->where(function ($query) {
$query->where('is_branding', 1)
->orWhere('is_premium_branding', 1);
})
->select('*', DB::raw("count(*) as count")) // <<<<<<< Added this
->get();
With that added select, I get the following:
{
"branding_order_items_count": 11,
"branding_order_items_sales": 649.99,
"branding_order_items_margin": 142.12,
"branding_order_items_margin_percent": 0
}
The first result is the correct numbers, 12 items / 799.98, etc. So why does adding the select to the eloquent query return a different result, and how can I have it return the correct results while still using the select?
TIA

I'm assuming this is using a MySQL database. PostgreSQL would throw a grouping error with that query.
The reason your results differ is most likely due to DB::raw("count(*) as count"). Without a GROUP BY clause, the results will only be 1 row of data.
If your orders table looks like this:
id
cancelled
1
1
2
0
3
0
4
1
SELECT * FROM orders WHERE cancelled = 0 will return
id
cancelled
2
0
3
0
SELECT *, COUNT(*) AS count FROM orders WHERE cancelled = 0 will return
id
cancelled
count
2
0
2
SELECT *, COUNT(*) AS count FROM orders WHERE cancelled = 0 GROUP BY id will return
id
cancelled
count
2
0
1
3
0
1

Related

Laravel groupBy performance slow

I'm working in one of my Laravel 8 projects and need to parse dates of my returned query for formatting with my graph. My query, which returns around 70,000 rows takes around 100ms, but upon further debugging, the getUptimeTimeline function in my project which takes the results of my query as $uptimeChecks is taking 800ms for just the groupBy part.
How can I improve this performance or maybe exclude it entirely?
The query results passed to my function are:
$uptimeChecks = UptimeCheck::where('user_id', $user->id)
->where('monitor_id', $monitor['id'])
->where('checked_at', '>=', $from)
->where('checked_at', '<=', $to)
->orderBy('checked_at', 'asc')
->select('event', 'response_time', 'checked_at')
->get();
Here's my function:
/**
* Get uptime timeline
*
* #return Response
*/
protected function getUptimeTimeline($user, $id, $uptimeChecks, $period, $days)
{
try {
$start = microtime(true);
$dates = collect($period->toArray())->mapWithKeys(function ($date) {
return [$date->format('Y-m-d') => [
'total_events' => 0,
'down_events' => 0,
'up_events' => 0,
'uptime' => 'No Data',
'fill' => '#ced1d7',
]];
});
$end = microtime(true);
Log::debug('timeline_1', [
'diff' => ($end - $start) * 1000
]);
$start = microtime(true);
$uptimeDates = $uptimeChecks->groupBy(function ($item, $key) {
$date = Carbon::parse($item->checked_at);
return $date->format('Y-m-d');
});
$end = microtime(true);
Log::debug('timeline_2', [
'diff' => ($end - $start) * 1000
]);
return $uptimeDates;
} catch (\Exception $e) { }
}
It's the timeline_2 results which is slow.
UPDATE
$responseTimes = UptimeCheck::where('user_id', $user->id)
->where('monitor_id', $monitor['id'])
->where('checked_at', '>=', $from)
->where('checked_at', '<=', $to)
->orderBy('checked_at', 'asc')
->select('event', 'response_time', 'checked_at', DB::raw('DATE(checked_at) as check_date'))
->groupBy('check_date')
->limit(10)
->get();
Attempting to run a select containing DB::raw() throws an error:
[2023-01-30 20:42:31] local.ERROR: SQLSTATE[42000]: Syntax error or access violation: 1055 Expression #1 of SELECT list is not in GROUP BY clause and contains nonaggregated column 'domainmonitor_db.uptime_checks.event' which is not functionally dependent on columns in GROUP BY clause; this is incompatible with sql_mode=only_full_group_by (SQL: select event, response_time, checked_at, DATE(checked_at) as check_date from uptime_checks where user_id = 1 and monitor_id = 1 and checked_at >= 2022-12-26 20:42:31 and checked_at <= 2023-01-30 20:42:31 group by check_date order by checked_at asc limit 10) {"userId":1,"exception":"[object] (Illuminate\Database\QueryException(code: 42000): SQLSTATE[42000]: Syntax error or access violation: 1055 Expression #1 of SELECT list is not in GROUP BY clause and contains nonaggregated column 'domainmonitor_db.uptime_checks.event' which is not functionally dependent on columns in GROUP BY clause; this is incompatible with sql_mode=only_full_group_by (SQL: select event, response_time, checked_at, DATE(checked_at) as check_date from uptime_checks where user_id = 1 and monitor_id = 1 and checked_at >= 2022-12-26 20:42:31 and checked_at <= 2023-01-30 20:42:31 group by check_date order by checked_at asc limit 10) at C:\Users\Ryan\Desktop\web-projects\domain-monitor\domain-monitor-api\vendor\laravel\framework\src\Illuminate\Database\Connection.php:760)
You should group the item during the query and handle other column properly on how you want like combine, sum, min, max or whatever that belongs to a group
here's an example grouping your query by checked_at and combing the values of other columns you want to select.
$uptimeChecks = UptimeCheck::select(
DB::raw('group_concat(event) as events'), // this combines event column values belongs to a group
DB::raw('group_concat(response_time) as responses_time'), // this combines response_time column values belongs to a group
'checked_at'
)
->where('user_id', $user->id)
->where('monitor_id', $monitor['id'])
->whereBetween('checked_at', [$from, $to])
->groupBy('checked_at')
->orderBy('checked_at', 'asc')
->get();

Laravel query with date range and orWhere returns wrong results

need help in formulating eloquent query, I have this query which uses orWhere however I can't get the correct results when I wanted to added date range in the query.
comment table
id
content
user_id
post_id
created_at
is_owner
1
test
1
1
2022-07-09T04:28:50
false
2
test 1
2
2
2022-07-10T04:28:50
true
3
test 2
2
3
2022-07-11T04:28:50
true
4
test 3
2
2
2022-07-11T04:28:50
false
5
test 4
3
3
2022-07-12T04:28:50
true
6
test 5
2
2
2022-07-14T04:28:50
false
7
test 6
4
2
2022-07-14T04:28:50
false
8
test 7
5
1
2022-07-15T04:28:50
false
Assuming I have the table above with it's data and the login user is the owner of the comment.
Code
$comment = Comment::where(function ($query) use ($postIds, $userId) {
$query->whereIn('post_id', $postIds)
->where('user_id', $userId);
}
)
if ($isCommentOwner) {
$comment->orWhere(function ($query) {
->where('is_owner', true);
});
}
Using the code above I got the corrects results however when I tried to filter it out by date I can't get correct results.
The code above generate below query.
SELECT
*
FROM
`comments`
WHERE
(
(
`post_id` in (1, 2)
AND `user_id` = 2
)
OR (`is_owner` = 1)
)
AND `document_issues`.`deleted_at` IS NULL
ORDER BY `created_at` DESC
I wanted to filter the comment created from the given date range, I tried adding the ff. code.
$comment->whereDate('created_at', '>=', '2022-07-13')
$comment->whereDate('created_at', '<=', '2022-07-15');
However, I can't get the correct filtered results.
Filtering with date do work when I remove orWhere
Can someone help me with this?
Thank you.
use whereBetween try this query
$from ="2022-07-09";
$to = "2022-07-12";
$postIds = [1,2];
$comment = Comment::where(function ($query) use ($postIds, $userId,$from, $to) {
$query->whereIn('post_id', $postIds)
->where('user_id', $userId)
->whereBetween('created_at', [$from, $to]);
}
)->get();
if ($isCommentOwner) {
$comment = $comment->filter(function ($item){
return $item->is_owner == true;
});
}
I missed an important thing. You need to provide an array to whereDate method:
// whereDate need a query as first parameter
// i think the method is usually used in closures.
$comment->whereDate($comment, ['created_at', '>=', '2022-07-13'])
$comment->whereDate($comment, ['created_at', '<=', '2022-07-15']);

Get records from two date columns in db in laravel via eloquent

i have a leaves table called user_leaves in which leaves taken by users are stored, table has this structure.
id, leave_type_id, from_date, to_date, status, created_at, updated_at.
leave_type_id is basically a foreign key of types of leaves my system has.
I want to fetch all leaves taken by a user in a range of two dates like start_date and end_date (for example all leaves from 2020-08-01 to 2020-08-31 .
I want records in numbers like,
{
"user_id" : 1,
"leaves" :
{
"medical_leave" : 2,
"earned_leave" : 5,
"sick_leave" : 3
}
}
I used the code
$UserLeaves = UserLeave::whereDate('date_from', '>=', $start_date)
->whereDate('date_from', '<=', $end_date)
->get();
The problem is that if i choose from_date to query my result ,and then loop through $UserLeaves to get all days between from_date and to_date for each leave_type.
Their are cases when to_date may exceed my end_date and thus giving wrong data if i calculate all days between from_date and to_date.
You would do something like this
$baseQuery = $baseQuery->where(function ($query) use ($date_created_from, $date_created_to) {
if (isset($date_created_from) && $date_created_from != '') {
$dateFrom = Carbon::parse($date_created_from)->format('Y-m-d');
$query->whereDate('date_column', '>=', $dateFrom);
}
if (isset($date_created_to) && $date_created_to != '') {
$dateEnd = Carbon::parse($date_created_to)->format('Y-m-d');
$query->whereDate('date_column', '<=', $dateEnd);
}
});
Where
$baseQuery is the query you write to perform all your selections, aggregation etc.
The above code accounts for null values and additionally you could perform additional checks before appending the query to your base query like "date from is not greater than 1 week before today".
you can use whereBetween clause and in wherebetween you can pass $from_date and $to_date as below:
$from = date('2018-01-01');
$to = date('2018-05-02');
youModal::whereBetween('column_from', [$from, $to])->get();

Laravel query with max and group by

I need to create a select query in Laravel 5.1 which I will have no problems creating via regular SQL and I am wondering if you could help me to write it in Laravel.
I created this query that gets all Users that have a truck, trailer and delivery_date equals a particular date (comes from $week_array). It is working, but it is missing some components
$RS = $this->instance->user()
->with(['driver.delivery' => function($query) use ($week_array) {
$query->where('delivery_date', [Carbon\Carbon::parse($week_array['date'])->toDateTimeString()]);
}])
->with(['driver.trailer', 'driver.truck', 'driver.trailer.trailerType'])->get();
I need to exclude those drivers that have MAX delivery date which equals or greater than selected delivery date in the query above. This is the normal query that I need to plug-in to laravel.
In other words, I need to convert the following query (simplified) to Laravel:
SELECT
*
FROM
USERS
INNER JOIN
DRIVERS ON DRIVERS.user_id = USERS.id
INNER JOIN
DELIVERIES ON DELIVERIES.driver_id = DRIVERS.id
WHERE
1 = 1
AND DELIVERIES.driver_id NOT IN (SELECT
driver_id
FROM
DELIVERIES
GROUP BY driver_id
HAVING MAX(delivery_date) >= '2016-05-10')
You're looking for whereHas. Try:
$date = Carbon\Carbon::parse($week_array['date'])->toDateTimeString();
$RS = $this->instance->user()
->with(['driver.delivery' => function($query) use ($date) {
$query->where('delivery_date', [$date]);
}])
->with(['driver.trailer', 'driver.truck', 'driver.trailer.trailerType'])
->whereHas('driver.delivery', function($query) use ($date) {
return $query->where('delivery_date', '>', $date);
}, '=', 0)
->get();
Also try validating the query looks right by replacing ->get() with ->toSql() and using the dd helper function.

Laravel Query Builder does not work correctly

I am trying to do an advanced join with laravel, which looks like this:
$locations = DB::table('location')
->select('location.location_time',
'heart_rate.heartrate'
)
->leftJoin('heart_rate' , function($join) use ($data) {
$join->on('heart_rate.user_id', '=', DB::raw('1'))
->where('heart_rate.time', '>', DB::raw('(location.location_time - INTERVAL 5 SECOND)')) // ->whereBetween('heart_rate.time', array(, DB::raw('location.location_time + 2 SECOND')));
->where('heart_rate.time', '<', DB::raw('(location.location_time + INTERVAL 5 SECOND)'));
})
->where('location.user_id', '=', DB::raw('1'))
->whereBetween('location.location_time', array( "2015-04-24 00:00:00", "2015-04-25 23:59:59" ))
->get();
which returns the following: [{"location_time":"2015-04-24 14:00:00","heartrate":null}]
When I let it dump the query and type it directely into mySQL however, i get the following result : , which is how it should be, as the tables from the database are:
Table 'heart_rate' :
Table 'location' :
The SQL Query i dump is
SELECT
`location`.`location_time`,
`heart_rate`.`heartrate`
FROM
`location`
LEFT JOIN `heart_rate`
ON `heart_rate`.`user_id` = 1
AND `heart_rate`.`time` > (
location.location_time - INTERVAL 5 SECOND
)
AND `heart_rate`.`time` < (
location.location_time + INTERVAL 5 SECOND
)
WHERE `location`.`user_id` = 1
AND `location`.`location_time` BETWEEN "2015-04-24 00:00:00"
AND "2015-04-25 23:59:59"

Categories