Come usare paginate () con una clausola having () quando la colonna non esiste nella tabella

Ho un caso complicato …

La seguente query del database non funziona:

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

Fallisce con il messaggio: la distanza della colonna non esiste.

L’errore si verifica quando paginate () tenta di conteggiare i record con

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

Man mano che i nomi delle colonne vengono rimossi, la distanza non è nota e viene sollevata un’eccezione.

Qualcuno ha un lavoro in giro per essere in grado di utilizzare la paginazione è questo caso?

Grazie

Questo è un po ‘un problema con il generatore di query poiché tutte le selezioni vengono scartate quando si effettua una chiamata aggregata (come count(*) ). La soluzione di make-do per ora è build manualmente il 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); 

Testato con il seguente schema usando 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), ]); } 

Usando Eloquent, so che puoi passare colonne all’impaginatore, qualcosa del genere:

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

Non sono sicuro che funzioni senza Eloquent, ma potresti provare.

È ansible utilizzare l’impaginazione manuale come having stesse comportando in modo particolare con la class di impaginazione.

 $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); 

Puoi calcolare la distanza nella parte WHERE :

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

Se hai bisogno del valore della distance nella tua applicazione, dovrai calcolarlo due volte:

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

Questo è lo scope implementa una ricerca formula Haversine , con ottimizzazione aggiuntiva per la velocità, che è documentata qui .

Vorrei che ci fosse un modo più semplice per ottenere SQL raw dall’object query, ma sfortunatamente toSql() restituisce SQL prima che i segnaposto siano stati sostituiti, quindi mi sono basato su diverse *Raw chiamate *Raw . Non è male, ma vorrei che fosse più pulito.

Il codice presuppone che tu abbia colonne lat e lng nella tua tabella.

 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'."); } } 

Questo può essere usato come tale:

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

L'SQL generato, sarà simile approssimativamente a questo:

 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 

Questa non è una risposta soddisfacente, ma se hai solo bisogno di visualizzare i link “Avanti” e “Precedente” nella tua vista di impaginazione, puoi usare il metodo simplePaginate . Effettuerà una query più efficiente e non si arresterà in modo anomalo se si utilizza l’ having .

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