php - 如何在 Laravel Query Builder/MySQL Spatial 包中按距离对查询结果进行排序?

标签 php mysql laravel laravel-5 eloquent

首先,我想向您展示当前的数据库结构。一共有三个表:

菜肴(id,名字)

位置(id、名称、坐标(POINT))

dish_location(location_id,dish_id)

现在我想实现一个 API,它获取用户的位置(纬度、经度)并返回按距离(以公里为单位)排序的菜肴列表。我已经有了一个方法,它需要两个纬度和两个经度并给我距离。但我相信您可以告诉我一种方法,这是一种直接在 MySQL 查询中执行此操作的更高效的方法。

附加:我想在 API 中执行“加载更多”功能。所以我传递了已收到元素的数量,或者在这种情况下我将如何解决这个问题?

我将其用于 MySQL Spatial package

最佳答案

首先,让我们看一下如何使用基本查询构建器来完成此操作。然后,我们将讨论如何使用 Eloquent 模型执行此查询:

function paginateDishesFromPoint(Point $point, $pageSize) 
{
    $distanceField = "ST_Distance_Sphere(locations.coordinates, "
        . "ST_GeomFromText('{$point->toWKT()}') AS distance"; 

    return DB::table('dishes') 
        ->select('dishes.*', DB::raw($distanceField))
        ->join('dish_locations', 'dish_locations.dish_id', '=', 'dishes.id')
        ->join('locations', 'locations.id', '=', 'dish_locations.location_id')
        ->orderBy('distance') 
        ->paginate($pageSize);
}

ST_Distance_Sphere() 函数计算我们可以作为结果排序依据的距离。 Laravel 的 paginate() 方法使用通过请求 URL 传递的 page 参数为我们执行自动分页。阅读 pagination docs了解更多信息。通过上面的函数,我们可以获取一个分页的结果集,如下所示:

$point = new Point($latitude, $longitude); 
$sortedDishes = paginateDishesFromPoint($point, 15); 

...其中 Point 是来自 the packageGrimzy\LaravelMysqlSpatial\Types\Point 类我们正在使用,15 是每页的结果数。

现在,让我们尝试使用 Eloquent 模型来做到这一点。我们将使用 local query scope封装创建执行排序的查询部分所需的逻辑:

class Dish extends Model 
{
    ...

    public function locations() 
    {
        return $this->belongsToMany(App\Location::class);
    }

    public function scopeOrderByDistanceFrom($query, Point $point) 
    {
        $relation = $this->locations();
        $locationsTable = $relation->getRelated()->getTable();
        $distanceField = "ST_Distance_Sphere($locationsTable.coordinates, "
        . "ST_GeomFromText('{$point->toWKT()}') AS distance";

        return $query
            ->select($this->getTable() . '.*', DB::raw($distanceField))
            ->join(
                $relation->getTable(), 
                $relation->getQualifiedForeignKeyName(), 
                '=', 
                $relation->getQualifiedParentKeyName()
            )
            ->join(
                $locationsTable,
                $relation->getRelated()->getQualifiedKeyName(),
                '=', 
                $relation->getQualifiedRelatedKeyName()
            )
            ->orderBy('distance');
    }
}

此实现使用模型上的元数据将表名和字段名添加到查询中,因此如果它们发生变化,我们无需更新此方法。现在我们可以使用模型获取有序集:

$point = new Point($latitude, $longitude); 
$sortedDishes = Dish::orderByDistanceFrom($point)->paginate($pageSize);

$sortedDishes 是 Laravel 的 LengthAwarePaginator 的实例,它包装了模型的 Collection。如果我们将结果传递给 View ,下面是如何在 Blade 模板中显示它们:

<ul>
    @foreach($sortedDishes as $dish) 
        <li>{{ $dish->name }} is {{ $dish->distance }} meters away.</li>
    @endforeach
</ul>

<a href="{{ $sortedDishes->nextPageUrl() }}">Load more...</a>

如上所示,分页器提供convenience methods我们可以使用它轻松地在分页结果之间移动。

或者,我们可以使用 AJAX 请求来加载结果。请务必在请求数据的 page 参数中传递 current page + 1

关于php - 如何在 Laravel Query Builder/MySQL Spatial 包中按距离对查询结果进行排序?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/46531031/

相关文章:

php - 从其中包含内爆函数的查询获取输出

php - MSSQL 是否有 mysql_ping 的替代方案?

php - 在 Zend Framework 中去除 HTML 标签

php - 使用 PHP 和 MySQL 的分组/分层报告

php - MySQL 限制删除

Laravel 验证 : required field in not required array

php - Laravel 和数据库设计

php - 带有 OAuth 的 Lumen API,Guzzle get/post 方法

php - 拉拉维尔 : Integrity constraint violation

mysql - 在 CentOs 7 上安装 JasperServer 6.3.0 问题