I am trying to do union with DB query builder because with models is not working the union with paginate/cursorPaginate.
I have this but give an SQL error because Laravel is not building the right query (maybe my fault, I don’t known where to change)
The error is:
"SQLSTATE[42S22]: Column not found: 1054 Unknown column 'nv.id' in 'where clause'
(Connection: mysql, SQL:
(
select `nv`.*, `pi`.`image`, `unv`.`username`, `p`.`image` as `profileimage`
from `posts` as `nv`
inner join `post_images` as `pi` on `nv`.`id` = `pi`.`post_id`
inner join `users` as `unv` on `nv`.`user_id` = `unv`.`id`
inner join `profiles` as `p` on `nv`.`user_id` = `p`.`user_id`
where `nv`.`id` not in (1, 2, 3, 4, 5, 6, 8)
and (`nv`.`id` < 1)
order by `nv`.`id` desc
) union (
select `v`.*, `piv`.`image`, `uv`.`username`, `pr`.`image` as `profileimage`
from `posts` as `v`
inner join `post_images` as `piv` on `v`.`id` = `piv`.`post_id`
inner join `users` as `uv` on `v`.`user_id` = `uv`.`id`
inner join `profiles` as `pr` on `v`.`user_id` = `pr`.`user_id`
where `v`.`id` in (1, 2, 3, 4, 5, 6, 8)
and (`nv`.`id` < 1)
order by `v`.`id` desc
)
limit 3
)"
And that is right! I don’t know WHY Laravel uses the nv.id alias where the correct is v.id…
The function code is:
public function getPosts()
{
$viewedPostsIds = ViewedPost::select('post_id')->where('user_id', Auth::id())->get();
$postsNotViewed = DB::table('posts as nv')
->select(['nv.*', 'pi.image', 'unv.username', 'p.image as profileimage'])
->whereNotIn('nv.id', $viewedPostsIds)
->orderByDesc('nv.id')
->join('post_images as pi', 'nv.id', '=', 'pi.post_id')
->join('users as unv', 'nv.user_id', '=', 'unv.id')
->join('profiles as p', 'nv.user_id', '=', 'p.user_id');
$postsViewed = DB::table('posts as v')
->select(['v.*', 'piv.image', 'uv.username', 'pr.image as profileimage'])
->whereIn('v.id', $viewedPostsIds)
->orderByDesc('v.id')
->join('post_images as piv', 'v.id', '=', 'piv.post_id')
->join('users as uv', 'v.user_id', '=', 'uv.id')
->join('profiles as pr', 'v.user_id', '=', 'pr.user_id');
$cursorData = $postsNotViewed->union($postsViewed)->cursorPaginate(2);
return [
'data' => $cursorData->groupBy('post_id')->flatten(1),
'next_page' => $cursorData->nextPageUrl(),
'has_more_page' => $cursorData->hasMorePages(),
];
}
Edit:
If I edit the 2nd query to match the same alias as the first (nv.) works but the second page give me 0 results:
First page:
Second page:
Edit 2: Trying with ‘order by id in (…)’ (for remove the union) gives me a meaningless error:
public function getPosts()
{
$viewedPostsIds = ViewedPost::select('post_id')->where('user_id', Auth::id())->get()->pluck('post_id')->all();
$posts = DB::table('posts as p')->selectRaw('p.*,max(i.image) as image, u.username, max(pro.image) as profileimage')
// ->whereNotIn('nv.id', $viewedPostsIds)->orderByDesc('nv.id')
->join('post_images as i', 'p.id', '=', 'i.post_id')
->join('users as u', 'p.user_id', '=', 'u.id')
->join('profiles as pro', 'p.user_id', '=', 'pro.user_id')
->orderByRaw("p.id in (" . implode(',', $viewedPostsIds) . ") DESC")
->groupByRaw('p.id');
$cursorData = $posts->cursorPaginate(2);
return [
'data' => $cursorData->items(),//->groupBy('p.id')->flatten(1),
'next_page' => $cursorData->nextPageUrl(),
'has_more_pages' => $cursorData->hasMorePages(),
];
}
Edit 3:
Without the group by SAME error:
$viewedPostsIds = ViewedPost::select('post_id')->where('user_id', Auth::id())->get()->pluck('post_id')->all();
$posts = DB::table('posts as p')->selectRaw('p.*,i.image as image, u.username, pro.image as profileimage')
// ->whereNotIn('nv.id', $viewedPostsIds)->orderByDesc('nv.id')
->join('post_images as i', 'p.id', '=', 'i.post_id')
->join('users as u', 'p.user_id', '=', 'u.id')
->join('profiles as pro', 'p.user_id', '=', 'pro.user_id')
->whereRaw('i.id = (select min(id) from post_images where post_id = p.id)')
->orderByRaw("p.id in (" . implode(',', $viewedPostsIds) . ") DESC");
$cursorData = $posts->cursorPaginate(2);
return [
'data' => $cursorData->items(),//->groupBy('p.id')->flatten(1),
'next_page' => $cursorData->nextPageUrl(),
'has_more_pages' => $cursorData->hasMorePages(),
];
2
Answers
Please provide
SHOW CREATE TABLE posts;
The problem is that this is in the second subquery, which otherwise sees only
v
:On to other issues…
Each sub-select does an
ORDER BY
but noLIMIT
. Recommend addingLIMIT 3
to them.The outer select does a
LIMIT
without anORDER BY
. AddLIMIT 3
.For pagination to, say, page 11, change to
LIMIT 33
for inner queries andLIMIT 30, 3
for the outer query.More discussion:
https://mysql.rjweb.org/doc.php/pagination#pagination_and_union
You’re making 3 queries here; one to get a list of post IDs, one to get posts that are in that list and another to get ones that aren’t. For starters, mashing those last two together with
UNION
will leave you unable to tell which posts have been viewed and which have not in your controller/view. But you say in comments you only want this information for ordering, showing the unviewed posts first.So, you can reduce this to a single (ish) query by doing the viewed check as a subquery and passing it to the
ORDER BY
clause.An Eloquent (with a capital E) solution would put you back into multiple queries on the database side and return a nested object, but would make your controller code much more concise. It would involve setting up a scope on your
Post
model that adds the subquery column and then sorts by it:And assuming you already have expected relationships, the query would look something like this:
This is all untested, but should work in theory and at least give you a starting point to work with.