Yii2通过联结表急切加载聚合

时间:2016-09-28 09:08:29

标签: mysql activerecord yii2

我有以下表格:

  • 内容 - id (PK),title, ...其他字段......
  • content_category - content_id (FK到内容),category_id (FK到内容)

一段内容 has_many 类别,而某个类别也是一段内容。

在内容中,我有以下代码:

rv.notifyDataSetChanged();

对于后端的网格视图,我想为每个内容显示逗号分隔的类别列表。

我知道我可以单独选择这些信息,但我想将其作为查询查询的一部分并尽可能使用现有关系。

2 个答案:

答案 0 :(得分:1)

使用定义的关系(更简单,效率更低)。

这种方法采用典型方式,适用于相关的Category模型。因此它需要大量的内存。

class Content extends \yii\db\ActiveRecord
{
    /**
     * Returns comma separated list of category titles using specified separator.
     *
     * @param string $separator
     *
     * @return string
     */
    public function getCategoriesCsv($separator = ', ')
    {
        $titles = \yii\helpers\ArrayHelper::getColumn($this->categories, 'title');

        return implode($separator, $titles);
    }

    // ...
}

应该与急切加载一起使用:

Content::find()
    ->with('categories')
    ->all();

使用子查询(更高效,更不方便)

此方法使用子查询,不使用关系和相关模型。因此这种方式更快,并保留了大量内存。

class Content extends \yii\db\ActiveRecord
{

    const ATTR_CATEGORIES_CSV = 'categoriesCsv';

    /**
     * @var string Comma separated list of category titles.
     */
    public $categoriesCsv;

    /**
     * Returns DB expression for retrieving related category titles.
     *
     * @return \yii\db\Expression
     */
    public function prepareRelatedCategoriesExpression()
    {
        // Build subquery that selects all category records related with current content row.
        $queryRelatedCategories = Category::find()
            ->leftJoin('{{%content_category}}', '{{%content_category}}.[[category_id]] = {{%category}}.[[id]]')
            ->andWhere(new \yii\db\Expression('{{%content_category}}.[[content_id]] = {{%content}}.[[id]]'));

        // Prepare subquery for retrieving only comma-separated titles
        $queryRelatedCategories
            ->select(new \yii\db\Expression('GROUP_CONCAT( {{%category}}.[[title]] )'));

        // Prepare expression with scalar value from subquery
        $sqlRelatedCategories = $queryRelatedCategories->createCommand()->getRawSql();

        return new \yii\db\Expression('(' . $sqlRelatedCategories . ')');
    }

    // ...
}

当附加列的别名等于某个模型属性时,它将由all()方法填充:

$contentModels = Content::find()
    ->andSelect([
        '*',
        Content::ATTR_CATEGORIES_CSV => Content::prepareRelatedCategoriesExpression(),
    ])
    ->all();


foreach ($contentModels as $contentModel) {
    $contentModel->id;
    $contentModel->categoriesCsv;   // it will be also populated by ->all() method.
    // ...
}

ps:我没有测试过这段代码,可能应该修复查询类别。

此外,在这个例子中,它使用基本的简单语法编写,但它可以使用各种帮助器,连接模型等优化到更可爱的状态。

答案 1 :(得分:0)

加载类别

最初我将它实现为:

public function getCategoriesCsv(){
    $categoryTitles = [];
    foreach ($this->categories as $category){
        $categoryTitles[] = $category->title;
    }
    return implode(', ', $categoryTitles);       
}

感谢@IStranger,我将其添加到:

public function getCategoriesCsv()
{
    $titles = ArrayHelper::getColumn($this->categories, 'title');
    return implode(', ', $titles);
}

没有加载类别

我现在设法通过添加单独的CategoryCsv ActiveRecord来避免加载所有类别模型:

class CategoryCsv extends ActiveRecord
{
  public static function tableName(){
    return '{{%content_category}}';
  }

  public function attributes(){
    return ['content_id', 'value'];
  }

  public static function find(){
    return parent::find()
      ->select([
        'content_id',
        'GROUP_CONCAT(
           categoryCsv.title 
           ORDER BY categoryCsv.title 
           SEPARATOR ", "
         ) value'
       ])
     ->innerJoin('content categoryCsv','category_id = categoryCsv.id')
     ->groupBy('content_id');
  }
}

然后在Content ActiveRecord中:

public function getCategoriesCsv(){
  return $this->hasOne(CategoryCsv::className(), ['content_id' => 'id']);
}

因此我可以像这样访问值:

$contents = Content::find()->with('categoryCsv')->all();
foreach($contents as $content){
   echo $content->categoryCsv->value;
}