У меня есть две таблицы, скажем «users» и «users_actions», где «users_actions» имеет отношение hasMany с пользователями:
пользователи
id | name | surname | email...
действия
id | id_action | id_user | log | created_at
Модель Users.php
class Users {
public function action()
{
return $this->hasMany('Action', 'user_id')->orderBy('created_at', 'desc');
}
}
Теперь я хочу получить список из всех пользователи с их последние действия.
Я видел, что делает Users::with('action')->get();
может легко дать мне последнее действие, просто выбрав только первый результат отношения:
foreach ($users as $user) {
echo $user->action[0]->description;
}
но я хотел избежать этого, конечно, и просто выбрать только последнее действие для каждого пользователя.
Я пытался использовать ограничение, как
Users::with(['action' => function ($query) {
$query->orderBy('created_at', 'desc')
->limit(1);
}])
->get();
но это дает мне неверный результат, так как Laravel выполняет этот запрос:
SELECT * FROM users_actions WHERE user_id IN (1,2,3,4,5)
ORDER BY created_at
LIMIT 1
что, конечно, неправильно. Есть ли возможность получить это без выполнения запроса для каждой записи с помощью Eloquent?
Я делаю какую-то очевидную ошибку, которую не вижу? Я совершенно новичок в использовании Eloquent, и иногда отношения меня беспокоят.
Редактировать:
Часть из представительских целей, мне также нужна эта функция для поиск внутри отношения, скажем, например, я хочу искать пользователей, где LAST ACTION = ‘что-то’
Я пытался с помощью
$actions->whereHas('action', function($query) {
$query->where('id_action', 1);
});
но это дает мне ВСЕХ пользователей, у которых было действие = 1, и, поскольку это журнал, каждый прошел этот шаг.
Благодаря @berkayk похоже, что я решил первую часть своей проблемы, но все же я не могу искать в отношениях.
Actions::whereHas('latestAction', function($query) {
$query->where('id_action', 1);
});
все еще не выполняет правильный запрос, он генерирует что-то вроде:
select * from `users` where
(select count(*)
from `users_action`
where `users_action`.`user_id` = `users`.`id`
and `id_action` in ('1')
) >= 1
order by `created_at` desc
Мне нужно получить запись, где самый последний Действие 1
Мое решение, связанное с @berbayk — это круто, если вы хотите легко получить последнюю версию hasMany
родственная модель.
Тем не менее, он не может решить другую часть того, что вы просите, так как запрос этого отношения с where
предложение приведет к тому же самому, что вы уже испытали — все строки будут возвращены, только latest
на самом деле не будет последним (но последним, соответствующим ограничению where).
Итак, поехали:
легкий путь — получить все и отфильтровать коллекцию:
User::has('actions')->with('latestAction')->get()->filter(function ($user) {
return $user->latestAction->id_action == 1;
});
или трудный путь — сделать это в sql (предполагая MySQL):
User::whereHas('actions', function ($q) {
// where id = (..subquery..)
$q->where('id', function ($q) {
$q->from('actions as sub')
->selectRaw('max(id)')
->whereRaw('actions.user_id = sub.user_id');
})->where('id_action', 1);
})->with('latestAction')->get();
Выберите одно из этих решений, сравнив производительность. вернуть все строки и отфильтровать, возможно, большую коллекцию.
Последний побежит подзапрос (whereHas
) с вложенным подзапросом (where('id', function () {..}
), поэтому оба пути могут быть потенциально медленными на большом столе.
Я думаю, что решение, которое вы запрашиваете, объясняется здесь http://softonsofa.com/tweaking-eloquent-relations-how-to-get-latest-related-model/
Определите это отношение в пользовательской модели,
public function latestAction()
{
return $this->hasOne('Action')->latest();
}
И получить результаты с
User::with('latestAction')->get();
Я создал пакет для этого: https://github.com/staudenmeir/eloquent-eager-limit
Использовать HasEagerLimit
черта в родительской и связанной модели.
class User extends Model {
use \Staudenmeir\EloquentEagerLimit\HasEagerLimit;
}
class Action extends Model {
use \Staudenmeir\EloquentEagerLimit\HasEagerLimit;
}
Тогда вы можете подать заявку ->limit(1)
к вашим отношениям, и вы получите последнее действие для каждого пользователя.
Давайте немного изменим код @ berkayk.
Определите это отношение в Users
модель,
public function latestAction()
{
return $this->hasOne('Action')->latest();
}
А также
Users::with(['latestAction' => function ($query) {
$query->where('id_action', 1);
}])->get();
Laravel использует функцию take (), а не Limit
Попробуйте приведенный ниже код, я надеюсь, что он работает нормально для вас
Users::with(['action' => function ($query) {
$query->orderBy('created_at', 'desc')->take(1);
}])->get();
или просто добавьте метод take в ваши отношения, как показано ниже
return $this->hasMany('Action', 'user_id')->orderBy('created_at', 'desc')->take(1);
Чтобы загрузить последние связанные данные для каждого пользователя, вы можете получить их, используя метод самостоятельного соединения в таблице действий, что-то вроде
select u.*, a.*
from users u
join actions a on u.id = a.user_id
left join actions a1 on a.user_id = a1.user_id
and a.created_at < a1.created_at
where a1.user_id is null
a.id_action = 1 // id_action filter on related latest record
Чтобы сделать это с помощью конструктора запросов, вы можете написать это как
DB::table('users as u')
->select('u.*', 'a.*')
->join('actions as a', 'u.id', '=', 'a.user_id')
->leftJoin('actions as a1', function ($join) {
$join->on('a.user_id', '=', 'a1.user_id')
->whereRaw(DB::raw('a.created_at < a1.created_at'));
})
->whereNull('a1.user_id')
->where('aid_action', 1) // id_action filter on related latest record
->get();
Чтобы стремиться к последнему отношению для пользователя, вы можете определить его как hasOne
отношение к вашей модели, как
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\DB;
class User extends Model
{
public function latest_action()
{
return $this->hasOne(\App\Models\Action::class, 'user_id')
->leftJoin('actions as a1', function ($join) {
$join->on('actions.user_id', '=', 'a1.user_id')
->whereRaw(DB::raw('actions.created_at < a1.created_at'));
})->whereNull('a1.user_id')
->select('actions.*');
}
}
Нет необходимости в зависимом подзапросе, просто примените обычный фильтр внутри whereHas
User::with('latest_action')
->whereHas('latest_action', function ($query) {
$query->where('id_action', 1);
})
->get();