有一个MySQL“类别”表。
类别模型具有如下所示的“hasMany”关系。
public function children(){
return $this->hasMany(self::class, 'parent_id');
}
在类别编辑页面上,为了将父类别分配给当前类别(为此我需要填写其parent_id字段),我必须生成一个选择标签,该标签将包含类别表中的所有行,除了当前类别及其所有子类别。
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]
如果某个类别的parent_id为NULL,则表示该类别没有父类别。
简短回答:您需要选择当前类别中的所有记录
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 模型。请注意,当使用
UNION
或 EXCEPT
等查询时,您需要确保两者的 SELECT
语句返回相同的列。
在深入研究代码之后,我找到了一个更优雅的解决方案。 我正在使用“文章”模型中创建的“类别”关系从数据库中进行选择
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());
});
}