Yii模型关系一对一

时间:2013-12-04 09:10:43

标签: orm yii model

假设我们有3个这样的表:

---------    ---------------    -------------------
| 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_statusstatus_dict中都有一条相关记录,它是int status的字符串标签(例如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用户的最新状态。

BUT。

当我想查找具有指定状态的所有用户时,它无法正常工作。我没有获得具有指定状态的用户(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_statususer_status.status_id = 0与{{1}}相关记录的所有用户,即使某些用户具有较新的不同状态。

提前感谢您的帮助。

2 个答案:

答案 0 :(得分:1)

我找到了其他类似的解决方案:

范围功能:

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;
}

关系记录(此联接是保护与过滤条件的关系):

'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'
            ),

它对我有用。

答案 1 :(得分: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()获取没有任何状态的用户数