Яркое вложенное отношение с некоторыми ограничениями

У меня следующие три таблицы:

A
-------------
| id | name |
-------------

B
--------------------
| id | A_id | name |
--------------------

C
--------------------
| id | B_id | name |
--------------------

Таким образом, данные в таблице C относятся к данным в таблице B, которые относятся к данным в таблице A. Теперь я хочу запросить C, а также получить данные из B и A, а следующий код очень трюк.

C::with('B.A')->get();

Теперь проблема заключается в том, что я хочу запросить C с некоторыми ограничениями. Одним из этих ограничений является id of A. Я пробовал следующее:

C::with(array('B.A' => function ($query)
{
    $query->where('id', '=', $constraint);
}))->get();

Но кажется, что Eloquent будет извлекать все строки в C, даже не принимая во внимание ограничение, кроме случаев, когда он выполняет запрос для извлечения данных в таблице A. Как мне обойти эту проблему? Мне нужно добавить другое поле в C, то есть A_id, и сопоставить $constraint с этим полем?

Ответ 1

Вы путаете метод with() с SQL JOIN, и это происходит очень часто.

Сначала немного фона

Когда вы используете Foo::with('bar')->where_something(1), Laravel сначала загрузит Foo, а затем, на основе Foo.bar_id, он загрузит Bar. Он служит для того, чтобы рассказывать Laravel о желаемых зависимостях загрузки вашей модели от комбинированного запроса, что значительно улучшает производительность итераций на этих моделях.

Если вы не используете его, должны быть выполнены следующие запросы:

SELECT * FROM foos WHERE foos.something = 1;
SELECT * FROM bars WHERE bars.id = 30;
SELECT * FROM bars WHERE bars.id = 57;
SELECT * FROM bars WHERE bars.id = 134;
SELECT * FROM bars WHERE bars.id = 1096;

Если вы используете его, с другой стороны:

SELECT * FROM foos WHERE foos.something = 1;
SELECT * FROM bars WHERE bars.id IN (30, 57, 134, 1096); // Eager loading

Когда вы добавляете условие к этому with(), вы ограничиваете только загрузку этих зависимостей, а не первый запрос.

Теперь к вашему ответу

Чтобы достичь того, чего вы хотите, вам нужно использовать ->join().

C::with(array('b', 'b.a'))
 ->join('b', 'b.id', '=', 'c.b_id')
 ->join('a', 'a.id', '=', 'b.a_id')
 ->where('a.id', '=', $ID)
 ->get('c.*');

Я включил with(), потому что не знал, нужен ли вам доступ к $c->b->a. Если вы этого не сделаете, и вам просто нужны данные $c, вы можете удалить with(), поскольку он будет запрашивать для B и A без необходимости.