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


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

  • 将数据添加到联结表的最佳方法是什么? link方法接受参数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']);
}

7118
2018-02-15 12:16


起源



答案:


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


官方Yii指南2.0说明了两种使用联结表的方法:使用 viaTable() 和使用 via() (看到 这里)。虽然前者期望联结表的名称作为参数,但后者期望关系名称作为参数。

如果您需要访问联结表中的数据,我会使用 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())
}

7
2017-09-01 20:43



你好@Biggie,如何在form.php中添加$ form->字段 $group = Group::find()->where(['id' => $id])->with('userGroups.user')->one();, 请回复 - Vinaya Maheshwari


答案:


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


官方Yii指南2.0说明了两种使用联结表的方法:使用 viaTable() 和使用 via() (看到 这里)。虽然前者期望联结表的名称作为参数,但后者期望关系名称作为参数。

如果您需要访问联结表中的数据,我会使用 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())
}

7
2017-09-01 20:43



你好@Biggie,如何在form.php中添加$ form->字段 $group = Group::find()->where(['id' => $id])->with('userGroups.user')->one();, 请回复 - Vinaya Maheshwari


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

  1. 添加了一个模型 用户组 用于连接表
  2. 添加了关系

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

    $query = Group::find()->where('id =' . $id)->with('users')->with('userGroups');
    

这就是我想要的东西  所有 用户 并且,由我的新模型代表 用户组,来自联结表的数据。

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

如果您有更好的解决方案,请告诉我。 


3
2018-02-28 16:54





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

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 自定义返回列。 记住这一点 viaTable必须留下来因为 链接() 依靠它。

2)子选择查询

添加新的类属性 User 模型 public $flag;

并在 Group 模型修改 getUsers() 关系:

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

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

3)条件关系

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

// 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。您需要知道何时只选择管理员和所有用户。缺点:您需要为每个标志值创建单独的关系。


使用单独模型的解决方案 UserGroup 对于所有情况,仍然更加灵活和普遍。就像你可以添加验证和基本的ActiveRecord东西。这些解决方案更多的是单向方向 - 将问题排除在外。


3
2017-10-02 13:34





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

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

欲了解更多信息,请查看 Yii2联结表属性扩展

谢谢。


1
2017-09-15 17:58