Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[BUG] Pagination not working if query contain HAVING statement #3105

Closed
Elyahou opened this issue Jan 2, 2014 · 26 comments
Closed

[BUG] Pagination not working if query contain HAVING statement #3105

Elyahou opened this issue Jan 2, 2014 · 26 comments

Comments

@Elyahou
Copy link

Elyahou commented Jan 2, 2014

I have a query that return the cities on a fixed distance from a point:

$query->addSelect(DB::Raw(
            '(3959 * acos (
              cos ( radians('.$lat.') )
              * cos( radians( lat ) )
              * cos( radians( lon ) - radians('.$lon.') )
              + sin ( radians('.$lat.') )
              * sin( radians( lat ) )
            )) AS distance'))
      ->having('distance', '<', 300)->paginate(10);

This cause this mysql error:

Column not found: 1054 Unknown column 'distance' in 'having clause'

Because the agregate query generated by the pagination class contain the having but not the select (distance)

select count(*) as aggregate from `cities` where `id` > 0 having `distance` < 300
@patrickheeney
Copy link

This might be related to my issue #2560 and pull request #2564. Basically when using pagination it firsts runs the aggregate query and fails to add the select's back to the query which errors out.

@anlutro
Copy link
Contributor

anlutro commented Jan 7, 2014

See http://stackoverflow.com/a/20945960/2490608 for a workaround if you're interested.

@taylorotwell
Copy link
Member

Closing this as duplicate.

@deep88
Copy link

deep88 commented Nov 13, 2016

Hello sir i am getting this problem in laravel 5.3.. i am stuck on this from long..please solve.

@deep88
Copy link

deep88 commented Nov 13, 2016

my query is as follwing

$properties = \DB::table('properties')->select(\DB::raw("*,( 6371 * acos( cos( radians($this->user_lat) ) * cos( radians( latitude ) ) * cos( radians( longitude ) - radians($this->user_lng) ) + sin( radians($this->user_lat) ) * sin( radians( latitude ) ) ) ) AS distance"))->having('distance', '<=', $radius)
->paginate(config('local.PROPERTIES_PER_PAGE'));

@deep88
Copy link

deep88 commented Nov 13, 2016

SQLSTATE[42S22]: Column not found: 1054 Unknown column 'distance' in 'having clause' (SQL: select count(*) as aggregate from properties having distance <= 500)

@carlosalaniz
Copy link

carlosalaniz commented Nov 17, 2016

use ->simplePaginate();

public function searchByRange(Request $request)
     {
         //Validate
         $this->validate($request,
             [
                 "long" => ["required", "numeric"],
                 "lat" => ["required", "numeric"],
                 "distance" => ["numeric", "max:100"],
                 "kilometers" => ["boolean"]
             ]
         );
         $results = Item::distance(
                $request->get("distance"),
                $request->get("lat"),
                $request->get("long")
         );
         return $results->simplePaginate();
     }

It's faling due to the having clause in your query,
On method public function getCountForPagination($columns = ['*']) in Query/Builder.php it tries to do a Count(*). evaluation order on the query is causing it to break.

@carlosalaniz
Copy link

Now here an other solution...
In your model...

    /**
     * @param \Illuminate\Database\Eloquent\Builder $subQuery
     * @param double $distance
     * @param double $latitude
     * @param double $longitude
     * @return Builder
     */
    public function scopeDistance($subQuery, $distance, $latitude, $longitude)
    {
        //Generating Query
        $item_distance_query = '* , (3959 * ' .
            'acos( cos( radians(?) ) ' . //lat
            '* cos( radians( latitude ) ) ' .
            '* cos( radians( longitude ) - radians(?) ) ' . //long
            '+ sin( radians(?) ) ' . //lat
            '* sin( radians( latitude ) ) ' .
            ') ) as distance'; //distance3

        $subQuery->getQuery()->selectRaw($item_distance_query,
            [$latitude, $longitude, $latitude]
        );
        $rawQuery = self::getSql($subQuery);
        return DB::table(DB::raw("(" . $rawQuery . ") as item"))
            ->where('distance', '<', $distance);
    }

    /**
     * @param Builder $builder
     * @return string
     */
    private static function getSql($builder)
    {
        $sql = $builder->toSql();
        foreach($builder->getBindings() as $binding)
        {
            $value = is_numeric($binding) ? $binding : "'".$binding."'";
            $sql = preg_replace('/\?/', $value, $sql, 1);
        }
        return $sql;
    }

In your controller...

