【问题标题】:Eloquent ORM pagination Reddit-like - before/after hash-id雄辩的 ORM 分页类似 Reddit - 在哈希 ID 之前/之后
【发布时间】:2016-08-16 21:03:08
【问题描述】:

我有一个带有排名列的帖子表。有没有办法从一个帖子的 ID 开始检索前 20 个帖子,同时按排名对其进行排序。它的行为应该类似于 reddit 的分页。

例如。 domain.com/posts/previous/{id_of_post_to_start_from}

更新

让我重新表述一下。正常的 laravel/eloquent(简单)分页将使用页面,可以通过执行以下操作来实现:

$posts = Post::with(array('user'=>function($q){
            $q->select('id', 'img','nick', 'points', 'slug');
        }))
           ->with('categories')
           ->with('voted')
           ->orderBy('rank', 'DESC')
           ->orderBy('created_at','DESC')
           ->simplePaginate(20);
return view('layouts.home')->with('posts',$posts);

这将使用带有?page=pageNo 参数的网址。因为排名变化非常快,所以我真正想要的是能够在某个由 id 或 hashId 标识的帖子之后获得以下或以前的帖子。这是reddit的做法:

https://www.reddit.com/?count=25&after=t3_4g1n0n

更新 2

@Jarek 的答案在按排名列排序时似乎有效,因为它很少或没有重复项,但在按票数排序时失败。它只显示 3 页而不是 5 页,并且在最后一页中跳过了一些行。我一整天都在尝试解决这个问题,我真的不知道下一步该去哪里。请帮忙。

我的网址是/ordered-by-votes?before=hashId/ordered-by-votes?after=hashId

votes desc, created_at desc排序的表结构:

| id  | rank        | votes | url        | created_at          |
+-----+-----------------------------+-----+------------+---------------------+
|  41 | 220.6454978 |  16   | 7X1dqLLNRG | 2016-04-24 08:02:03 |
|  32 | 192.6005579 |  15   | k39djkOGP0 | 2016-04-09 17:49:22 |
|  81 | 401.8382322 |  13   | 7X1dqPLNRG | 2016-07-27 18:04:14 |
|  36 | 192.6460924 |  12   | mZEzDLzxpb | 2016-04-09 19:36:12 |
|  35 | 192.5188647 |   9   | 5ykOxjNBp7 | 2016-04-09 19:34:29 |
|  50 | 325.8341567 |   8   | lm2dpyxOQY | 2016-06-18 06:39:19 |
|  34 | 192.4059869 |   7   | EDMOM0d16m | 2016-04-09 19:31:41 |
|  33 | 192.2031068 |   6   | J6RN0mdGmY | 2016-04-09 17:49:44 |
|  57 | 325.5080990 |   3   | 76mz7R1OGl | 2016-06-18 07:54:15 |
|  56 | 325.5039213 |   3   | KE0NQEkN1p | 2016-06-18 07:51:07 |
|  40 | 193.2540546 |   3   | 796Na28dJr | 2016-04-10 10:43:43 |
|  39 | 193.2262101 |   3   | 6yMNr0Ox-Y | 2016-04-10 10:22:50 |
|  38 | 193.2205435 |   3   | Y21OmeODEp | 2016-04-10 10:18:35 |
|  55 | 325.3238522 |   2   | e4Bd3xoOjg | 2016-06-18 07:48:08 |
|  54 | 325.3145633 |   2   | Z56zRyqOYM | 2016-06-18 07:41:10 |
|  53 | 325.2995633 |   2   | G3JdEMGd0y | 2016-06-18 07:29:55 |
|  52 | 325.2952967 |   2   | gm8dna7Noq | 2016-06-18 07:26:43 |
|  49 | 323.1741856 |   2   | -41zKQndp3 | 2016-06-17 04:55:53 |
|  48 | 323.1371411 |   2   | obmd2DLze2 | 2016-06-17 04:28:06 |
|  47 | 322.2090522 |   2   | 2KGdGR-N9j | 2016-06-16 16:52:02 |
|  45 | 318.4326967 |   2   | ngYN4lRNDB | 2016-06-14 17:39:46 |
|  42 | 244.7367189 |   2   | -arOeX3dby | 2016-05-07 08:27:47 |
|  27 | 191.1572300 |   2   | 7rbzy5zXGm | 2016-04-09 10:43:10 |
|  20 | 183.1333856 |   2   | PE4N13Oa-r | 2016-04-05 06:25:17 |
| 102 | 430.7966222 |   1   | -arOe3mNby | 2016-08-12 09:58:29 |
|  93 | 415.2774667 |   1   | G3JdEjGO0y | 2016-08-04 07:59:07 |
|  80 | 397.8948667 |   1   | 796NaX8zJr | 2016-07-26 06:42:10 |
|  79 | 397.0725111 |   1   | 6yMNrm0zx- | 2016-07-25 20:25:24 |
|  77 | 397.0368667 |   1   | j3-Ob8aN9k | 2016-07-25 19:58:40 |
|  75 | 388.2400889 |   1   | 5ykOxQjzBp | 2016-07-21 06:01:05 |
|  72 | 386.3236889 |   1   | k39djBkdGP | 2016-07-20 06:03:47 |
|  65 | 378.7166889 |   1   | B0kd5bkO7L | 2016-07-16 06:58:32 |
|  64 | 367.0564222 |   1   | l16zlkxOqe | 2016-07-10 05:13:20 |
|  63 | 365.7214000 |   1   | qBEdkk2d2M | 2016-07-09 12:32:04 |
|  62 | 354.0362889 |   1   | Xk3O-1jOEY | 2016-07-03 10:28:14 |
|  61 | 354.0347556 |   1   | kryNP1Kd9a | 2016-07-03 10:27:05 |
|  60 | 353.5897333 |   1   | PE4N123da- | 2016-07-03 04:53:19 |
|  58 | 329.7797111 |   1   | J-azJQ2Oeb | 2016-06-20 19:15:48 |
|  44 | 257.9238889 |   1   | jexdZ0JzR5 | 2016-05-14 09:03:56 |
|  59 | 350.6545778 |   0   | QK2N6yKzn6 | 2016-07-01 16:11:57 |
|  51 | 324.9408000 |   0   | 2P8O8P-Opm | 2016-06-18 06:46:37 |
|  46 | 321.4635111 |   0   | Da8zBj6z-Y | 2016-06-16 11:18:39 |
|  43 | 249.2581556 |   0   | e6XOLJENQ3 | 2016-05-09 20:44:38 |
|  92 | 413.8514933 | -26   | gm8dn07zoq | 2016-08-04 07:50:52 |

