<rmcreative>

RSS

Yii2: JOIN вернулся

8 января 2014

В Active Record Yii2 вернулся JOIN. Внутри всё значительно проще, чем было в Yii 1.1, но возможностей сильно больше.

Сразу скажу, что поддержка noSQL никуда не делась. Более того, можно в одном «запросе» выбрать JOIN-ом из нескольких SQL-таблиц, а часть связанных данных забрать, например, из MongoDB.

Отличный пример привёл ORey на англоязычном форуме:

// Выбираем MyModel с relation1 и relation2.
// Все три забираем запросом с JOIN.
$query = MyModel::find()
    ->joinWith(['relation1', 'relation2']);
 
$countQuery = clone $query;
$pages = new Pagination(['totalCount' => $countQuery->count(), 'pageSize' => 15]);
 
$items = $query
    ->orderBy($sort->orders)
    ->offset($pages->offset)
    ->limit($pages->limit)
    // Забираем дополнительно relation3 и relation4.
    // Фильтровать по ним нам не нужно, так что будут
    // запросы вида WHERE ID IN (1,2,3,4) или аналоги
    // для noSQL.
    ->with(['relation3', 'relation4'])
    ->all();

Официальная документация на тему

Комментарии RSS

  1. №8741
    Алексей
    Алексей 08 янв. 2014 г., 7:12:49

    Вот это правильно, намного больше контроля получается.

  2. №8743
    Spider
    Spider 09 янв. 2014 г., 1:27:20

    Что причиной тому?

  3. №8744
    Sam
    Sam 09 янв. 2014 г., 3:55:36

    Spider, причины две:

    1. Много просили.
    2. Мы нашли как сделать красиво.
  4. №8748
    Андрей
    Андрей 12 янв. 2014 г., 0:47:55

    Очень круто!

  5. №8872
    denis909
    denis909 13 марта 2014 г., 14:36:40

    Так даже более лучше, чем в первой версии!

  6. №8873
    Sam
    Sam 13 марта 2014 г., 15:36:37

    denis909, хочу RSS у тебя в блоге ;)

  7. №9016
    Андрей
    Андрей 18 мая 2014 г., 17:28:25

    Одного не понял в приведенном примере - зачем автор использует клон $query для определения $pages вместо того, чтобы использовать тот же объект?

  8. №9019
    Sam
    Sam 22 мая 2014 г., 18:16:03

    Андрей, один используется для запроса типа count, второй для запроса с offset и limit.

  9. №9020
    Андрей
    Андрей 22 мая 2014 г., 18:47:47

    А разве, если сделать так:

    $pages = new Pagination(['totalCount' => $query->count(), 'pageSize' => 15]);
     
    $items = $query
        ->orderBy($sort->orders)
        ->offset($pages->offset)
        ->limit($pages->limit)
        ->with(['relation3', 'relation4'])
        ->all();

    результат будет другой?

  10. №9021
    Sam
    Sam 23 мая 2014 г., 3:42:22

    В коде из master нет.

  11. №9073
    Эдуард
    Эдуард 23 июня 2014 г., 21:23:17

    Почему бы не сделать поддержку пагинации внутри? К примеру, как в laravel.

    $items = $query
      ->where('status', 1)
      ->orderBy('id', 'desc')
      ->paginate(15);

    Это бы сократило размеры кода)) Да и вообще, посмотрите на досуге Eloquent, в нем очень много полезного, что можно было бы внедрить в Yii.

  12. №9074
    Sam
    Sam 23 июня 2014 г., 22:38:31

    Эдуард, не очень понял, что сделает такой запрос. Разобьёт по 15 записей на страницу? Выберет 15 записей? С какой страницы?

  13. №9075
    Эдуард
    Эдуард 24 июня 2014 г., 23:17:41

    Запрос вернет 15 записей. Примерно внутри он может выглядеть

    public function paginate($perPage = 15, $page = null)
    {
        // определяем текущую страницу
        if (! $page) {
             $page = (isset($_REQUEST['page'])) ? $_REQUEST['page'] : 1;
        }
        $page = (int) $page * 1;
     
         // подсчитываем кол-во записей по текущему запросу
        $countRows =  $this->count(); 
     
        // создаем объект пагинации
        $this->pagination = new Pagination(['totalCount' => $countRows), 'pageSize' => $perPage, 'page' => $page]);
     
        // возвращаем результат
        return $this->offset($this->pagination->offset)
             ->limit($this->pagination->limit)
             ->all();
    }
     
    public function getPagination()
    {
        return $this->pagination;
    }
     
    // получаем в результат 10 записей
    $articles = Articles::find()
      ->where('status', 1)
      ->paginate(10);
     
    // выводим ссылки 
    echo $articles->getPagination()->getLinks();
     
    //получаем 20 пользователей 5ой страницы
    $users = User::find()
      ->paginate(20, 5);
     
    // выводим ссылки
    echo $users->getPagination()->getLinks();

    Или, как вариант, он может возвращать класс, какой-нибудь PaginationCollection вида

    function paginate($perPage, $page = null)
    {
        ....
       $pages = ....
       $result = ....
       return new PaginationCollection($result, $pages);
    }

    И уже в PaginationCollection реализовать получение ссылок, получение результата выборки через магические методы т.д.

  14. №9076
    Sam
    Sam 24 июня 2014 г., 23:43:28

    Выглядит как плохая идея. Модель не должна работать с запросом напрямую.

  15. №9077
    Эдуард
    Эдуард 24 июня 2014 г., 23:52:33

    Ну это я, как пример сделал. просто так намного удобнее было было пользоваться, чем отдельно писать два запроса + создавать пагинацию. Вроде не много, всего несколько лишних строк, но ведь можно их и сократить до одного метода. А как уже его реализовать, какой там будет слой абстракции и т.д. это совершенно другой вопрос, тут Вам виднее ))

  16. №9079
    Эдуард
    Эдуард 27 июня 2014 г., 15:42:22

    Александр, а как вам немного другая идея реализации? метод paginate в моделе выглядит так:

    public function paginate($perPage = 15, $page = null)
    {
        return new PaginationCollection($this, $perPage, $page);
    }

    А класс PaginationCollection работает с запросами. Вот весь код класса pastebin.com/J4WpeJdK, он имплементирует ArrayAccess и Iterator. Пользоваться можно, как и прежде

    $articles = Articles::find()
       ->where('user_id', 1)
       ->paginate(15);
     
    //перебор
    foreach ($articles as $article) {
           echo $article->title;
    }
     
    //вывод ссылок двумя способами
    echo $articles->getPagination()->getLinks();
     
    // через __invoke()
    echo $articles()->getLinks();
  17. №9080
    Sam
    Sam 28 июня 2014 г., 15:38:24

    Я не думаю, что работу с реквестом стоит тащить в модель.

  18. №9569
    Kak
    Kak 13 янв. 2015 г., 22:32:15

    Эдуард Мне кажется DataProvider ры будут профитние

    $provider = new ActiveDataProvider([
        'query' => Post::find(),
        'pagination' => [
            'pageSize' => 20,
        ],
    ]);
  19. №9932
    djagya
    djagya 10 авг. 2015 г., 17:21:39

    А что если MyModel и relation1 имеют одинаковые названия колонок? Например name. Тогда ведь при JOIN будет подгружено *, и name из relation1 перезапишет в массиве, что используется в populate(), name из MyModel. Верно?

  20. №10295
    denis909
    denis909 05 марта 2016 г., 11:57:07

    denis909, хочу RSS у тебя в блоге ;)

    ого, только что увидел, а всего пару лет прошло ) sam, сделаю на досуге!

  21. №10424
    kushchiro
    kushchiro 31 марта 2016 г., 12:38:09

    Большое подозрение что joinWith не имеет никакого смысла поскольку в запрос кострукция JOIN добавляется, но в SELECT выгребаются только поля первой таблицы, которая указанна в блоке FROM.

    А relations потом набираются в методе \yii\db\ActiveQuery.populate, отдельным запросом

    Подробнее описал на форуме yiiframework.ru/forum/viewtopic.php?f=19&t=36147

  22. №10425
    Sam
    Sam 31 марта 2016 г., 15:40:47

    Почему не имеет? Для фильтрации очень даже имеет.

  23. №10426
    kushchiro
    kushchiro 31 марта 2016 г., 15:51:40

    Sam, с помощью AR я могу получить данные из связанных таблиц одним запросом?

    Пока я для этого использую join из Query

  24. №10427
    Sam
    Sam 31 марта 2016 г., 18:54:54

    Нет.

  25. №11132
    Jane
    Jane 19 дек. 2017 г., 10:35:41

    $users = \app\models\ActiveRecord\User::find() ->select($fields) ->joinWith('announce', true) ->where(['user_id' => $this->user_id]) ->asArray()->all();

    так выводит поля из всех связанных таблиц

  1. Почта опубликована не будет.

  2. Можно использовать синтаксис Markdown или HTML.

  3. Введите ответ в поле. Щёлкните, чтобы получить другую задачу.