mirror of
https://github.com/yiisoft/yii.git
synced 2026-03-06 08:14:21 +01:00
731 lines
44 KiB
Plaintext
731 lines
44 KiB
Plaintext
Реляционная Active Record
|
||
=========================
|
||
|
||
Мы уже рассмотрели использование Active Record (AR) для выбора данных из одной таблицы базы данных.
|
||
В этом разделе мы расскажем, как использовать AR для соединения нескольких связанных таблиц и получения
|
||
набора связанных данных.
|
||
|
||
Перед использованием реляционной AR рекомендуется установить ограничения внешних ключей для таблиц базы данных.
|
||
Это позволит обеспечить непротиворечивость и целостность хранимых данных.
|
||
|
||
Для наглядности примеров в этом разделе мы будем использовать схему базы данных,
|
||
представленную на следующей диаграмме сущность-связь (ER).
|
||
|
||

|
||
|
||
> Info|Информация: Поддержка ограничений внешних ключей различается в разных СУБД.
|
||
> SQLite 3.6.19 и более ранние версии не поддерживает ограничений, но вы, тем не менее, можете их объявить
|
||
> при создании таблиц. Движок MySQL MyISAM не поддерживает внешние ключи.
|
||
|
||
Установка связей между AR-классами
|
||
----------------
|
||
|
||
Перед тем как использовать AR для выполнения реляционных запросов, нам необходимо установить связи между AR-классами.
|
||
|
||
Связь между двумя AR-классами напрямую зависит от связей между соответствующими таблицами базы данных. С точки
|
||
зрения БД, связь между таблицами A и В может быть трёх типов: один-ко-многим (например, `tbl_user` и `tbl_post`), один-к-одному
|
||
(например, `tbl_user` и `tbl_profile`) и многие-ко-многим (например, `tbl_category` и `tbl_post`). В AR существует четыре типа связей:
|
||
|
||
- `BELONGS_TO`: если связь между А и В один-ко-многим, значит В принадлежит А (например, `Post` принадлежит `User`);
|
||
|
||
- `HAS_MANY`: если связь между таблицами А и В один-ко-многим, значит у А есть много В (например, у `User` есть много `Post`);
|
||
|
||
- `HAS_ONE`: это частный случай `HAS_MANY`, где А может иметь максимум одно В (например, у `User` есть только один `Profile`);
|
||
|
||
- `MANY_MANY`: эта связь соответствует типу связи многие-ко-многим в БД. Поскольку многие СУБД не поддерживают непосредственно
|
||
этот тип связи, требуется ассоциативная таблица для преобразования связи многие-ко-многим в связи один-ко-многим.
|
||
В нашей схеме базы данных этой цели служит таблица `tbl_post_category`. В терминологии AR связь `MANY_MANY` можно описать как
|
||
комбинацию `BELONGS_TO` и `HAS_MANY`. Например, `Post` принадлежит многим `Category`, а у `Category` есть много `Post`.
|
||
|
||
Установка связей производится внутри метода [relations()|CActiveRecord::relations] класса [CActiveRecord].
|
||
Этот метод возвращает массив с конфигурацией связей. Каждый элемент массива представляет одну связь в следующем формате:
|
||
|
||
~~~
|
||
[php]
|
||
'VarName'=>array('RelationType', 'ClassName', 'ForeignKey', …дополнительные параметры)
|
||
~~~
|
||
|
||
где `VarName` — имя связи, `RelationType` указывает на один из четырёх типов связей,
|
||
`ClassName` — имя AR-класса, связанного с данным классом, а
|
||
`ForeignKey` обозначает один или несколько внешних ключей, используемых для связи.
|
||
Кроме того, можно указать ряд дополнительных параметров, о которых будет рассказано позже.
|
||
|
||
В приведённом ниже коде показано, как установить связь между классами `User` и `Post`.
|
||
|
||
~~~
|
||
[php]
|
||
class Post extends CActiveRecord
|
||
{
|
||
…
|
||
public function relations()
|
||
{
|
||
return array(
|
||
'author'=>array(self::BELONGS_TO, 'User', 'author_id'),
|
||
'categories'=>array(self::MANY_MANY, 'Category',
|
||
'tbl_post_category(post_id, category_id)'),
|
||
);
|
||
}
|
||
}
|
||
|
||
class User extends CActiveRecord
|
||
{
|
||
…
|
||
public function relations()
|
||
{
|
||
return array(
|
||
'posts'=>array(self::HAS_MANY, 'Post', 'author_id'),
|
||
'profile'=>array(self::HAS_ONE, 'Profile', 'owner_id'),
|
||
);
|
||
}
|
||
}
|
||
~~~
|
||
|
||
> Info|Информация: Внешний ключ может быть составным, то есть состоять из двух и более столбцов таблицы. В этом случае
|
||
имена столбцов следует разделить запятыми и передать их либо в качестве строки, либо в виде массива `array('key1','key2')`.
|
||
Задать свою связь первичного ключа с внешним можно в виде массива `array('fk'=>'pk')`. Для составных
|
||
ключей это будет `array('fk_c1'=>'pk_c1','fk_c2'=>'pk_c2')`. Для типа связи `MANY_MANY` имя ассоциативной таблицы также должно быть
|
||
указано во внешнем ключе. Например, связи `categories` модели `Post` соответствует внешний ключ `tbl_post_category(post_id, category_id)`.
|
||
Для каждой добавленной связи неявно создаётся свойство класса. После выполнения реляционного запроса
|
||
соответствующее свойство будет содержать связанный экземпляр класса AR (или массив экземпляров для связей типа один-ко-многим и
|
||
многие-ко-многим). Например, если `$author` является экземпляром AR-класса `User`, то можно использовать `$author->posts`
|
||
для доступа к связанным экземплярам `Post`.
|
||
|
||
Выполнение реляционного запроса
|
||
-------------------------------
|
||
|
||
Самый простой способ выполнить реляционный запрос — использовать реляционное свойство AR-класса.
|
||
Если обращение к этому свойству производится впервые, то будет выполнен реляционный запрос, который соединит связанные таблицы
|
||
и оставит только данные, соответствующие первичному ключу текущего экземпляра AR.
|
||
Результат запроса будет сохранён в свойстве как экземпляр (или массив экземпляров) связанного класса.
|
||
Этот подход также известен как «отложенная загрузка» (lazy loading), при которой непосредственный запрос выполняется
|
||
только в момент первого обращения к связанным объектам. Ниже приведён пример использования этого подхода:
|
||
|
||
~~~
|
||
[php]
|
||
// получаем запись с ID=10
|
||
$post=Post::model()->findByPk(10);
|
||
// Получаем автора записи. Здесь будет выполнен реляционный запрос.
|
||
$author=$post->author;
|
||
~~~
|
||
|
||
> Info|Информация: Если связанные данные не найдены, то соответствующее
|
||
свойство примет значение null для связей `BELONGS_TO` и `HAS_ONE` или будет являться пустым массивом
|
||
для `HAS_MANY` и `MANY_MANY`.
|
||
Стоит отметить, что связи `HAS_MANY` и `MANY_MANY` возвращают
|
||
массивы объектов, и обращаться к их свойствам необходимо в цикле, иначе
|
||
можно получить ошибку «Trying to get property of non-object».
|
||
|
||
Способ отложенной загрузки удобен, но не всегда эффективен. Например, если нам потребуется
|
||
получить информацию об авторах `N` записей, то использование отложенной загрузки
|
||
потребует выполнения `N` дополнительных запросов к базе данных.
|
||
В данной ситуации разумно использовать метод «жадной загрузки» (eager loading).
|
||
|
||
Этот метод заключается в загрузке всех связанных данных вместе с
|
||
основным экземпляром AR. Реализуется этот подход путем использования метода
|
||
[with()|CActiveRecord::with] вместе с методом [find|CActiveRecord::find] или
|
||
[findAll|CActiveRecord::findAll].
|
||
Например:
|
||
~~~
|
||
[php]
|
||
$posts=Post::model()->with('author')->findAll();
|
||
~~~
|
||
|
||
Приведённый код вернёт массив экземпляров `Post`. В отличие от отложенной загрузки, свойство `author` каждой записи будет
|
||
заполнено связанным экземпляром `User` ещё до обращения к этому свойству. Таким образом, вместо выполнения отдельного запроса
|
||
для каждой записи, жадная загрузка получит все записи вместе с их авторами в одном запросе!
|
||
|
||
В методе [with()|CActiveRecord::with] можно указать несколько связей, и жадная загрузка вернёт их за один раз.
|
||
Например, следующий код вернёт записи вместе с их авторами и категориями:
|
||
|
||
~~~
|
||
[php]
|
||
$posts=Post::model()->with('author','categories')->findAll();
|
||
~~~
|
||
|
||
Кроме того, можно осуществлять вложенную жадную загрузку. Для этого вместо простого списка имён связей, мы передаем методу
|
||
[with()|CActiveRecord::with] имена связей, упорядоченных иерархически, как в следующем примере:
|
||
|
||
~~~
|
||
[php]
|
||
$posts=Post::model()->with(
|
||
'author.profile',
|
||
'author.posts',
|
||
'categories')->findAll();
|
||
~~~
|
||
|
||
Пример выше вернёт нам все записи вместе с их авторами и категориями, а также профиль каждого автора и все его записи.
|
||
|
||
Жадная загрузка может быть выполнена путём указания свойства [CDbCriteria::with]:
|
||
|
||
~~~
|
||
[php]
|
||
$criteria=new CDbCriteria;
|
||
$criteria->with=array(
|
||
'author.profile',
|
||
'author.posts',
|
||
'categories',
|
||
);
|
||
$posts=Post::model()->findAll($criteria);
|
||
~~~
|
||
|
||
или
|
||
|
||
~~~
|
||
[php]
|
||
$posts=Post::model()->findAll(array(
|
||
'with'=>array(
|
||
'author.profile',
|
||
'author.posts',
|
||
'categories',
|
||
)
|
||
));
|
||
~~~
|
||
|
||
|
||
Реляционный запрос без получения связанных моделей
|
||
--------------------------------------------------
|
||
|
||
Иногда требуется выполнить запрос с использованием связи, но при этом
|
||
не требуются данные из связанной модели. Допустим, есть пользователи (`User`),
|
||
которые публикуют множество записей (`Post`). Запись может быть опубликована,
|
||
а может быть черновиком. Этот факт определяется значением поля `published` модели `Post`.
|
||
Пусть нам необходимо получить всех пользователей, которые опубликовали хотя бы одну
|
||
запись, при этом сами записи нам не интересны. Сделать это можно следующим образом:
|
||
|
||
~~~
|
||
[php]
|
||
$users=User::model()->with(array(
|
||
'posts'=>array(
|
||
// записи нам не нужны
|
||
'select'=>false,
|
||
// но нужно выбрать только пользователей с опубликованными записями
|
||
'joinType'=>'INNER JOIN',
|
||
'condition'=>'posts.published=1',
|
||
),
|
||
))->findAll();
|
||
~~~
|
||
|
||
|
||
Параметры реляционного запроса
|
||
------------------------------
|
||
|
||
Выше мы упоминали о том, что в реляционном запросе можно указать дополнительные параметры.
|
||
Эти параметры — пары имя-значение — используются для тонкой настройки реляционного запроса.
|
||
Список параметров представлен ниже.
|
||
|
||
- `select`: список выбираемых полей для связанного AR-класса. По умолчанию значение параметра равно '*',
|
||
что соответствует всем полям таблицы. Для используемых столбцов должны быть разрешены конфликты имён.
|
||
|
||
- `condition`: соответствует SQL оператору `WHERE`, по умолчанию значение параметра пустое.
|
||
Для используемых столбцов должны быть разрешены конфликты имён.
|
||
|
||
- `params`: параметры для связывания в генерируемом SQL-выражении. Параметры передаются как массив пар имя-значение.
|
||
|
||
- `on`: соответствует SQL оператору `ON`. Условие, указанное в этом параметре,
|
||
будет добавлено к основному условию соединения при помощи SQL оператора `AND`. Для используемых столбцов должны быть разрешены конфликты имён.
|
||
Данный параметр неприменим для связей типа `MANY_MANY`.
|
||
|
||
- `order`: соответствует SQL оператору `ORDER BY`, по умолчанию значение параметра пустое.
|
||
Для используемых столбцов должны быть разрешены конфликты имён.
|
||
|
||
- `with`: список дочерних связанных объектов, которые должны быть загружены с самим объектом.
|
||
Неправильное использование данной возможности может привести к бесконечному циклу.
|
||
|
||
- `joinType`: тип соединения таблиц. По умолчанию значение параметра равно `LEFT
|
||
OUTER JOIN`;
|
||
|
||
- `alias`: псевдоним таблицы, ассоциированной со связью. По умолчанию значение параметра
|
||
равняется null, что означает, что псевдоним соответствует имени связи.
|
||
|
||
- `together`: параметр, устанавливающий необходимость принудительного соединения таблицы, ассоциированной с этой связью,
|
||
с другими таблицами. Этот параметр имеет смысл только для связей типов `HAS_MANY` и `MANY_MANY`. Если параметр не установлен или
|
||
равен false, тогда каждая связь `HAS_MANY` или `MANY_MANY` будет использовать отдельный SQL-запрос для связанных данных,
|
||
что может улучшить скорость выполнения запроса, т.к. уменьшается количество выбираемых данных.
|
||
Если параметр равен `true`, то зависимая таблица при выполнении запроса всегда будет
|
||
соединяться с основной, то есть будет выполнен один SQL-запрос даже в том случае, если
|
||
к основной таблице применяется постраничная разбивка. Если данный параметр не
|
||
задан, зависимая таблица будет соединена с основной только в случае, когда
|
||
к основной таблице не применяется постраничная разбивка. Более подробное описание
|
||
можно найти в разделе «производительность реляционного запроса».
|
||
|
||
- `join`: дополнительный оператор `JOIN`. По умолчанию пуст. Этот параметр
|
||
доступен с версии 1.1.3.
|
||
|
||
- `group`: соответствует SQL оператору `GROUP BY`, по умолчанию значение параметра пустое.
|
||
Для используемых столбцов должны быть разрешены конфликты имён.
|
||
|
||
- `having`: соответствует SQL оператору `HAVING`, по умолчанию значение параметра пустое.
|
||
Для используемых столбцов должны быть разрешены конфликты имён.
|
||
|
||
- `index`: имя столбца таблицы, значения которого должны быть использованы в
|
||
качестве ключей массива, хранящего связанные объекты. Без установки этого
|
||
параметра массив связанных объектов использует целочисленный индекс,
|
||
начинающийся с нуля. Параметр может быть установлен только для связей типа
|
||
`HAS_MANY` и `MANY_MANY`.
|
||
|
||
- `scopes`: группы условий, которые необходимо применить. В случае одной группы
|
||
может задаваться в виде строки `'scopes'=>'scopeName'`. Если же групп несколько, то
|
||
их необходимо перечислить в массиве `'scopes'=>array('scopeName1','scopeName2')`. Этот параметр
|
||
доступен с версии 1.1.9.
|
||
|
||
Кроме того, для отложенной загрузки некоторых типов связей доступен ряд дополнительных параметров:
|
||
|
||
- `limit`: параметр для ограничения количества строк в выборке. Параметр неприменим для связей `BELONGS_TO`;
|
||
|
||
- `offset`: параметр для указания начальной строки выборки. Параметр неприменим для связей `BELONGS_TO`.
|
||
|
||
- `through`: имя связи модели, которое при получении данных будет
|
||
использоваться как мост. Параметр может быть установлен только для связей
|
||
`HAS_ONE` и `HAS_MANY`. Этот параметр доступен с версии 1.1.7.
|
||
|
||
Ниже мы изменим определение связи `posts` в модели `User`, добавив несколько вышеприведенных параметров:
|
||
|
||
~~~
|
||
[php]
|
||
class User extends CActiveRecord
|
||
{
|
||
public function relations()
|
||
{
|
||
return array(
|
||
'posts'=>array(self::HAS_MANY, 'Post', 'author_id',
|
||
'order'=>'posts.create_time DESC',
|
||
'with'=>'categories'),
|
||
'profile'=>array(self::HAS_ONE, 'Profile', 'owner_id'),
|
||
);
|
||
}
|
||
}
|
||
~~~
|
||
|
||
Теперь при обращении к `$author->posts`, мы получим записи автора, отсортированные
|
||
в обратном порядке по времени их создания. Для каждой записи будут загружены
|
||
её категории.
|
||
|
||
Устранение конфликта имён столбцов
|
||
----------------------------------
|
||
|
||
При совпадении имён столбцов в двух и более соединяемых таблицах,
|
||
приходится разрешать конфликт имён. Это делается при помощи добавления
|
||
псевдонима таблицы к имени столбца.
|
||
|
||
В реляционном запросе псевдоним главной таблицы всегда равен `t`,
|
||
а псевдоним связанной таблицы по умолчанию равен имени связи.
|
||
В приведённом ниже коде псевдонимы таблиц для моделей `Post` и `Comment` будут соответственно
|
||
`t` и `comments`:
|
||
|
||
~~~
|
||
[php]
|
||
$posts=Post::model()->with('comments')->findAll();
|
||
~~~
|
||
|
||
Допустим, что и в `Post`, и в `Comment` есть столбец `create_time`, в котором
|
||
хранится время создания записи или комментария, и нам необходимо получить записи
|
||
вместе с комментариями к ним, отсортированные сначала по времени создания
|
||
записи, а затем по времени написания комментария. Для этого нам понадобится
|
||
устранить конфликт столбцов `create_time` следующим образом:
|
||
|
||
~~~
|
||
[php]
|
||
$posts=Post::model()->with('comments')->findAll(array(
|
||
'order'=>'t.create_time, comments.create_time'
|
||
));
|
||
~~~
|
||
|
||
Динамические параметры реляционного запроса
|
||
-------------------------------------------
|
||
|
||
Мы можем использовать динамические параметры как для метода
|
||
[with()|CActiveRecord::with], так и для параметра `with`. Динамические параметры переопределяют существующие
|
||
параметры в соответствии с описанием метода [relations()|CActiveRecord::relations]. К примеру, если для модели `User`, приведённой выше,
|
||
мы хотим воспользоваться жадной загрузкой для получения записей автора в порядке возрастания (параметр `order` в определении связи
|
||
задает убывающий порядок), можно сделать это следующим образом:
|
||
|
||
~~~
|
||
[php]
|
||
User::model()->with(array(
|
||
'posts'=>array('order'=>'posts.create_time ASC'),
|
||
'profile',
|
||
))->findAll();
|
||
~~~
|
||
|
||
Динамические параметры в реляционных запросах можно использовать вместе с
|
||
отложенной загрузкой. Для этого необходимо вызвать метод с тем же именем, что и
|
||
имя связи, и передать параметры в качестве аргументов. К примеру, следующий код
|
||
вернёт публикации пользователя, у которых `status` равен 1:
|
||
|
||
~~~
|
||
[php]
|
||
$user=User::model()->findByPk(1);
|
||
$posts=$user->posts(array('condition'=>'status=1'));
|
||
~~~
|
||
|
||
Производительность реляционного запроса
|
||
---------------------------------------
|
||
|
||
Как было сказано выше, жадная загрузка используется, главным образом, когда
|
||
требуется получить множество связанных объектов. В этом случае при соединении
|
||
всех таблиц генерируется сложный SQL-запрос. Такой запрос во многих случаях
|
||
является предпочтительным, т.к. упрощает фильтрацию по значению столбца связанной таблицы.
|
||
Тем не менее, в некоторых случаях такие запросы не являются эффективными.
|
||
|
||
Рассмотрим пример, в котором нам необходимо найти последние добавленные записи вместе с их комментариями.
|
||
Предположив, что у каждой записи 10 комментариев, при использовании одного большого SQL-запроса
|
||
мы получим множество лишних данных, так как каждая запись будет повторно выбираться с каждым
|
||
её комментарием. Теперь попробуем по-другому: сначала выберем последние записи, а затем комментарии к ним.
|
||
В этом случае нам необходимо выполнить два SQL-запроса. Плюс в том, что полученные данные
|
||
не будут содержать дублирующую информацию.
|
||
|
||
Какой же подход более эффективен? Однозначного ответа на этот вопрос нет.
|
||
Выполнение одного большого SQL-запроса может оказаться более эффективным, так как СУБД
|
||
не приходится лишний раз разбирать и выполнять дополнительные запросы.
|
||
С другой стороны, используя один SQL-запрос, мы получаем лишние данные, а значит нам требуется больше времени на их передачу и обработку.
|
||
По умолчанию Yii использует жадную загрузку, то есть генерирует один SQL-запрос
|
||
за исключением случая, когда к главной модели применяется `LIMIT`. Если выставить
|
||
опцию `together` в описании связи в `true`, то мы получим единственный SQL-запрос
|
||
даже если используется `LIMIT`. Если использовать `false`, то выборка из
|
||
некоторых таблиц будет производиться отдельными запросами.
|
||
Например, для того чтобы использовать отдельные SQL-запросы для выборки
|
||
последних записей и комментариев к ним, связь `comments` модели `Post` следует
|
||
описать следующим образом:
|
||
|
||
~~~
|
||
[php]
|
||
public function relations()
|
||
{
|
||
return array(
|
||
'comments' => array(self::HAS_MANY, 'Comment', 'post_id', 'together'=>false),
|
||
);
|
||
}
|
||
~~~
|
||
|
||
Для жадной загрузки мы можем задать этот параметр динамически:
|
||
|
||
~~~
|
||
[php]
|
||
$posts = Post::model()->with(array('comments'=>array('together'=>false)))->findAll();
|
||
~~~
|
||
|
||
Статистический запрос
|
||
-----------------
|
||
|
||
Помимо реляционных запросов, описанных выше, Yii также поддерживает так называемые статистические запросы (или запросы агрегирования).
|
||
Этот тип запросов используется для получения агрегированных данных, относящихся к связанным объектам (количество комментариев
|
||
к каждой записи, средний рейтинг для каждого наименования продукции и т.д.).
|
||
Статистические запросы могут быть использованы только для связей типа `HAS_MANY` (например, у записи есть много
|
||
комментариев) или `MANY_MANY` (например, запись принадлежит многим категориям, а категориия может относиться ко множеству записей).
|
||
|
||
Выполнение статистического запроса аналогично выполнению реляционного запроса. Первым делом необходимо
|
||
объявить статистический запрос в методе [relations()|CActiveRecord::relations] класса [CActiveRecord].
|
||
|
||
~~~
|
||
[php]
|
||
class Post extends CActiveRecord
|
||
{
|
||
public function relations()
|
||
{
|
||
return array(
|
||
'commentCount'=>array(self::STAT, 'Comment', 'post_id'),
|
||
'categoryCount'=>array(self::STAT, 'Category', 'post_category(post_id, category_id)'),
|
||
);
|
||
}
|
||
}
|
||
~~~
|
||
|
||
Выше мы объявили два статистических запроса: `commentCount` подсчитывает количество комментариев к записи, а `categoryCount`
|
||
считает количество категорий, к которым относится запись. Обратите внимание, что связь между `Post` и `Comment` — типа `HAS_MANY`, а
|
||
связь между `Post` и `Category` — типа `MANY_MANY` (с использованием преобразующей таблицы `post_category`). Как можно видеть,
|
||
способ объявления похож на объявление связей, описанных выше. Единственное различие состоит в том, что в данном случае тип связи
|
||
равен `STAT`.
|
||
|
||
За счёт объявленных связей мы можем получить количество комментариев к записи, используя выражение `$post->commentCount`.
|
||
В момент первого обращения к данному свойству для получения соответствующего результата неявным образом выполняется SQL-запрос.
|
||
Как мы уже говорили, это называется подходом *отложенной загрузки*. Можно также использовать *жадный* вариант загрузки, если необходимо
|
||
получить количество комментариев к нескольким записям:
|
||
|
||
~~~
|
||
[php]
|
||
$posts=Post::model()->with('commentCount', 'categoryCount')->findAll();
|
||
~~~
|
||
|
||
Выражение выше выполняет три SQL-запроса для получения всех записей вместе с количеством комментариев к ним и числом категорий.
|
||
В случае отложенной загрузки нам бы понадобилось выполнить `2*N+1` SQL-запросов для `N` записей.
|
||
|
||
По умолчанию статистический запрос считает количество с использованием выражения `COUNT`.
|
||
Его можно уточнить путём указания дополнительных параметров в момент объявления в методе [relations()|CActiveRecord::relations].
|
||
Доступные параметры перечислены ниже:
|
||
|
||
- `select`: статистическое выражение, по умолчанию равно `COUNT(*)`, что соответствует количеству связанных объектов;
|
||
|
||
- `defaultValue`: значение, которое присваивается в случае, если результат статистического запроса пуст.
|
||
Например, если запись не имеет ни одного комментария, то свойству `commentCount` будет присвоено это значение. По умолчанию значение
|
||
данного параметра равно 0;
|
||
|
||
- `condition`: соответствует SQL оператору `WHERE`, по умолчанию значение параметра пустое;
|
||
|
||
- `params`: параметры для связывания в генерируемом SQL-выражении. Параметры передаются
|
||
как массив пар имя-значение;
|
||
|
||
- `order`: соответствует SQL оператору `ORDER BY`, по умолчанию значение параметра пустое;
|
||
|
||
- `group`: соответствует SQL оператору `GROUP BY`, по умолчанию значение параметра пустое;
|
||
|
||
- `having`: соответствует SQL оператору `HAVING`, по умолчанию значение параметра пустое.
|
||
|
||
Реляционные запросы с именованными группами условий
|
||
---------------------------------------------------
|
||
|
||
В реляционном запросе [именованные группы условий](/doc/guide/database.ar#named-scopes)
|
||
могут быть использованы двумя способами. Их можно применить к основной модели и
|
||
к связанным моделям.
|
||
|
||
Следующий код иллюстрирует случай их применения к основной модели:
|
||
|
||
~~~
|
||
[php]
|
||
$posts=Post::model()->published()->recently()->with('comments')->findAll();
|
||
~~~
|
||
|
||
Данный код очень похож на нереляционный запрос. Единственное отличие состоит в том, что
|
||
присутствует вызов `with()` после вызовов групп условий. Данный запрос
|
||
вернёт недавно опубликованные записи вместе с комментариями к ним.
|
||
|
||
В следующем примере показано, как применить группы условий к связанным моделям:
|
||
|
||
~~~
|
||
[php]
|
||
$posts=Post::model()->with('comments:recently:approved')->findAll();
|
||
// или, начиная с версии 1.1.7
|
||
$posts=Post::model()->with(array(
|
||
'comments'=>array(
|
||
'scopes'=>array('recently','approved')
|
||
),
|
||
))->findAll();
|
||
// или, начиная с версии 1.1.7
|
||
$posts=Post::model()->findAll(array(
|
||
'with'=>array(
|
||
'comments'=>array(
|
||
'scopes'=>array('recently','approved')
|
||
),
|
||
),
|
||
));
|
||
~~~
|
||
|
||
Этот запрос вернёт все записи вместе с одобренными комментариями. Здесь `comments`
|
||
соответствует имени связи. `recently` и `approved` — именованные группы, описанные
|
||
в модели `Comment`. Имя связи и группы условий разделяются двоеточием.
|
||
|
||
Вам может понадобится использовать вместо жадной выборку отложенную для
|
||
связи с группой условий. Синтаксис для этого такой:
|
||
|
||
~~~
|
||
[php]
|
||
// имя связи comments повторяется два раза
|
||
$approvedComments = $post->comments('comments:approved');
|
||
~~~
|
||
|
||
|
||
Именованные группы могут быть использованы при описании связей модели в
|
||
методе [CActiveRecord::relations()] в параметре `with`. В следующем примере
|
||
при обращении к `$user->posts` вместе с публикациями будут получены все
|
||
*одобренные* комментарии.
|
||
|
||
~~~
|
||
[php]
|
||
class User extends CActiveRecord
|
||
{
|
||
public function relations()
|
||
{
|
||
return array(
|
||
'posts'=>array(self::HAS_MANY, 'Post', 'author_id',
|
||
'with'=>'comments:approved'),
|
||
);
|
||
}
|
||
}
|
||
// или, начиная с версии 1.1.7
|
||
class User extends CActiveRecord
|
||
{
|
||
public function relations()
|
||
{
|
||
return array(
|
||
'posts'=>array(self::HAS_MANY, 'Post', 'author_id',
|
||
'with'=>array(
|
||
'comments'=>array(
|
||
'scopes'=>'approved'
|
||
),
|
||
),
|
||
),
|
||
);
|
||
}
|
||
}
|
||
~~~
|
||
|
||
|
||
В версии 1.1.7 появилась возможность передавать параметры именованным группам условий
|
||
связи. К примеру, если в `Post` есть именованная группа условий `rated`,
|
||
принимающая минимальный рейтинг записи, использовать её в `User` можно следующим образом:
|
||
|
||
|
||
> Note|Примечание: до версии 1.1.7 именованные группы условий, применяемые к реляционным моделям,
|
||
> должны быть описаны в CActiveRecord::scopes. Поэтому они не могут быть параметризованы.
|
||
|
||
|
||
~~~
|
||
[php]
|
||
$users=User::model()->findAll(array(
|
||
'with'=>array(
|
||
'posts'=>array(
|
||
'scopes'=>array(
|
||
'rated'=>5,
|
||
),
|
||
),
|
||
),
|
||
));
|
||
|
||
class Post extends CActiveRecord
|
||
{
|
||
......
|
||
|
||
public function rated($rating)
|
||
{
|
||
$this->getDbCriteria()->mergeWith(array(
|
||
'condition'=>'rating=:rating',
|
||
'params'=>array(':rating'=>$rating),
|
||
));
|
||
return $this;
|
||
}
|
||
|
||
......
|
||
}
|
||
~~~
|
||
|
||
Реляционные запросы с through
|
||
-----------------------------
|
||
|
||
При использовании `through` определение связи должно выглядеть следующим образом:
|
||
|
||
~~~
|
||
[php]
|
||
'comments'=>array(self::HAS_MANY,'Comment',array('key1'=>'key2'),'through'=>'posts'),
|
||
~~~
|
||
|
||
В коде выше, а именно в `array('key1'=>'key2')`:
|
||
|
||
- `key1` — ключ, определённый в связи, на которую указывает `through` (в нашем случае `posts`).
|
||
- `key2` — ключ, определённый в модели, на которую указывает связь (в нашем случае `Comment`).
|
||
|
||
`through` может использоваться как с `HAS_ONE`, так и с `HAS_MANY`.
|
||
|
||
### `HAS_MANY` through
|
||
|
||

|
||
|
||
Пример использования `HAS_MANY` с `through` — получение пользователей, состоящих
|
||
в определённой группе, если они записаны в группу через роли.
|
||
|
||
Более сложным примером является получение всех комментариев для всех пользователей
|
||
определённой группы. В этом случае необходимо использовать несколько связей
|
||
с `through` в одной модели:
|
||
|
||
~~~
|
||
[php]
|
||
class Group extends CActiveRecord
|
||
{
|
||
...
|
||
public function relations()
|
||
{
|
||
return array(
|
||
'roles'=>array(self::HAS_MANY,'Role','group_id'),
|
||
'users'=>array(self::HAS_MANY,'User',array('user_id'=>'id'),'through'=>'roles'),
|
||
'comments'=>array(self::HAS_MANY,'Comment',array('id'=>'user_id'),'through'=>'users'),
|
||
);
|
||
}
|
||
}
|
||
~~~
|
||
|
||
#### Примеры
|
||
|
||
~~~
|
||
[php]
|
||
// получаем все группы с соответствующими им пользователями
|
||
$groups=Group::model()->with('users')->findAll();
|
||
|
||
// получаем все группы с соответствующими им пользователями и ролями
|
||
$groups=Group::model()->with('roles','users')->findAll();
|
||
|
||
// получаем всех пользователей и роли для группы с ID, равным 1
|
||
$group=Group::model()->findByPk(1);
|
||
$users=$group->users;
|
||
$roles=$group->roles;
|
||
|
||
// получаем все комментарии для группы с ID, равным 1
|
||
$group=Group::model()->findByPk(1);
|
||
$comments=$group->comments;
|
||
~~~
|
||
|
||
|
||
### `HAS_ONE` through
|
||
|
||

|
||
|
||
Пример использования `HAS_ONE` с `through` — получение адреса пользователя в
|
||
случае, если пользователь связан с адресом через профиль. Все задействованные
|
||
сущности (пользователь, профиль и адрес) имеют соответствующие им модели:
|
||
|
||
~~~
|
||
[php]
|
||
class User extends CActiveRecord
|
||
{
|
||
...
|
||
public function relations()
|
||
{
|
||
return array(
|
||
'profile'=>array(self::HAS_ONE,'Profile','user_id'),
|
||
'address'=>array(self::HAS_ONE,'Address',array('id'=>'profile_id'),'through'=>'profile'),
|
||
);
|
||
}
|
||
}
|
||
~~~
|
||
|
||
#### Примеры
|
||
|
||
~~~
|
||
[php]
|
||
// получаем адрес пользователя с ID, равным 1
|
||
$user=User::model()->findByPk(1);
|
||
$address=$user->address;
|
||
~~~
|
||
|
||
|
||
### through с собой
|
||
|
||
`through` можно использовать для модели, связанной с собой через мост. В нашем
|
||
случае это пользователь, обучающий других пользователей:
|
||
|
||
|
||

|
||
|
||
|
||
Связи для данного случая определяются следующим образом:
|
||
|
||
~~~
|
||
[php]
|
||
class User extends CActiveRecord
|
||
{
|
||
...
|
||
public function relations()
|
||
{
|
||
return array(
|
||
'mentorships'=>array(self::HAS_MANY,'Mentorship','teacher_id','joinType'=>'INNER JOIN'),
|
||
'students'=>array(self::HAS_MANY,'User',array('student_id'=>'id'),'through'=>'mentorships','joinType'=>'INNER JOIN'),
|
||
);
|
||
}
|
||
}
|
||
~~~
|
||
|
||
#### Примеры
|
||
|
||
~~~
|
||
[php]
|
||
// получаем всех студентов учителя с ID, равным 1
|
||
$teacher=User::model()->findByPk(1);
|
||
$students=$teacher->students;
|
||
~~~ |