【发布时间】:2021-12-01 08:36:19
【问题描述】:
请把问题读到最后;用法的最后部分包含说明限制和条件的重要示例
问题描述
假设我有以下简化的表格方案
CREATE TABLE albums (
id SEQUENCE PRIMARY KEY,
parent_id BIGINT,
_lft BIGINT NOT NULL,
_rgt BIGINT NOT NULL
...
)
可以将相册组织成一棵树并使用nested set approach。
假设我有两个整数p_lft 和p_rgt(如父左和父右)。最终,Eloquent Builder 应该构造一个查询,该查询应该在 SQL 层编译成类似这样的内容:
SELECT * FROM albums AS child
WHERE
p_lft < child._lft AND child._rgt < p_rgt
AND
NOT EXISTS (
SELECT * FROM albums AS inner
WHERE
p_lft < inner._lft AND inner._lft <= child._lft
AND
child._rgt <= inner._rgt AND inner._rgt < p_rgt
AND
(more conditions here)
);
实际问题出现在在程序中的两个不同点构建查询:
a) 查询的外部部分(即上面与SELECT * FROM albums AS child 对应的部分)
b) 内部子查询
特别是内部子查询应该写成一个通用函数——称为过滤函数——它可以应用于外部查询的任意实例。外部查询的唯一要求是它查询正确的模型,即Album。特别是,我无法控制外部查询,因此我不知道是否已经发生了任何别名。因此,我不知道如何从内部查询(即从过滤器内部)引用外部查询。
解决方案的尝试
这就是我走了多远
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Query\Builder as BaseBuilder;
function applyFilter(Builder $builder, int $p_lft, int $p_rgt): void {
// Ensure that the outer query queries for the right model
$model = $query->getModel();
if (!($model instanceof Album )) {
throw new \InvalidArgumentException();
}
// We must wrap everything into an outer query to avoid any undesired
// effects in case that the original query already contains an
// "OR"-clause.
$filter = function (Builder $query) use ($p_lft, $p_rgt) {
$query
->where('_lft', '>', $p_lft) // _lft corresponds to child._lft in SQL
->where('_rgt', '<', $p_rgt) // _rgt corresponds to child._rgt in SQL
->whereNotExists(function (BaseBuilder $subQuery) use ($p_lft, $p_rgt) {
$subQuery->from('albums')
->where('_lft', '>', p_lft) // here _lft corresponds to inner._lft in SQL
->where('_lft', '<=', ????) // how do I refer to the outer _lft here?
->where('_rgt', '>=', ????) // some question
->where('_rgt', '<', p_rgt) // here _rgt corresponds to inner._rgt in SQL
});
};
$builder->where($filter);
}
使用示例
在代码中的其他地方可能会像这样调用过滤器
applyFilter(
Albums::query()
->where(some_condition)
->orWhere(some_other_condition),
$left, $right
)->get();
或者像这样
Photo::query()
->where(some_condition)
->whereHas('album', fn(Builder $b) => applyFilter($b, $left, $right))
甚至像这样
Album::query()
->whereHas('parent', fn(Builder $b) => applyFilter($b, $left, $right))
请注意,最后一种情况特别棘手。过滤器函数在whereHas 内部使用,用于将Album 与其自身关联起来。因此,过滤器函数会针对已经将 album 别名为不同名称的查询调用。
【问题讨论】:
标签: php laravel eloquent subquery exists