18

我有一个棘手的案例...

以下数据库查询不起作用:

DB::table('posts')
->select('posts.*', DB::raw($haversineSQL . ' as distance'))
->having('distance', '<=', $distance)
->paginate(10);

它失败并显示消息:列距离不存在。

当 paginate() 尝试计算记录时发生错误

select count(*) as aggregate from {query without the column names}

由于列名被剥离,距离未知并引发异常。

在这种情况下,有人有办法使用分页吗?

谢谢

4

7 回答 7

8

您可以计算零件中的距离WHERE

DB::table('posts')
    ->whereRaw($haversineSQL . '<= ?', [$distance])
    ->paginate(10);

如果您需要distance应用程序中的值,则必须计算两次:

DB::table('posts')
    ->select('posts.*', DB::raw($haversineSQL . ' as distance'))
    ->whereRaw($haversineSQL . '<= ?', [$distance])
    ->paginate(10);
于 2018-05-18T20:10:29.750 回答
7

这对于查询构建器来说有点问题,因为在进行聚合调用(如count(*))时,所有选择都会被丢弃。目前的解决方案是手动构建 pagniator。

$query = DB::table('posts')
    ->select(DB::raw('(c1 - c2) as distance'))
    ->having('distance', '<=', 5);

$perPage = 10;
$curPage = Paginator::getCurrentPage(); // reads the query string, defaults to 1

// clone the query to make 100% sure we don't have any overwriting
$itemQuery = clone $query;
$itemQuery->addSelect('posts.*');
// this does the sql limit/offset needed to get the correct subset of items
$items = $itemQuery->forPage($curPage, $perPage)->get();

// manually run a query to select the total item count
// use addSelect instead of select to append
$totalResult = $query->addSelect(DB::raw('count(*) as count'))->get();
$totalItems = $totalResult[0]->count;

// make the paginator, which is the same as returned from paginate()
// all() will return an array of models from the collection.
$paginatedItems = Paginator::make($items->all(), $totalItems, $perPage);

使用 MySQL 使用以下模式进行测试:

Schema::create('posts', function($t) {
    $t->increments('id');
    $t->integer('c1');
    $t->integer('c2');
});

for ($i=0; $i < 100; $i++) { 
    DB::table('posts')->insert([
        'c1' => rand(0, 10),
        'c2' => rand(0, 10),
    ]);
}
于 2014-01-06T08:36:14.637 回答
7

这不是一个令人满意的答案,但如果您只需要在分页视图中显示简单的“下一个”和“上一个”链接,您可以使用该simplePaginate方法。它将执行更有效的查询,并且如果您使用having.

DB::table('posts')
->select('posts.*', DB::raw($haversineSQL . ' as distance'))
->having('distance', '<=', $distance)
->simplePaginate(10);
于 2018-05-16T10:29:59.590 回答
5

有一种更好的方法,也适用于链接:

        $curPage = \Illuminate\Pagination\Paginator::resolveCurrentPage();
        $total = $model->get()->count();
        $items = $model->forPage($curPage, $showPerPag)->get();
        $paginated = new \Illuminate\Pagination\LengthAwarePaginator($items, $total, $showPerPage, $curPage, ['path' => request()->url(), 'query' => request()->query()]);
于 2018-11-28T07:19:49.713 回答
4

使用 Eloquent,我知道您可以将列传递给分页器,如下所示:

Post::having('distance','<=', $distance)
   ->paginate(10, array('*', DB::raw($haversineSQL . ' as distance')));

不知道它是否在没有 Eloquent 的情况下工作,但你可以试一试。

于 2014-01-06T12:15:53.770 回答
2

这是范围实现了一个Haversine 公式搜索,对速度进行了额外的优化,在此处记录

我希望有一种更简洁的方法从查询对象中获取原始 SQL,但不幸toSql()的是在占位符被替换之前返回 SQL,所以我依赖于几个*Raw调用。这不是太糟糕,但我希望它更清洁。

该代码假定您有列lat并且lng在您的表中。

