如何通过 Eloquent 中具有多对多关系的外部表中的列聚合进行分组?
Posted
技术标签:
【中文标题】如何通过 Eloquent 中具有多对多关系的外部表中的列聚合进行分组?【英文标题】:How to group by aggregation of column in foreign table with many-to-many relation in Eloquent? 【发布时间】:2019-06-25 14:29:34 【问题描述】:我开始为此头疼,所以我想我只是在这里发布。 我有两个通过数据透视表关联的表(因为它是多对多关系)。我使用 Laravel 和 Eloquent(但也非常感谢有关如何使用普通 SQL 查询实现此目的的一般帮助)。 我想根据第二个表的列订购第一个表,但为此需要“聚合”列。
以由许多司机共享并且可以有不同颜色的汽车为例:
Car-Table: [id, color]
Driver-Table: [id, name]
Car.Driver-Table: [car_id, driver_id]
我需要一个查询来获取所有只驾驶红色汽车的司机,然后是所有不驾驶红色汽车的司机。 我必须使用查询,因为之后我可能会对此查询执行其他操作(如过滤),并希望最终分页。 我已经使用了获取两个组之一的查询。它们看起来像这样: 在 Driver 模型中:
public function redCars()
return $this->cars()->where('color', 'red');
public function otherColoredCars()
return $this->cars()->where('color', '<>', 'red');
然后在控制器的某个地方:
$driversWithOnlyRedCars = Driver::whereDoesntHave('otherColoredCars')->get();
$driversWithoutRedCars = Driver::whereDoesntHave('redCars')->get();
有没有办法将这两者结合起来? 也许我在这里的想法完全错误。
更新澄清: 基本上我需要这样的东西(或任何其他会导致相同结果的方式)
$driversWithOnlyRedCars->addTemporaryColumn('order_column', 0); // Create temporary column with value 0
$driversWithoutRedCars->addTemporaryColumn('order_column', 1);
$combinedQuery = $driversWithOnlyRedCars->combineWith($driversWithoutRedCars); // Somehow combine both queries
$orderedQuery = $combinedQuery->orderBy('order_colum');
$results = $combinedQuery->get();
更新 2 我想,我发现了如何通过原始查询接近我的目标。 会是这样的:
$a = DB::table(DB::raw("(
SELECT id, 0 as ordering
FROM drivers
WHERE EXISTS (
SELECT * FROM cars
LEFT JOIN driver_car ON car.id = driver_car.car_id
WHERE driver.id = driver_car.driver_id
AND cars.color = 'red'
)
) as only_red_cars"));
$b = DB::table(DB::raw("(
SELECT id, 1 as ordering
FROM drivers
WHERE EXISTS (
SELECT * FROM cars
LEFT JOIN driver_car ON car.id = driver_car.car_id
WHERE driver.id = driver_car.driver_id
AND cars.color <> 'red'
)
) as no_red_cars"));
$orderedQuery = $a->union($b)->orderBy('ordering');
现在的问题是我需要像这样订购并最终分页的模型,所以这并不是我问题的真正答案。我试图将其转换回模型,但我还没有成功。我尝试了什么:
$queriedIds = array_column($orderedQuery->get()->toArray(), 'id');
$orderedModels = Driver::orderByRaw('(FIND_IN_SET(drivers.id, "' . implode(',', $queriedIds) . '"))');
但看起来FIND_IN_SET
只允许将表的列作为第二个参数。是否有另一种方法可以从有序联合查询中以正确的顺序获取模型?
【问题讨论】:
情侣问题;whereDoesntHave()
需要 2 个参数、一个关系名称和其他查询逻辑。如果你不想传递额外的逻辑,那就是doesntHave()
。其次,我认为它不适用于otherColoredCars
,因为这不完全是一种关系。您可以使用 whereDoesntHave('cars', function($subQuery) ... );
并将 ...
替换为仅省略/包含红色汽车等的逻辑。
嗯,这实际上不可行,除非我写下这个小例子时犯了一个错误。这就是我在我的项目中所做的,我从中得到了想要的结果。
您的意思是UNION
查询吗?
@JonasStaudenmeir 我刚刚发现了如何为此使用联合,现在当我回来为我的问题添加更新时看到了你的评论:) 也许你可以看看。
【参考方案1】:
您可以使用UNION
查询:
$driversWithOnlyRedCars = Driver::select('*', DB::raw('0 as ordering'))
->whereDoesntHave('otherColoredCars');
$driversWithoutRedCars = Driver::select('*', DB::raw('1 as ordering'))
->whereDoesntHave('redCars');
$drivers = $driversWithOnlyRedCars->union($driversWithoutRedCars)
->orderBy('ordering')
->orderBy('') // TODO
->paginate();
您希望如何订购具有相同 ordering
的驱动程序?您应该在每次执行查询时添加第二个ORDER BY
子句以获得一致的顺序。
【讨论】:
这看起来和我一直在寻找但想不出的东西一模一样。我会尽快尝试,如果有效,我会将其标记为答案。谢谢【参考方案2】:这是我得到的最好的:
$driversWithOnlyRedCars = Driver::whereHas('cars',function($q)
$q->where('color', 'red');
)->withCount('cars')->get()->where('cars_count',1);
【讨论】:
我不确定,但我可能会尝试一下(也许再次^^)。但如果这行得通,为什么这能确保只有红色汽车的司机排在其他人之前呢?而且我想我以后不能再做所有其他可能的查询了? (比如当所有司机拥有相同的“汽车状态”时,按姓名对他们进行排序?) 哦,对不起,我读错了你如何命名结果。据我所知,这不会让只驾驶红色汽车的司机,而是所有驾驶至少一辆红色汽车(可能还有其他任何一辆)的司机。请参阅文档中 whereHas 的第一个示例:laravel.com/docs/5.8/…。此外,查询所有只有红色汽车的司机并不能解决我的问题,因为我想要一个列表,其中所有只有红色汽车的司机首先列出,所有其他司机列在他们之后。 感谢您的帮助,也许我可以以某种方式使用它,但这并不能解决我的问题。这只是我已经以另一种方式做的事情的另一种方式(让所有司机只驾驶红色汽车)。我将编辑我的问题,以更清楚地说明我需要什么。以上是关于如何通过 Eloquent 中具有多对多关系的外部表中的列聚合进行分组?的主要内容,如果未能解决你的问题,请参考以下文章
Laravel Eloquent 根据数据透视表字段条件获取多对多关系
如何在 Eloquent ORM 中按多对多关系的数据透视表的字段进行排序