模型中的after方法

public function scopeAfter($query, self $post)
{
    return $query->orderBy('votes', 'desc')
        ->latest()
        ->where('status', '=', 'approved')
        ->where('id', '<>', $post->id)
        ->where('votes', '<=', $post->votes)
        ->where('created_at', '<=', $post->created_at);
}

控制器:

$posts=null;
if(isset($req['before'])){
 //...
} elseif (isset($req['after'])){
    $prevPost = Post::where('url','=',$req['after'])->first();
    $posts= Post::after($prevPost)
        ->with('categories', 'voted')
        ->with(array('user'=>function($q){
            $q->select('id', 'img','nick', 'points', 'slug');
        }))
        ->limit(10)
        ->get();
} else {
    // if no params, get the first 10
    $posts= Post::with('categories', 'voted')
        ->with(array('user'=>function($q){
            $q->select('id', 'img','nick', 'points', 'slug');
        }))
        ->where('status','=','approved')
        ->orderBy('votes', 'desc')
        ->orderBy('created_at', 'desc')
        ->limit(10)
        ->get();
}

//return $posts;

视图链接

<a href="{{url('/ordered-by-votes') . '?' . http_build_query(['after' => $lastPost->url])}}" class="btn after">< Previous</a>
<a href="{{url('/ordered-by-votes') . '?' . http_build_query(['before' => $firstPost->url])}}" class="btn before">Before ></a>

更新 3

应该有5页44条结果,但只显示3页27条结果。

第 1 页 - 正确 - 10 个结果

from 7X1dqLLNRG to KE0NQEkN1p

第 2 页 - 正确 - 10 个结果

from 796Na28dJr to 2KGdGR-N9j

第 3 页 - 不正确 - 只有 7 个结果以错误的顺序显示

ngYN4lRNDB (OK)
-arOeX3dby (OK)
7rbzy5zXGm (OK)
PE4N13Oa-r (OK)
jexdZ0JzR5 (NOT OK)
Da8zBj6z-Y (NOT OK)
e6XOLJENQ3 (NOT OK)