const DISTANCE_UNIT_KILOMETERS = 111.045;
const DISTANCE_UNIT_MILES      = 69.0;

/**
 * @param $query
 * @param $lat
 * @param $lng
 * @param $radius numeric
 * @param $units string|['K', 'M']
 */
public function scopeNearLatLng($query, $lat, $lng, $radius = 10, $units = 'K')
{
    $distanceUnit = $this->distanceUnit($units);

    if (!(is_numeric($lat) && $lat >= -90 && $lat <= 90)) {
        throw new Exception("Latitude must be between -90 and 90 degrees.");
    }

    if (!(is_numeric($lng) && $lng >= -180 && $lng <= 180)) {
        throw new Exception("Longitude must be between -180 and 180 degrees.");
    }

    $haversine = sprintf('*, (%f * DEGREES(ACOS(COS(RADIANS(%f)) * COS(RADIANS(lat)) * COS(RADIANS(%f - lng)) + SIN(RADIANS(%f)) * SIN(RADIANS(lat))))) AS distance',
        $distanceUnit,
        $lat,
        $lng,
        $lat
    );

    $subselect = clone $query;
    $subselect
        ->selectRaw(DB::raw($haversine));

    // Optimize the query, see details here:
    // http://www.plumislandmedia.net/mysql/haversine-mysql-nearest-loc/

    $latDistance      = $radius / $distanceUnit;
    $latNorthBoundary = $lat - $latDistance;
    $latSouthBoundary = $lat + $latDistance;
    $subselect->whereRaw(sprintf("lat BETWEEN %f AND %f", $latNorthBoundary, $latSouthBoundary));

    $lngDistance     = $radius / ($distanceUnit * cos(deg2rad($lat)));
    $lngEastBoundary = $lng - $lngDistance;
    $lngWestBoundary = $lng + $lngDistance;
    $subselect->whereRaw(sprintf("lng BETWEEN %f AND %f", $lngEastBoundary, $lngWestBoundary));

    $query
        ->from(DB::raw('(' . $subselect->toSql() . ') as d'))
        ->where('distance', '<=', $radius);
}

/**
 * @param $units
 */
private function distanceUnit($units = 'K')
{
    if ($units == 'K') {
        return static::DISTANCE_UNIT_KILOMETERS;
    } elseif ($units == 'M') {
        return static::DISTANCE_UNIT_MILES;
    } else {
        throw new Exception("Unknown distance unit measure '$units'.");
    }
}

这可以这样使用:

        $places->NearLatLng($lat, $lng, $radius, $units);
        $places->orderBy('distance');

生成的 SQL 大致如下所示:

select
  *
from
  (
    select
      *,
      (
        '111.045' * DEGREES(
          ACOS(
            COS(
              RADIANS('45.5088')
            ) * COS(
              RADIANS(lat)
            ) * COS(
              RADIANS('-73.5878' - lng)
            ) + SIN(
              RADIANS('45.5088')
            ) * SIN(
              RADIANS(lat)
            )
          )
        )
      ) AS distance
    from
      `places`
    where lat BETWEEN 45.418746  AND 45.598854
      and lng BETWEEN -73.716301 AND -73.459299
  ) as d
where `distance` <= 10
order by `distance` asc
于 2015-04-28T22:42:13.240 回答
2

您可以使用手动分页作为having分页类的特殊行为。

$posts = DB::table('posts')
    ->select('posts.*', DB::raw($haversineSQL . ' as distance'))
    ->having('distance', '<=', $distance)
    ->get();

// Items per page
$perPage = 10;
$totalItems = count($posts);
$totalPages = ceil($totalItems / $perPage);

$page = Input::get('page', 1);

if ($page > $totalPages or $page < 1) {
    $page = 1;
}

$offset = ($page * $perPage) - $perPage;

$posts = array_slice($posts, $offset, $perPage);

$posts = Paginator::make($posts, $totalItems, $perPage);


dd($posts);
于 2014-01-06T09:43:47.430 回答