【问题标题】:How can I build such a query using Laravel query builder?如何使用 Laravel 查询构建器构建这样的查询?
【发布时间】:2021-06-05 03:35:07
【问题描述】:

有一个 MySQL "Categories" 表。

Category 模型具有如下所示的“hasMany”关系。

public function children(){
    return $this->hasMany(self::class, 'parent_id');
}

在类别编辑页面上,为了将父类别分配给当前类别(为此我需要填写其 parent_id 字段),我必须生成一个 Select 标记,该标记将包含类别表中的所有行,当前类别及其所有子类别除外。

category_id  | parent_id
------------------------
1            | NULL
2            | 1
3            | 2
4            | 3
5            | NULL
6            | 5

For example,
for category_id 1, should be selected lines with category_id [5, 6]
for category_id 2, should be selected lines with category_id [1, 5, 6]
for category_id 3, should be selected lines with category_id [1, 2, 5, 6]
for category_id 4, should be selected lines with category_id [1, 2, 3, 5, 6]
for category_id 5, should be selected lines with category_id [1, 2, 3, 4]
for category_id 6, should be selected lines with category_id [1, 2, 3, 4, 5]

如果一个category有parent_id为NULL,则表示它没有父category。

【问题讨论】:

  • parent_id 是干什么用的?您给出的示例没有 parent_id 并且 parent_id 值不唯一?
  • "parent_id" 是包含父类别 ID 的字段。需要此字段才能知道该类别属于哪个父类别。

标签: mysql laravel laravel-query-builder


【解决方案1】:

在代码中挖掘了一下之后,我找到了一个更优雅的解决方案。 我正在使用“文章”模型中创建的“类别”关系从数据库中进行选择

public function categories(){
    return $this->morphToMany(Category::class, 'categoryable');
}

这样做时,我只选择那些没有父类别的项目。 但是由于“类别”关系,所有的孩子都进入了“文章”集合,并且项目树构建正确,我不必用“加入”进行复杂的查询。

public function topMenu(){
    View::composer('layouts.header', function ($view){
        $view->with('categories', Category::whereNull('parent_id')->where('published', 1)->get());
    });
}

【讨论】:

    【解决方案2】:

    简短回答:您需要从当前类别中选择所有记录EXCEPT递归记录。

    对于递归部分,您可以这样使用:https://github.com/staudenmeir/laravel-adjacency-list

    查看它默认包含的descendantsAndSelf() 关系。

    接下来您需要构建EXCEPT 查询。查询构建器不包含 except 函数,因此您需要自己构建一个。

    例如,我的一个控制器中有这个:

    我有一个名为Thread 的雄辩模型,它是一个用于保存消息线程的模型。人们对这些线程有不同的查看权限,所以我需要几个查询来过滤出他们可以看到和看不到的线程。

    我使用元素查询构建器预先构建了这些单独的查询,其中一些是我 UNION。比有一个明确排除线程的查询。而不是我结合联合和$excludeExplicit 查询:

    $query = Thread::query() 
        ->fromRaw( 
            '(SELECT * FROM ((' . $unioned->toSql() . ') EXCEPT ' . $excludeExplicit->toSql() . ') AS threads) AS threads', 
            array_merge($unioned->getBindings(), $excludeExplicit->getBindings()) 
        );
    

    (见https://stackoverflow.com/a/64231210/9258054

    这个原始查询的技巧在于参数绑定的合并。此外,此查询的结果是 Eloquent 模型。请注意,当使用 UNIONEXCEPT 之类的查询时,您需要确保两者的 SELECT 语句返回相同的列。

    【讨论】:

    • 什么是“线程”这个类以及如何在控制器中连接它? $unioned、$excludeExplicit 变量包含什么?
    • 我已经更新了我的答案来解释它。我对您的建议是不要从字面上看答案并期望您可以在代码中使用它。我的回答只显示了如何做,如何构建您需要的查询。现在由你来围绕它编写你自己的逻辑。 “如何在控制器上连接它”是什么意思?
    • 关于“线程”模型,问题已经结束,我已经明白了。现在的问题仍然是如何构建子查询。如果这对您来说很容易,请显示在您的情况下在 MySQL 中执行的结果查询,这可以从调试面板中看到。然后我可能能够理解我应该创建哪些查询以便随后合并或排除它们。
    • 好吧,构建子查询并不是最难的部分。在你的情况下:$q1 = Category::whereNotNull('category_id');(基本上全选,但没有立即得到结果),$q2 = Category::where('category_id', $current)->descendantsAndSelf();。然后构建原始查询:$q1 EXCEPT $q2
    • 控制器中的这一行导致一个非常奇怪的错误 $q2 = Category::where('id', $category->parent_id)->descendantsAndSelf(); BadMethodCallException 调用未定义的方法 Staudenmeir\LaravelAdjacencyList\Eloquent\Builder::descendantsAndSelf() 虽然在模型中我根据说明连接了这个特征 class Category extends Model { use HasFactory;使用 \Staudenmeir\LaravelAdjacencyList\Eloquent\HasRecursiveRelationships; }
    猜你喜欢
    • 2017-06-29
    • 2021-09-25
    • 2018-07-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-03-19
    • 2013-02-23
    • 1970-01-01
    相关资源
    最近更新 更多