如何在laravel 5中的关系列上使用“有”和分页
Posted
技术标签:
【中文标题】如何在laravel 5中的关系列上使用“有”和分页【英文标题】:How to use 'having' with paginate on relationship's column in laravel 5 【发布时间】:2017-03-04 19:07:00 【问题描述】:我需要抓住关系“经销商”距离
Vehicle::join('dealers', 'vehicles.dealer_id', '=', 'dealers.id')
->select(DB::raw("dealers.id, ( cos( radians(latitude) ) * cos( radians( longitude ) ) ) AS distance"))
->havingRaw('distance < 200');
我正在尝试对关系 (belongsTo) 经销商的别名“距离”使用 havingRaw。但失败并出现错误:
未找到列:1054“有子句”中的未知列“距离”
更新
当我像这样将分页功能添加到上述查询时,实际上会出现问题。
$vehicle = Vehicle::join('dealers', 'vehicles.dealer_id', '=', 'dealers.id')
->select(DB::raw("dealers.id, ( cos( radians(latitude) ) * cos( radians( longitude ) ) ) AS distance"))
->havingRaw('distance < 200');
$result = $vehicle->paginate(15);
【问题讨论】:
你的 ->haveRaw('distance toSql() 的结果是什么? 【参考方案1】:更新
如果你在查询中使用paginate()
,laravel 将尝试执行以下 SQL 代码来计算可能匹配的总数:
select count(*) as aggregate
from `vehicles` inner join `dealers`
on `vehicles`.`dealer_id` = `dealers`.`id`
having distance < 200
如您所见,此查询中没有 distance
这样的列或别名。
选项 2 在我的原始答案中也将解决该问题。
原答案
这似乎是 mysql 严格模式的问题。如果您使用 laravel 5.3,则默认启用严格模式。你有两个选择:
选项 1:在config/database.php
中禁用 MySQL 的严格模式
...
'mysql' => [
...
'strict' => false,
...
],
...
选项 2:使用 WHERE 条件
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');
文档:
标准 SQL 的 MySQL 扩展允许 HAVING 中的引用 子句到选择列表中的别名表达式。启用 ONLY_FULL_GROUP_BY 禁用此扩展,因此需要 HAVING 使用非别名表达式编写的子句。
Server SQL Modes - ONLY_FULL_GROUP_BY
【讨论】:
JFI - 此查询将运行逻辑两次,因此可能存在一些性能问题。 @Amit - 这可能是个问题,但可能不会。如果您有一个包含 1000 个项目(车辆)的表并且您使用LIMIT 20
(paginate(20)
),则“逻辑”将在 WHERE 子句中被评估 1000 次,在 SELECT 子句中被评估 20 次。这将是 2% 的相对开销(在最坏的情况下)。表越大,相对开销越小。对于较小的表,这并不重要,因为绝对运行时间非常低。恕我直言,更大的问题是 代码重复,可以使用 PHP 变量来解决。
不过,这并不能解决使用分页和分页的问题
@ArunP - 它解决了问题背后的问题。 Laravel 分页不能(或至少不能)与 HAVING 子句一起工作(很好)。【参考方案2】:
答案更新对应于更新的问题
问题出在查询构建器上,因为在进行聚合调用(如 count(*))时,所有选择都会被丢弃。目前的解决方案是手动构建分页器:
$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 子句在 SELECT 子句之后执行。count(*)
将无条件计算。 distance
将针对“随机”行计算,因为使用了非聚合列。之后,将检查 HAVING 条件的“随机”距离。因此,您要么在没有距离条件的情况下获得所有行的计数,要么根本不会获得计数。
$totalItems = $totalResult->count();我认为这更好地获得总数【参考方案3】:
having
和 havingRaw
无权访问查询中生成的字段
从 laravel 文档中看到这个例子
您的查询必须如下所示
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');
【讨论】:
你是天才,你拯救了我的一天:)。非常感谢 这看起来像我需要的确切解决方案,但在我的情况下,纬度和经度列位于连接表中,haveRaw 似乎也无法访问。有什么建议吗?【参考方案4】:这是我所做的与您尝试做的类似的事情:
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');
在这个例子中,我有一个单独的模型来处理纬度和经度坐标,以及名为Geo
的地址信息。您可能不需要这种级别的分离,因此您可以重构为这样的:
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');
记住,为了计算距离,你需要两个点。该模型将能够通过使用自己的latitude
和longitude
列与作为参数提供给范围查询的纬度和经度来检查两点之间的距离。
您可以将其放入您的车辆模型中并像这样调用:
Vehicle::nearest($latitude, $longitude, 200);
这未针对您的用例进行测试,因此我不能保证它可以开箱即用,但希望它可以为您指明正确的方向。
【讨论】:
我在使用 join 和 'have' 时实际上遇到了问题 经纬度不存储在车辆模型中,而是存储在称为经销商的关系模型中。 你能用 Laravel 的分页来完成这项工作吗?【参考方案5】:这应该可行:
Vehicle::join('dealers', 'vehicles.dealer_id', '=', 'dealers.id')
->select(DB::raw("dealers.id, ( cos( radians(latitude) ) * cos( radians( longitude ) ) ) AS distance")->havingRaw('distance < 200'));
【讨论】:
以上是关于如何在laravel 5中的关系列上使用“有”和分页的主要内容,如果未能解决你的问题,请参考以下文章