【问题标题】:Containable behavior doesn't return deeper model association and selecting fields可包含的行为不会返回更深层次的模型关联和选择字段
【发布时间】:2013-04-30 16:09:20
【问题描述】:

我想限制使用可包含的更深层关联返回的字段。

我的联想:

游戏有很多评论

分页和可包含的代码:

$this->paginate = array(
            'conditions' => $conditions,
            'fields' => array(
                'Game.id', 'Game.name',
                'Publisher.id', 'Publisher.name'
            ),
            'contain' => array(
                'Game' => array(                     
                    'Review' => array(
                        'fields' => array('Review.id', 'ROUND(AVG(Review.score),1)')
                    )
                ),
            )
        );
$games = $this->paginate('Game');

目前,Review 表中的所有字段都已返回。 'ROUND(AVG(Review.score),1)' 永远不会返回。如何指定要从 Review 关联返回的字段?


SQL 转储 使用@theJetzah 的答案获取两个搜索结果。第一个是搜索一个游戏作为结果,第二个是搜索返回三个游戏。

SELECT `Review`.`id`, `Review`.`review_text`, `Review`.`score`, `Review`.`user_id`, `Review`.`game_id`, `Review`.`created`, `Review`.`platform_id`, (ROUND(AVG(`Review`.`score`),1)) AS `Review__average_score` FROM `videogamedb`.`reviews` AS `Review` WHERE `Review`.`game_id` = (55)

SELECT `Review`.`id`, `Review`.`review_text`, `Review`.`score`, `Review`.`user_id`, `Review`.`game_id`, `Review`.`created`, `Review`.`platform_id`, (ROUND(AVG(`Review`.`score`),1)) AS `Review__average_score` FROM `videogamedb`.`reviews` AS `Review` WHERE `Review`.`game_id` IN (55, 56, 57)

【问题讨论】:

  • 既然你是对Game模型进行分页,你为什么在'contain'中包含'Game'?游戏将成为主表/模型?
  • @theJeztah:我目前在另一个名为 SearchesController 的控制器中。它用于处理整个网站的用户搜索。
  • Controller 没有定义你使用的模型,如果你在 $uses 数组中指定了多个模型,你也可以使用另一个模型,例如public $uses = array('ModelA', ModelB');
  • 刚刚意识到,您的计算字段可能不起作用;由于您使用的是聚合,因此需要对评论进行“分组”。你能解释一下这些模型是如何相关的吗?是Game->hasMany->Review 还是Game->belongsTo->Review
  • @thaJeztah 是Game->hasMany->Review。另外,我的控制器中有public $uses = array('Game');

标签: cakephp


【解决方案1】:

不是一个完整的答案,而是尝试让它工作:)

Approach1(更新:Containable 不支持“分组依据”)

首先,尝试将“游戏”模型添加到控制器的$uses 数组中,如果尚未包含它,并重新组织分页数组(如 Sam 之前建议的那样),以便您'将对游戏模型本身进行分页。

然后,为计算的分数创建一个虚拟字段可能会有所帮助,但需要对'Review'的结果进行分组,否则您将无法计算平均分数。

我无法对此进行测试,但可能值得一试

类似的东西;

public $uses = array(
    'Game',
    // other models
);

public function myfunction()
{
    $this->Game->Review->virtualFields['average_score'] = 'ROUND(AVG(Review.score),1)';

    $this->paginate = array(
        'Game' => array(
            'fields' => array(
                'Game.id',
                'Game.name',
                'Publisher.id',
                'Publisher.name'
            ),
            'contain' => array(
                'Review' => array(
                    'fields' => array(
                        'Review.game_id,
                        'Review.average_score',
                    ),
                    'group' => array(
                        'Review.game_id,
                    ),
                )
            )
        )
    );

    // Conditions can be passed to paginate,
    // that way you can specify 'paginate' at
    // one place and don't have to modify it
    // to include the conditions
    $games = $this->paginate('Game', $conditions);
}

替代方法:使用连接和数据库视图

显然, Containable 行为不喜欢 group-by 子句;请参阅此票以获取更多信息:Containable behavior does not implement 'group' option

CakePHP 允许您手动指定连接:Joining Tables

为了简化事情并避免为所有字段添加“分组依据”,请在您的数据库中创建一个简单的数据库视图;

CREATE VIEW review_scores AS
   SELECT 
       game_id, 
       ROUND(AVG(score),1) AS average_score,
       COUNT(id) AS total_reviews
   FROM
       reviews
   GROUP BY
      game_id;

如果您对此不熟悉;数据库“视图”基本上是一个“存储查询”,可以像访问常规表一样访问它。见Create View

然后,使用“手动”连接,使用新创建的数据库视图作为源表。在你的情况下,这看起来像这样;

$this->paginate = array(
    'Game' => array(
        'fields' => array(
            'Game.id',
            'Game.name',
            'Publisher.id',
            'Publisher.name',
            'ReviewScore.average_score',
            'ReviewScore.total_reviews',
        ),
        'joins' => array(
            array(
                'table' => 'review_scores',
                'alias' => 'ReviewScore',
                'type'  => 'LEFT',
                'conditions' => array(
                    'ReviewScores.game_id = Game.id',
                )
            )
        )
    )
);

希望对你有帮助

【讨论】:

  • @thaJetzah:使用你的代码,当我的搜索结果只返回一场比赛时,我得到了那场比赛的正确平均得分。但是,当搜索返回多个游戏时,仅返回一个平均分数,它是所有返回游戏的所有评论的平均分数。我已将 sql 转储 添加为对我的问题的编辑。
  • @user1927371 问题在于 Containable 行为似乎忽略了fieldsgroup。我不确定这种行为是否应该支持这一点。您只需要每场比赛的平均分吗?一个好的解决方案是在您的数据库中创建一个视图并将其用作模型的源。我将添加一个示例。
  • @thaJetzah:感谢您的帮助。您的替代解决方案有效。我不了解数据库视图,它们似乎非常有用。谢谢。
  • @user1927371 数据库视图确实很有用;它们可以成为一个受欢迎的“抽象”层,以简化复杂查询的使用。就个人而言,我在我的视图前加上vw_,以便更容易区分它是视图还是实际的数据库表。无论如何,很高兴我能帮上忙!
【解决方案2】:

我觉得你的数组配置有点不对,试试:

$this->paginate = array(
    'Game' => array(
        'conditions' => $conditions,
        'fields' => array(
            'Game.id', 'Game.name',
            'Publisher.id', 'Publisher.name'
        ),
        'contain' => array(
            'Review' => array(
                'fields' => array('Review.id', 'ROUND(AVG(Review.score),1)')
            )
        )
    )
);
$games = $this->paginate('Game');

顺便说一句,根据个人经验,在查询中指定字段并不总能加快速度(当然对于少量字段),假设这是这样做的动机。它确实减少了内存占用,但这仅与记录的原始大小和返回的记录数有关。

【讨论】:

  • 我重新排列了我的数组,它返回了完全相同的结果。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-11-23
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多