【问题讨论】:

    标签: php laravel pagination eloquent


    【解决方案1】:

    以下是使用代码的方法:

    // controller
    $post = Post::findByWhateverYouWant($key_from_route);
    
    // higher ranked:
    $paginator = Post::before($post)
                     ->with('categories', 'votes')
                     ->simplePaginate(20);
    
    // you need to reverse this result in order to sort 
    // appropriately but AFTER the query was executed   
    return $paginator->setCollection($paginator->reverse());
    
    
    
    
    // lower ranked:
    return Post::after($post)
               ->with('categories', 'votes')
               ->simplePaginate(20);
    

    这是带有提到的范围的模型:

    public function scopeBefore($query, self $post, $sortBy = 'votes')
    {
        return $query->orderBy($sortBy)
                     ->orderBy('created_at')
                     ->where('id', '<>', $post->id)
                     ->where(function ($q) use ($post, $sortBy) {
                        $q->where($sortBy, '=', $post->{$sortBy})
                          ->where('created_at', '>=', $post->created_at)
                          ->orWhere($sortBy, '>', $post->{$sortBy});
                     });
    }
    
    public function scopeAfter($query, self $post, $sortBy = 'votes')
    {
        return $query->orderBy($sortBy, 'desc')
                     ->latest()
                     ->where('id', '<>', $post->id)
                     ->where(function ($q) use ($post, $sortBy) {
                        $q->where($sortBy, '=', $post->{$sortBy})
                          ->where('created_at', '<=', $post->created_at)
                          ->orWhere($sortBy, '<', $post->{$sortBy});
                     });
    }
    

    您可以将第三个参数添加到这些范围内,以便根据需要自定义 2nd column

    【讨论】:

    • 您的解决方案非常适用于具有清晰、不同值的数据集,例如 rank 列(类似于 194.0356253),但由于未选择所有结果或在值不那么明显时跳过某些结果而失败.我在投票列上尝试了相同的方法(请参阅 更新 2),结果充其量是不稳定的。
    • 您使用whereDate 表示created_at 时间戳 - 它绝对与时间戳列上的where 不同。尝试使用简单的where,如果不是这样,则显示您得到的结果与预期的结果,否则不清楚。
    • 抱歉,这是我深夜的实验之一。结果还是一样。请参阅更新 3,对此我们深表歉意。
    • 查看status=approved 的剩余帖子怎么样?
    • 因为一开始where 子句是错误的,因为它只使用lower created_at 的记录,即使votes 的数量较低。这就是为什么我们需要拆分它并检查created_at only 那些具有相同votes 数量的记录。
    【解决方案2】:

    使用这样的东西(你没有共享任何代码,所以把你的模型和变量放在这里):

    $perPage = 20;
    $toSkip = $id_of_post_to_start_from - $perPage;
    $toSkip = $toSkip < 0 ? 0 : $toSkip;
    $prevousTwentyPosts = Post::orderBy('rank', 'desc')->skip($toSkip)->take($perPage)->get();
    

    这样做:

    有没有办法从ID开始检索前20个帖子 一篇文章,同时按等级排序

    【讨论】:

    • 感谢@Alexey,但 ID 不按顺序排列(排名列是指示发布顺序的列,它们不是按时间顺序排列的),而其他一些可能会丢失(已删除的帖子)。
    • @Răzvan,对不起,但你没有发布任何代码,我还不能读心。您可以轻松地为您的应用调整我的代码。
    【解决方案3】:

    我不知道我是否正确,但你不能只使用 where('id','>',$id) 吗?

    编辑:nvm,你想对所有结果进行分页,所以你不能使用 where。

    【讨论】:

      【解决方案4】:

      我认为您可以通过将当前 id 转换为排名,然后在排名上添加 where 子句并最终获取所需数量的记录来解决此问题。

      $posts = Post::with(array('user'=>function($q){
                  $q->select('id', 'img','nick', 'points', 'slug');
              }))
             ->with('categories')
             ->with('voted')
      
             ->where('rank', '>', function($q) use ($id){
                 $q->select('rank')
                   ->from('posts')
                   ->where('id', $id);
             })
      
             ->orderBy('rank', 'DESC')
             ->orderBy('created_at','DESC')
             ->take(20);
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2018-02-19
        • 2016-03-22
        • 1970-01-01
        • 1970-01-01
        • 2013-11-22
        • 1970-01-01
        • 2015-02-25
        • 2020-11-25
        相关资源
        最近更新 更多