public function searchByRange(Request $request)
    {
        //Validate
        $this->validate($request,
            [
                "long" => ["required", "numeric"],
                "lat" => ["required", "numeric"],
                "distance" => ["numeric", "max:100"],
                "kilometers" => ["boolean"]
            ]
        );
        /** @var Builder $results */
        $results = Item::distance(
            $request->get("distance"),
            $request->get("lat"),
            $request->get("long")
        );

        return $results->paginate();

    }

@iwasherefirst2
Copy link

iwasherefirst2 commented May 16, 2018

Why is this question closed? The problem still exists. Using paginator and having at the same query makes it crash.

@devcircus
Copy link
Contributor

According to the comment above, it is a duplicate of another issue.

@anlutro
Copy link
Contributor

anlutro commented May 16, 2018

Probably more accurately closed as a wontfix because of #5515

@asepmedia
Copy link

iit's simple

change paginate(10) with simplePaginate(10)

@royduin
Copy link
Contributor

royduin commented Jul 3, 2018

What I've done (Laravel 5.5) is overwritten runPaginationCountQuery() from the Query Builder with:

/**
 * Run a pagination count query.
 *
 * @param  array  $columns
 * @return array
 */
protected function runPaginationCountQuery($columns = ['*'])
{
    if ($this->havings) {
        $query = $this->cloneWithout(['orders', 'limit', 'offset'])
                      ->cloneWithoutBindings(['order']);

        // We don't need simple columns, only specials
        // like subselects which is why we're using
        // havings after all.
        foreach ($query->columns as $key => $value) {
            if (is_string($value)) {
                unset($query->columns[$key]);
            }
        }

        $countQuery = DB::table(DB::raw('('.$query->toSql().') as x'))->mergeBindings($query);

        // Using a aggregate here won't work when
        // groups are present because the
        // getCountForPagination() is
        // checking for it.
        if (!$this->groups) {
            $countQuery->setAggregate('count', $this->withoutSelectAliases($columns));
        }

        return $countQuery->get()->all();
    }

    return $this->cloneWithout(['columns', 'orders', 'limit', 'offset'])
                ->cloneWithoutBindings(['select', 'order'])
                ->setAggregate('count', $this->withoutSelectAliases($columns))
                ->get()->all();
}

Update: I've created a package for this: https://github.com/justbetter/laravel-pagination-with-havings

@jayantakundu
Copy link

Hi
Issue is still there. I have found same issue laravel 5.6.

$data = DB::table('jobs as j')
->selectRaw('j.*, (case
when (seeker_action = 'like' and employer_action = 'like') then 'match'
when (seeker_action is null and employer_action = 'like') then 'employer-liking'
when (seeker_action = 'like' and employer_action = 'delete') then 'rejected'
else seeker_action end) as 'action'')
->join('profiles_jobs as p', 'p.job_id', '=', 'j.id')
->where('p.profile_id', '=', 2)
->whereRaw('(p.seeker_action is not null) or (p.seeker_action is null and p.employer_action = 'like')
and j.deleted_at is null')
->having('action', '=', 'employer-liking');

$data = $data->paginate(10);

Please help me

@asepmedia
Copy link

@jayantakundu change paginate(10) to simplePaginate(10) if you work with having.

@jayantakundu
Copy link

I am using laravel api resource in my api & using Resource::collection to get paginated api response. But if i change this paginate to simplePaginate, in the api response some attributes missing in pagination links & meta as generated from paginate method.

@ko1eda
Copy link

ko1eda commented Aug 9, 2018

Just ran into this issue in 5.6 @royduin solution seemed to fix it so cheers! Is there any actual planned fix for this?

@BorjaRafols
Copy link

Same Here, @royduin seems to fix it. Any plans to put this into the source code?

@solofeed
Copy link

solofeed commented Sep 5, 2018

@royduin How you override QueryBuilder?

@royduin
Copy link
Contributor

royduin commented Sep 5, 2018

@solofeed I'm currently with vacation, when I'm back next week I'll share it, create a PR or a module for this.

@solofeed
Copy link

solofeed commented Sep 5, 2018

@royduin sry for this, thank you) have a nice vacation)

@royduin
Copy link
Contributor

royduin commented Sep 11, 2018

I'm back @solofeed and I've created a package for it: https://github.com/justbetter/laravel-pagination-with-havings

@dipbro
Copy link

dipbro commented Apr 16, 2019

Thank you @royduin for your great work .

@maxpohlmann
Copy link

Whenever I had this problem, the following worked fine for me, though I'm not sure whether this is a general solution:
For your given $query, replace
$query->paginate()
with
\DB::query()->fromSub($query, '_')->paginate().

Though this is probably pretty inefficient on large result sets, as MySQL will now load the entire subquery into working memory.

@joselilove
Copy link

Thank you so much @royduin .

@shbifcompany
Copy link

@royduin thank you

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests