繁体   English   中英

限制与Laravel的多态多对多关系中的相关记录

[英]Limit related records in polymorphic many-to-many relationship with Laravel

我正在使用Laravel 5.1,我需要使用多态多对多关系来限制我提取的相关记录的数量。

我想做的是通过parent_id获取类别列表。 对于每个类别,我只想拉四个帖子。

我有这个使用下面的代码,但它导致一堆额外的查询。 如果可能,我想只打一次数据库。 如果可能的话,我想使用Laravel / Eloquent框架,但我愿意接受这方面的任何工作。

@foreach ($categories as $category)
  @if ($category->posts->count() > 0)
    <h2>{{ $category->name }}</h2>
    <a href="/style/{{ $category->slug }}">See more</a>

    {-- This part is the wonky part --}

    @foreach ($category->posts()->take(4)->get() as $post)

      {{ $post->body }}

    @endforeach

  @endif
@endforeach

PostsController

public function index(Category $category)
{
  $categories = $category->with('posts')
      ->whereParentId(2)
      ->get();

  return view('posts.index')->with(compact('categories'));
}

数据库

posts
    id - integer
    body - string

categories
    id - integer
    name - string
    parent_id - integer

categorizables
    category_id - integer
    categorizable_id - integer
    categorizable_type - string

发布模型

<?php namespace App;
use Illuminate\Database\Eloquent\Model;
class Post extends Model
{
    public function categories()
    {
        return $this->morphToMany('App\Category', 'categorizable');
    }

分类模型

<?php namespace App;
use Illuminate\Database\Eloquent\Model;
class Category extends Model
{
    public function category()
    {
        return $this->belongsTo('App\Category', 'parent_id');
    }
    public function subcategories()
    {
        return $this->hasMany('App\Category', 'parent_id')->orderBy('order', 'asc');
    }
    public function posts()
    {
        return $this->morphedByMany('App\Post', 'categorizable');
    }

我在网上看到过这方面的一些链接,但实际上并没有对我有用。

我试过这个解决方案没有任何运气。

$categories = $category->with('posts')
->whereParentId(2)
->posts()
->take(4)
->get();

我已经在SoftOnTheSofa中查看了Jarek的这个解决方案 ,但这是为了一个hasMany关系,说实话有点超出我的sql技能,我可以根据我的需要调整它。

编辑

我为这个设置添加了一个github repo ,如果它对任何人都有用的话。

编辑您的类别模型

public function fourPosts() {
    // This is your relation object
    return $this->morphedByMany('App\Post', 'categorizable')
    // We will join the same instance of it and will add a temporary incrementing
    // Number to each post
    ->leftJoin(\DB::raw('(' . $this->morphedByMany('App\Post', 'categorizable')->select(\DB::raw('*,@post_rank := IF(@current_category = category_id, @post_rank + 1, 1) AS post_rank, @current_category := category_id'))
    ->whereRaw("categorizable_type = 'App\\\\Post'")
    ->orderBy('category_id', 'ASC')->toSql() . ') as joined'), function($query) {
        $query->on('posts.id', '=', 'joined.id');
    // And at the end we will get only posts with post rank of 4 or below
    })->where('post_rank', '<=', 4);
}

然后在你的控制器中你得到的所有类别

$categories = Category::whereParentId(1)->with('fourPosts')->get();

将只有四个帖子。 要测试这一点(请记住,现在您将使用fourPosts方法加载帖子,因此您必须使用此属性访问四个帖子):

foreach ($categories as $category) {
    echo 'Category ' . $category->id . ' has ' . count($category->fourPosts) . ' posts<br/>';
}

简而言之,您可以向变形对象添加子查询,以便我们为类别中的每个帖子分配临时递增编号。 然后你可以得到这个临时数小于或等于4的行:)

这对你有用吗?:

$categories = $category->with(['posts' => function($query)
    {
        $query->take(4);
    })
    ->whereParentId(2)
    ->get();

我认为最常用的DBMS方法是使用union all。 也许是这样的:

public function index(Category $category)
{
    $categories = $category->whereParentId(2)->get();

    $query = null;

    foreach($categories as $category) {
        $subquery = Post::select('*', DB::raw("$category->id as category_id"))
            ->whereHas('categories', function($q) use ($category) {
                $q->where('id', $category->id);
            })->take(4);

        if (!$query) {
            $query = $subquery;
            continue;
        }

        $query->unionAll($subquery->getQuery());
    }

    $posts = $query->get()->groupBy('category_id');

    foreach ($categories as $category) {
        $categoryPosts = isset($posts[$category->id]) ? $posts[$category->id] : collect([]);
        $category->setRelation('posts', $categoryPosts);
    }

    return view('posts.index')->with(compact('categories'));
}

然后,您就可以在视图中循环显示类别及其帖子。 不一定是最好看的解决方案,但它会将其减少到2个查询。 将其缩减为1个查询可能需要使用窗口函数(特别是row_number() ),MySQL不支持这些函数而没有一些技巧来模仿它( 更多关于这里。 )。 不过,我很高兴被证明是错的。

这是一个“半答案”。 我给你的一半是向你展示MySQL代码。 我无法给你的一半是将它翻译成Laravel。

以下是在加拿大各省寻找人口最多的3个城市的例子:

SELECT
    province, n, city, population
FROM
  ( SELECT  @prev := '', @n := 0 ) init
JOIN
  ( SELECT  @n := if(province != @prev, 1, @n + 1) AS n,
            @prev := province,
            province, city, population
        FROM  Canada
        ORDER BY
            province   ASC,
            population DESC
  ) x
WHERE  n <= 3
ORDER BY  province, n;

更多细节和解释可以在我的关于groupwise-max的博客中找到。

我认为最好的选择(不使用原始查询)将设置一个自定义方法与急切加载一起使用。

因此,在Category模型中,您可以执行以下操作:

<?php

public function my_custom_method()
{
    return $this->morphedByMany('App\Post', 'categorizable')->take(4);
}

然后在急切加载时使用该方法:

<?php

App\Category::with('my_custom_method')->get();

确保在视图中使用my_custom_method()而不是posts()

$category->my_custom_method()

你可以尝试这个,对我有用!

$projects = Project::with(['todo'])->orderBy('id', 'asc')->get()->map(function($sql) {
            return $sql->setRelation('todo', $sql->todo->take(4));
        });

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM