简体   繁体   English

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

[英]Retrieve data from junction table in Yii2

I'm trying to get data from a join table in Yii2 without an additional query. 我正在尝试从Yii2中的连接表中获取数据,而无需额外的查询。 I have 2 models (User, Group) associated via the junction table (user_group). 我有2个模型(用户,组)通过联结表(user_group)关联。 In the user_group table, I want to store extra data (admin flag, ...) for this relation. 在user_group表中,我想为此关系存储额外数据(admin flag,...)。

  • What's the best way to add data to the junction table? 将数据添加到联结表的最佳方法是什么? The link method accepts a parameter extraColumns but I can't figure out how this works. link方法接受参数extraColumns但我无法弄清楚它是如何工作的。

  • What's the best way to retrieve this data? 检索此数据的最佳方法是什么? I wrote an additional query to get the values out of the junction table. 我写了一个额外的查询来从连接表中获取值。 There must be a cleaner way to do this?! 必须有一个更清洁的方法来做到这一点?!

FYI, this is how I defined the relation in the models: 仅供参考,这是我在模型中定义关系的方式:

Group.php Group.php

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

User.php user.php的

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

In short : Using an ActiveRecord for the junction table like you suggested is IMHO the right way because you can set up via() to use that existing ActiveRecord . 简而言之 :像你建议的那样使用ActiveRecord作为联结表是恕我直言,因为你可以设置via()来使用现有的ActiveRecord This allows you to use Yii's link() method to create items in the junction table while adding data (like your admin flag) at the same time. 这允许您使用Yii的link()方法在连接表中创建项目,同时添加数据(如管理标志)。


The official Yii Guide 2.0 states two ways of using a junction table: using viaTable() and using via() (see here ). 官方的Yii Guide 2.0说明了两种使用联结表的方法:使用viaTable()和使用via() (见这里 )。 While the former expects the name of the junction table as parameter the latter expects a relation name as parameter. 虽然前者期望联结表的名称作为参数,但后者期望关系名称作为参数。

If you need access to the data inside the junction table I would use an ActiveRecord for the junction table as you suggested and use 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']);
    }
}

This way you can get the data of the junction table without additional queries using the userGroups relation (like with any other one-to-many relation): 这样,您可以使用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

This all can be done using the hasMany relation. 这一切都可以使用hasMany关系完成。 So you may ask why you should declare the many-to-many relation using via() : Because you can use Yii's link() method to create items in the junction table: 所以你可能会问为什么你应该使用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())
}

Since I have received no answer for almost 14 days, I'll post how I solved this problem. 由于我差不多14天没收到答案,我会发布我是如何解决这个问题的。 This is not exactly what I had in mind but it works, that's enough for now. 这并不是我想到的,但它有效,现在已足够了。 So... this is what I did: 所以...这就是我做的:

  1. Added a model UserGroup for the junction table 为联结表添加了一个模型UserGroup
  2. Added a relation to Group 添加了与Group的关系

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

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

This get's me what I wanted, the Group with all Users and, represented by my new model UserGroup , the data from the junction table. 这就是我想要的,包含所有用户 ,以及我的新模型UserGroup代表的联结表中的数据。

I thought about extending the query building Yii2 function first - this might be a better way to solve this. 我想首先扩展构建Yii2函数的查询 - 这可能是解决此问题的更好方法。 But since I don't know Yii2 very well yet, I decided not to do for now. 但由于我还不太了解Yii2,我现在决定不这样做。

Please let me know if you have a better solution. 如果您有更好的解决方案,请告诉我。

I don't know for sure it is best solution. 我不确定这是最好的解决方案。 But for my project it will be good for now :) 但对于我的项目,它现在将是好的:)

1) Left join 1)左连接

Add new class attribute in User model public $flag; User model public $flag;添加新的class属性public $flag; . Append two lines to your basic relation but don't remove viaTable this can (and should) stay. 在您的基本关系中附加两行但不删除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 makes possible to select data from junction table and with select to customize your return columns. leftJoin可以从联结表中选择数据,并select自定义返回列。 Remember that viaTable must stay because link() relies on it. 请记住, viaTable必须保留,因为link()依赖于它。

2) sub-select query 2)子选择查询

Add new class attribute in User model public $flag; User model public $flag;添加新的class属性public $flag;

And in Group model modified getUsers() relation: 并在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');
}

As you can see i added sub-select for default select list. 如您所见,我添加了默认选择列表的子选择。 This select is for users not group model. 此选择适用于不是组模型的用户。 Yes, i agree this is litle bit ugly but does the job. 是的,我同意这有点丑陋,但做的工作。

3) Condition relations 3)条件关系

Different option is to create one more relation for admins only: 不同的选择是仅为管理员创建一个以上的关系:

// 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 - get users with specific admin flag. $Group->admins - 获取具有特定管理标志的用户。 But this solution doesn't add attribute $flag . 但是这个解决方案不会添加属性$flag You need to know when you select only admins and when all users. 您需要知道何时只选择管理员和所有用户。 Downside: you need to create separate relation for every flag value. 缺点:您需要为每个标志值创建单独的关系。


Your solution with using separate model UserGroup still is more flexible and universal for all cases. 使用单独模型UserGroup解决方案仍然更灵活,适用于所有情况。 Like you can add validation and basic ActiveRecord stuff. 就像你可以添加验证和基本的ActiveRecord东西。 These solutions are more one way direction - to get stuff out. 这些解决方案更多的是单向方向 - 将问题排除在外。

For that purpose I've created a simple extension, that allows to attach columns in junction table to child model in relation as properties. 为此,我创建了一个简单的扩展,它允许将联结表中的列作为属性附加到子模型中。 So after setting up this extension you will be able to access junction table attributes like 因此,在设置此扩展后,您将能够访问联结表属性,如

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

For more info please have look at Yii2 junction table attributes extension 有关更多信息,请查看Yii2联结表属性扩展

Thanks. 谢谢。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM