2013-12-04 2 views
1

Предположим, у нас есть 3 таблицы, как это:Yii реляционная модель один-к-новейший

--------- --------------- ------------------- 
| USER | | USER_STATUS | | STATUS_DICT  | 
|-------| |-------------- |------------------ 
| id | | id   | | id (int)  | 
| name | | user_id  | | status (string) | 
| email | | status_id | ------------------- 
--------- | timestamp | 
      --------------- 

Каждый user имеет много статусов (user_status), связанные с user.id <-> user_status.user_id. Каждый user_status имеет одну связанную запись в status_dict, которая является строковой меткой статуса int (например, 0 = активна).

Что я хочу, так это сделать модель пользователя для получения статуса последнего пользователя с использованием отношения. Установить отношения:

/* User model */ 
public function relations() { 
    return array(
     'status' => array(self::HAS_ONE, 'UserStatus', 'user_id', 'order'=>'status.id DESC'), 
    ); 
} 

и

/* UserStatus model */ 
public function relations() { 
    return array(
     'status' => array(self::BELONGS_TO, 'StatusDictionary', 'status_id'), 
     'user' => array(self::BELONGS_TO, 'User', 'user_id'), 
    ); 
} 

Тогда, когда я звоню User::model()->findByPk(1)->status я последний статус для пользователя # 1.

НО.

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

Что я должен сделать, чтобы сделать отношение одним из самых новых (я называю это так для цели этого случая)? Я видел this article, и он выполняет эту работу, но мне интересно, есть ли способ достичь этого с помощью соглашений Yii.

Я хотел объявить названный объем, как это:

/* in User model */ 
public function withStatus($status) { 
    if(!is_numeric($status) && !is_null($status)) { 
     $status = StatusDictionary::model()->find('lower(name)=lower(:name)', array(':name' => $status))->getAttribute('id'); 
    } 

    $this->getDbCriteria()->mergeWith(array(
     'condition' => (is_null($status) ? 'status.status_id IS NULL' : 'status.status_id=:statusId'), 
     'params' => array(':statusId' => $status), 
     'with' => array('status') 
    )); 

    return $this; 
} 

Но это не работает либо, User::model()->withStatus(0)->findAll() возвращает все пользователи с соответствующей записью в user_status с user_status.status_id = 0, даже если некоторые из пользователей имеют новый, другой статус.

Заранее благодарим за любую помощь.

ответ

1

Хорошо, я нашел решение с помощью TheRifler со своего поста. Оказалось, что у меня есть решение прямо под моим носом (статья связана в вопросе).

связь должна быть определена, как показано ниже:

/* User model */ 
public function relations() { 
    return array(
     'status' => array(self::HAS_ONE, 'UserStatus', 'user_id', 
      'order' => 's.id DESC', 
      'alias' => 's', 
      // http://murrayhopkins.wordpress.com/2008/10/28/mysql-left-join-on-last-or-first-record-in-the-right-table/ 
      'join' => 'LEFT JOIN (SELECT s1.* 
         FROM `user_status` as s1 
         LEFT JOIN `user_status` AS s2 
         ON s1.user_id = s2.user_id AND s1.id < s2.id 
         WHERE s2.user_id IS NULL) as `status` 
         ON (s.user_id = `status`.user_id)' 
      ), 
    ); 
} 

и именованная:

public function withStatus($status) { 
    if(!is_numeric($status) && !is_null($status)) { 
     $status = StatusDictionary::model()->find('lower(name)=lower(:name)', array(':name' => $status))->getAttribute('id'); 
    } 

    $this->with('status'); 
    if(!is_null($status)) { 
     $this->getDbCriteria()->compare('status.status_id', $status); 
    } 
    else { 
     $this->getDbCriteria()->addCondition('s.status_id IS NULL'); 
    } 

    return $this; 
} 

Он содержит некоторую грязную работу, потому что withStatus() использует status объединения данных подзапроса, если $status указан или база s псевдоним, если $status является нулевым (это потому, что мы ищем пользователя без записи в таблице status). Возможно, это не самое элегантное решение, но работает так, как должно.

Теперь я могу использовать:

  • User::model()->findByPk(1)->status для доступа Статус указанного пользователя
  • User::model()->withStatus('active')->findAll() список пользователей с указанным статусом (статус может быть передан как целое [user_status.id] или строка [status_dict.name])
  • User::model()->withStatus(null)->count() получить количество пользователей без какого-либо статуса
1

я нашел где-то подобное решение:

Scope функции:

public function withStatus($status) { 
    if(!is_numeric($status) && !is_null($status)) { 
     $status = StatusDictionary::model()->find('lower(name)=lower(:name)', array(':name' => $status))->getAttribute('id'); 
    } 

    $this->with('status'); 
    $this->getDbCriteria()->compare('i.statusId', $status); 

    return $this; 
} 

Relations запись (это соединение защищает соотношение от критериев фильтра):

'status' => array(self::HAS_ONE, 'Status', 'clientId', 
       'join' => 'INNER JOIN (select max(id) id from status group by clientId) j ON i.id=j.id', 
       'alias' => 'i' 
      ), 

Это работает для меня.

+0

Это почти хорошее решение - почти, потому что оно не возвращать пользователей без какого-либо статуса. Однако ваше решение подтолкнуло меня к тому, чтобы что-то проверить, и я нашел, как и должно быть (я отправлю ответ). – Wirone

Смежные вопросы