Как сортировать по полю сводной таблицы отношения многие ко многим в Eloquent ORM

Я использую Eloquent ORM в течение некоторого времени и знаю это достаточно хорошо, но я не могу сделать следующее, а это очень просто сделать в Fluent.

У меня есть пользователи с песнями "многие-ко-многим", промежуточная таблица - song_user (как и должно быть). Хотелось бы получить топовые песни пользователя по количеству воспроизведений. Конечно, количество воспроизведений хранится в промежуточной таблице.

Я могу сделать это на Fluent:

$songs = DB::table('songs')
    ->join('song_user', 'songs.id', '=', 'song_user.song_id')
    ->where('song_user.user_id', '=', $user->id)
    ->orderBy("song_user.play_count", "desc")
    ->get();

Легкий. Но я хочу сделать это в Eloquent, что, конечно, не работает:

$songs = Song::
    with(array("song_user" => function($query) use ($user) {
        $query->where("user_id", "=", $user->id)->orderBy("play_count", "desc");
    }))

person duality_    schedule 13.01.2013    source источник
comment
У меня нет настроек, чтобы попробовать это, но вы пробовали что-то вроде $songs = $usr->songs()->pivot()->order_by('play_count','desc')->songs();? Вопрос в том, сможете ли вы вернуться к songs после сводной таблицы, поскольку это не похоже на нормальную модель.   -  person dualed    schedule 14.01.2013
comment
К сожалению, это не работает.   -  person duality_    schedule 15.01.2013
comment
Что ж, вы всегда можете сделать сводную таблицу моделью и поработать над этим. Вам нужно будет только настроить дополнительные внешние ключи, чтобы записи правильно удалялись, когда их родительский элемент удален.   -  person dualed    schedule 15.01.2013
comment
В настоящее время я делаю это, но при таком подходе вы теряете автоматическую фильтрацию песен, предназначенных только для пользователей ($ user- ›songs ()), и вам нужно помнить, что нужно делать это вручную (SongUser::where("user_id", "=", $user->id)->etc...).   -  person duality_    schedule 15.01.2013
comment
Почему именно для этого нужны отношения, вместо одного отношения «многие ко многим» вы создаете два отношения «один ко многим» со сводной таблицей. Вы бы просто добавили что-то вроде $this->has_many('song_user')   -  person dualed    schedule 15.01.2013


Ответы (5)


Не уверен, планируете ли вы перейти на Laravel 4, но вот пример Eloquent для сортировки по полям сводных таблиц:

public function songs() {
  return $this
    ->belongsToMany('Song')
    ->withPivot('play_count')
    ->orderBy('pivot_play_count', 'desc');
}

withPivot похож на красноречивый with и добавит поле play_count из сводной таблицы к другим уже включенным ключам. Все поля сводной таблицы имеют префикс pivot в результатах, поэтому вы можете ссылаться на них непосредственно в orderBy.

Я понятия не имею, как это будет выглядеть в Laravel 3, но, возможно, это поможет указать вам в правильном направлении.

Ваше здоровье!

person BigBlueHat    schedule 12.06.2013
comment
Я не знал, что смогу вот так придерживаться порядка в модели. Вы заслуживаете как минимум +1 за это! - person Relaxing In Cyprus; 12.03.2014
comment
Не могли бы вы указать, упоминается ли в документации префикс сводных полей? - person Walid Ammar; 22.07.2015
comment
Работает в Laravel 5.2 без необходимости добавлять префиксы к полям сводной таблицы. - person kerrin; 20.09.2016

Я только что нашел кое-что в руководстве пользователя, по-видимому, вам нужен with() метод.

Из Руководства пользователя:

По умолчанию будут возвращены только определенные поля из сводной таблицы (два поля идентификатора и временные метки). Если ваша сводная таблица содержит дополнительные столбцы, вы также можете получить их, используя метод with ():

class User extends Eloquent {
  public function roles()
  {
    return $this->has_many_and_belongs_to('Role', 'user_roles')->with('column');
  }
}

Таким образом, вы можете использовать что-то подобное при определении ваших отношений:

$this->has_many_and_belongs_to('User')->with('playcount');

Пример

Я просто использовал это, чтобы убедиться, что это работает ...

class Song extends Eloquent {
    function users()
    {
        return $this->has_many_and_belongs_to('User')->with('playcount');
    }
}

class User extends Eloquent {
    function songs()
    {
        return $this->has_many_and_belongs_to('Song')->with('playcount');
    }
}

// My test method
class TestOrm extends PHPUnit_Framework_TestCase {
    public function testSomethingIsTrue()
    {
        foreach(User::find(3)->songs()->order_by('playcount')->get() as $song)
            echo $song->name, ': ', $song->pivot->playcount, "\n";
        echo "\n";
        foreach(User::find(3)->songs()->order_by('playcount','desc')->get() as $song)
            echo $song->name, ': ', $song->pivot->playcount, "\n";
    }
}

Выход

Jingle Bells: 5
Mary had a little lamb: 10
Soft Kitty: 20
The Catalyst: 100

The Catalyst: 100
Soft Kitty: 20
Mary had a little lamb: 10
Jingle Bells: 5

Примечание. Неслучайно без использования order_by() результат будет отсортирован в ascending порядке playcount. Я подтвердил это посредством тестирования (поскольку я еще не знаю, как отображать запросы в модульных тестах), но вам, вероятно, не следует полагаться на такое поведение.

person dualed    schedule 15.01.2013

Любой метод, доступный в Fluent, также должен быть доступен в Eloquent. Возможно, это то, что вы ищете?

$songs = Song->join('song_user', 'songs.id', '=', 'song_user.song_id')
->where('song_user.user_id', '=', $user->id)
->orderBy("song_user.play_count", "desc")
->get();
person Travis Bennett    schedule 13.01.2013
comment
Нет, я ищу более красноречивый способ сделать это, посмотрите мой пример, который не работает. - person duality_; 13.01.2013
comment
Ответ, который я опубликовал, делает пользователь Eloquent. Он вернет массив объектов Eloquent (в данном случае песен), принадлежащих пользователю и упорядоченных через поле play_count (из таблицы соединения). Это также можно сделать напрямую из объекта пользователя, песни которого вы пытаетесь найти $ user- ›songs () -› order_by ('play_count', 'asc') - ›get (). Ваш неудачный пример не имеет никакого смысла: нетерпеливая загрузка сводной таблицы song_user - это не то же самое, что создание соединения, и в этом случае у вас действительно нет причин для нетерпеливой загрузки. Простое соединение должно выполнить то, что вам нужно. - person Travis Bennett; 13.01.2013

Я делал это (на нескольких сборках), просто используя метод отношений, как и вы. Я часто использую столбец «порядок» в сводной таблице, а затем делаю что-то вроде этого.

$article->tags()->order_by( 'order')->get();

Это может быть неоднозначным, если в объединяемой таблице есть столбцы с именем «порядок». В таком случае вам нужно будет указать -> order_by ('article_tag.order'). И да, вам нужно использовать -> with (), чтобы получить этот столбец в наборе результатов. Из соображений стиля я бы оставил with () вне метода отношений и вместо этого просто вернул бы ванильный объект отношения.

person Collin James    schedule 30.03.2013

В вашей модели Eloquent вы можете связать столбец orderBy, если включите имя таблицы:

return $this->belongsToMany('App\Post')->withTimestamps()->orderByDesc('posts.created_at');
person Josh LaMar    schedule 26.09.2017