如何在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】:

havinghavingRaw 无权访问查询中生成的字段 从 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');

记住,为了计算距离,你需要两个点。该模型将能够通过使用自己的latitudelongitude 列与作为参数提供给范围查询的纬度和经度来检查两点之间的距离。

您可以将其放入您的车辆模型中并像这样调用:

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中的关系列上使用“有”和分页的主要内容,如果未能解决你的问题,请参考以下文章

laravel 5.2中一列上有2个外键

Laravel 获取所有关系和分页关系数据

HIVE—索引分区和分桶的区别

Laravel 5.5 检索嵌套关系中的第一条记录

如何在laravel中使用逗号分隔值的列上使用'where'

如何从laravel中的关系中获取对象