I need to grab the vehicles whose relation \'dealer\' is having distance < 200
Vehicle::join(\'dealers\', \'vehicles.dealer_id\', \'=\', \'dealers.id\')
Answer Updated corresponding to updated question
The problem is with the query builder as all selects are discarded when doing an aggregate call (like count(*)). The make-do solution, for now, is to construct the paginator manually as:
$query = Vehicle::join('dealers', 'vehicles.dealer_id', '=', 'dealers.id')
->select(DB::raw("dealers.id, ( cos( radians(latitude) ) * cos( radians( longitude ) ) ) AS distance"))
->having('distance', '<', '200');
$perPage = 10;
$curPage = \Illuminate\Pagination\Paginator::resolveCurrentPage();
$itemQuery = clone $query;
$items = $itemQuery->forPage($curPage, $perPage)->get();
$totalResult = $query->addSelect(DB::raw('count(*) as count'))->get();
$totalItems = $totalResult->first()->count;
$vehicles = new \Illuminate\Pagination\LengthAwarePaginator($items->all(), $totalItems, $perPage);
having
and havingRaw
doesn't have access to generated fields in the query
see this example from laravel docs
Your query will have to lookk like this
Vehicle::join('dealers', 'vehicles.dealer_id', '=', 'dealers.id')
->select(DB::raw("dealers.id, ( cos( radians(latitude) ) * cos( radians( longitude ) ) ) AS distance"))
->havingRaw('(cos(radians(latitude)) * cos(radians(longitude))) < 200');
This should work:
Vehicle::join('dealers', 'vehicles.dealer_id', '=', 'dealers.id')
->select(DB::raw("dealers.id, ( cos( radians(latitude) ) * cos( radians( longitude ) ) ) AS distance")->havingRaw('distance < 200'));
If you use paginate()
with your query laravel will try to execute the following SQL code to count the total number of possible matches:
select count(*) as aggregate
from `vehicles` inner join `dealers`
on `vehicles`.`dealer_id` = `dealers`.`id`
having distance < 200
As you can see, there is no such column or alias distance
in this query.
Option 2 in my original answer will fix that issue too.
That seams to be a MySQL-strict-mode issue. If you use laravel 5.3 strict mode is enabled per default. You have two options:
Option 1: Disable strict mode for MySQL in config/database.php
...
'mysql' => [
...
'strict' => false,
...
],
...
Option 2: Use a WHERE condtition
Vehicle::join('dealers', 'vehicles.dealer_id', '=', 'dealers.id')
->select(DB::raw("dealers.id, ( cos( radians(latitude) ) * cos( radians( longitude ) ) ) AS distance"))
->whereRaw('cos( radians(latitude) ) * cos( radians( longitude ) ) < 200');
Documentation:
A MySQL extension to standard SQL permits references in the HAVING clause to aliased expressions in the select list. Enabling ONLY_FULL_GROUP_BY disables this extension, thus requiring the HAVING clause to be written using unaliased expressions.
Server SQL Modes - ONLY_FULL_GROUP_BY
Here's something I did that appears to be similar to what you're trying to do:
public function scopeNearest($query, Geo $geo, $miles = 25)
{
return $query
->select(DB::raw("*, ( 3959 * acos( cos( radians({$geo->lat}) ) * cos( radians( lat ) ) * cos( radians( lng ) - radians({$geo->lng}) ) + sin( radians({$geo->lat}) ) * sin( radians( lat ) ) ) ) AS distance"))
->groupBy('id')
->having('distance', '<', $miles)
->orderBy('distance');
}
In this example, I had a separate model handling latitude and longitude coordinates, as well as address information called Geo
. You might not need that level of separation, so you can probably refactor to something like this:
public function scopeNearest($query, $lat, $lng, $miles = 25)
{
return $query
->select(DB::raw("*, ( 3959 * acos( cos( radians({$lat}) ) * cos( radians( latitude ) ) * cos( radians( longitude ) - radians({$lng}) ) + sin( radians({$lat}) ) * sin( radians( latitude ) ) ) ) AS distance"))
->groupBy('id')
->having('distance', '<', $miles)
->orderBy('distance');
}
Remember, in order to calculate distance, you need two points. The model will be able to check the distance between two points by using its own latitude
and longitude
columns against the latitude and longitude provided to the scope query as an argument.
You can put this in your Vehicle model and call like so:
Vehicle::nearest($latitude, $longitude, 200);
This is untested for your use-case, so I can't guarantee that it will work out of the box, but hopefully it should point you in the right direction.