从Yii2中的联结表中检索数据

时间:2015-02-15 12:16:02

标签: php mysql activerecord yii2

我正在尝试从Yii2中的连接表中获取数据而无需其他查询。我有2个模型(用户,组)通过联结表(user_group)关联。在user_group表中,我想存储此关系的额外数据(admin flag,...)。

  • 将数据添加到联结表的最佳方法是什么?链接方法接受参数extraColumns但我无法弄清楚它是如何工作的。

  • 检索此数据的最佳方法是什么?我写了一个额外的查询来从结表中获取值。必须有一个更清洁的方法来做到这一点?!

仅供参考,这是我在模型中定义关系的方式:

Group.php

public function getUsers() {
    return $this->hasMany(User::className(), ['id' => 'user_id'])
        ->viaTable('user_group', ['group_id' => 'id']);
}

user.php的

public function getGroups() {
    return $this->hasMany(Group::className(), ['id' => 'group_id'])
        ->viaTable('user_group', ['user_id' => 'id']);
}

4 个答案:

答案 0 :(得分:8)

简而言之:使用ActiveRecord建议的联结表是恕我直言,因为您可以设置via()来使用现有的ActiveRecord }。这允许您使用Yii的link()方法在联结表中创建项目,同时添加数据(如管理标志)。

官方Yii指南2.0说明了两种使用联结表的方法:使用viaTable()并使用via()(请参阅here)。虽然前者期望联结表的名称作为参数,但后者需要将关系名称作为参数。

如果您需要访问联结表中的数据,我会按照您的建议使用ActiveRecord作为联结表,并使用via()

class User extends ActiveRecord
{
    public function getUserGroups() {
        // one-to-many
        return $this->hasMany(UserGroup::className(), ['user_id' => 'id']);
    }
}

class Group extends ActiveRecord
{
    public function getUserGroups() {
        // one-to-many
        return $this->hasMany(UserGroup::className(), ['group_id' => 'id']);
    }

    public function getUsers()
    {
        // many-to-many: uses userGroups relation above which uses an ActiveRecord class
        return $this->hasMany(User::className(), ['id' => 'user_id'])
            ->via('userGroups');
    }
}

class UserGroup extends ActiveRecord
{
    public function getUser() {
        // one-to-one
        return $this->hasOne(User::className(), ['id' => 'user_id']);
    }

    public function getGroup() {
        // one-to-one
        return $this->hasOne(Group::className(), ['id' => 'userh_id']);
    }
}

通过这种方式,您可以使用userGroups关系获取联结表的数据而无需其他查询(与任何其他一对多关系一样):

$group = Group::find()->where(['id' => $id])->with('userGroups.user')->one();
// --> 3 queries: find group, find user_group, find user
// $group->userGroups contains data of the junction table, for example:
$isAdmin = $group->userGroups[0]->adminFlag
// and the user is also fetched:
$userName = $group->userGroups[0]->user->name

这一切都可以使用hasMany关系来完成。因此,您可能会问为什么要使用via()声明多对多关系:因为您可以使用Yii的link()方法在联结表中创建项目:

$userGroup = new UserGroup();
// load data from form into $userGroup and validate
if ($userGroup->load(Yii::$app->request->post()) && $userGroup->validate()) {
    // all data in $userGroup is valid
    // --> create item in junction table incl. additional data
    $group->link('users', $user, $userGroup->getDirtyAttributes())
}

答案 1 :(得分:3)

由于我差不多14天没有得到答案,我将发布如何解决这个问题。这并不是我想到的,但它有效,现在已经足够了。所以...这就是我所做的:

  1. 为联结表
  2. 添加了 UserGroup 模型
  3. 添加了与

    的关系
    public function getUserGroups()
    {
        return $this->hasMany(UserGroup::className(), ['user_id' => 'id']);
    }
    
  4. 在我的搜索模型函数中加入UserGroup

    $query = Group::find()->where('id =' . $id)->with('users')->with('userGroups');
    
  5. 这让我得到了我想要的东西,群组包含所有用户,并由我的新模型 UserGroup 代表,联结表中的数据。

    我考虑过首先扩展构建Yii2函数的查询 - 这可能是解决此问题的更好方法。但由于我还不太了解Yii2,我现在决定不这样做。

    如果您有更好的解决方案,请与我们联系。

答案 2 :(得分:3)

我不确定这是最好的解决方案。但是对于我的项目来说,现在好了:)

1)左连接

&模型User中添加新的类属性。 在您的基本关系中添加两行,但不要删除public $flag;这可以(并且应该)保留。

viaTable

public function getUsers() { return $this->hasMany(User::className(), ['id' => 'user_id']) ->viaTable('user_group', ['group_id' => 'id']) ->leftJoin('user_group', '{{user}}.id=user_id') ->select('{{user}}.*, flag') //or all ->select('*'); } 可以从联结表和leftJoin中选择数据来自定义您的返回列。 请注意,select必须保留,因为link()依赖它。

2)子选择查询

viaTable模型User

中添加新的类属性

public $flag;模型修改后的Group关系:

getUsers()

如您所见,我添加了默认选择列表的子选择。此选择适用于不是组模型的用户。是的,我同意这有点丑陋,但做的工作。

3)条件关系

不同的选择是仅为管理员创建一个以上的关系:

public function getUsers()
{
    return $this->hasMany(User::className(), ['id' => 'user_id'])
        ->viaTable('user_group', ['group_id' => 'id'])
        ->select('*, (SELECT flag FROM user_group WHERE group_id='.$this->id.' AND user_id=user.id LIMIT 1) as flag');
}

// Select all users public function getUsers() { .. } // Select only admins (users with specific flag ) public function getAdmins() { return $this->hasMany(User::className(), ['id' => 'user_id']) ->viaTable('user_group', ['group_id' => 'id'], function($q){ return $q->andWhere([ 'flag' => 'ADMIN' ]); }); } - 获取具有特定管理员标记的用户。但是此解决方案不会添加属性$Group->admins。您需要知道何时只选择管理员和所有用户。缺点:您需要为每个标志值创建单独的关系。

使用单独模型$flag的解决方案仍然更灵活,适用于所有情况。就像你可以添加验证和基本的ActiveRecord东西。这些解决方案更多的是单向方向 - 将东西拿出来。

答案 3 :(得分:1)

为此,我创建了一个简单的扩展,允许将联结表中的列作为属性附加到子模型中。 因此,在设置此扩展后,您将能够访问连接表属性,如

foreach ($parentModel->relatedModels as $childModel)
{
    $childModel->junction_table_column1;
    $childModel->junction_table_column2;
    ....
}

欲了解更多信息,请查看 Yii2 junction table attributes extension

感谢。