在cakePHP 1.3中查找hasMany关系的复杂查询

时间:2010-08-18 22:10:16

标签: cakephp models

我已经忙着使用cakePHP框架几个月了,我真的很喜欢它。目前我正在开发一个非常新的项目,它可以完成它应该做的工作(我想......)但我对我编写的一些代码感到不舒服。事实上,我应该优化我的分页条件查询,以便立即获得正确的结果(现在我通过一堆Set :: extract方法调用来操作结果集。

我将草拟应用程序的相关方面。我有一个模型'Site'与模型'SiteMeta'有一个hasMany关系。最后一个表格如下:id,site_id,key,value,created。

在最后一个模型中,我在不同时期记录了几个站点的值。我要存储的密钥的名称(例如alexarank,google pagerank,...),当然还有价值。在给定的时间间隔,我让我的应用程序更新此数据库,以便我可以跟踪此值的演变。

现在我的问题是这个。

在各个网站的概述页面上(controller => Sites,action => index)我想展示网站的CURRENT pagerank。因此,我需要一个精确的SiteMeta记录,其中'created'字段是最高的,'key'中的值应该与'pagerank'一词匹配。我已经尝试了几个我在网上阅读但却没有工作的东西(可包含,绑定模型等)。可能我做错了什么。

现在,当我执行$ this-> paginate

时,我得到这样的结果
Array
(
    [0] => Array
        (
            [Site] => Array
                (
                    [id] => 1
                    [parent_id] => 0
                    [title] => test
                    [url] => http://www.test.com
                    [slug] => www_test_com
                    [keywords] => cpc,seo
                    [language_id] => 1
                )
            [SiteMeta] => Array
                (
                    [0] => Array
                        (
                            [id] => 1
                            [site_id] => 1
                            [key] => pagerank
                            [value] => 5
                            [created] => 2010-08-03 00:00:00
                        )

                    [1] => Array
                        (
                            [id] => 2
                            [site_id] => 1
                            [key] => pagerank
                            [value] => 2
                            [created] => 2010-08-17 00:00:00
                        )

                    [2] => Array
                        (
                            [id] => 5
                            [site_id] => 1
                            [key] => alexa
                            [value] => 1900000
                            [created] => 2010-08-10 17:39:06
                        )
                )
        )

要获取pagerank,我只需遍历所有站点并操作我得到的这个数组。接下来,我使用Set :: extract过滤结果。但这感觉不太对劲:)

$sitesToCheck = $this->paginate($this->_searchConditions($this->params));

foreach($sitesToCheck as $site) {
            $pagerank = $this->_getPageRank($site['Site']);
            $alexa = $this->_getAlexa($site['Site']);
            $site['Site']['pagerank'] = $pagerank;
            $sites[] = $site;
        }

if (isset($this->params['named']['gpr']) && $this->params['named']['gpr']) {
                $rank = explode('-', $this->params['named']['gpr']);
                        $min = $rank[0];$max = $rank[1];
                $sites = Set::extract('/Site[pagerank<=' . $max . '][pagerank>=' . $min .']', $sites);
        }

$this->set(compact('sites', 'direction'));          

你能帮助我考虑解决方案吗?提前谢谢。


感谢您的贡献。我尝试了这些选项(也是使用bindmodel但也没有工作的东西),但仍然无法让它像它应该的那样工作。如果我定义了这个

$this->paginate = array(
                    'joins'=>   array(
                                array(
                                    'table'=>'site_metas',
                                    'alias'=>'SiteMeta',
                                    'type' =>'inner',
                                    'conditions' =>array('Site.id = SiteMeta.site_id')  
                                    )                                                   
                        ),                                                                
        );

我得到重复的结果 我有一个网站有3个不同的SiteMeta记录和一个有2个不同记录的网站。 paginate方法总共返回5条记录。这可能是一个简单的解决方案,但我无法弄明白:)

我也尝试自己编写一个sql查询,但在这种情况下我似乎无法使用分页魔法。查询我想模仿分页选项和条件如下。查询完全按照我想要的方式返回。

$sites = $this->Site->query('SELECT * FROM sites Site, site_metas SiteMeta WHERE SiteMeta.id = (select SiteMeta.id from site_metas SiteMeta WHERE Site.id = SiteMeta.site_id AND SiteMeta.key = \'pagerank\' order by created desc limit 0,1 )');

4 个答案:

答案 0 :(得分:2)

当您尝试检索hasMany关系中的数据时,默认情况下,cakephp不会加入表。如果您选择joins,您可以执行以下操作:

$this->paginate = array(
                   'joins'=>array(
                              array(
                               'table'=>'accounts',
                               'alias'=>'Account',
                               'type' =>'inner',
                               'conditions' =>array('User.id = Account.user_id')
                              )
                            ),
                            'conditions'=> array('OR' => 
                               array(
                                'Account.name'=>$this->params['named']['nickname'],
                                'User.id' => 5)
                               )
                            );
$users = $this->paginate();
         $this->set('users',$users);
debug($users);
$this->render('/users/index');

你必须根据自己的需要来配合这个。关于加入的More,就像在另一个回答中已经提到的那样。

编辑1:这是因为您缺少第二个“条件”。请参阅我的代码段。第一个'条件'只表示连接发生的位置,而第二个'条件'表示实际选择。

编辑2: Here有关如何编写条件以便选择所需数据的一些信息。您可能希望在精炼条件下使用列max上的rdbms的created函数。

编辑3:不能同时使用包含和连接。引自手册:使用包含Containable行为的连接可能会导致一些SQL错误(重复的表),因此如果您的主要目标是基于相关数据执行搜索,则需要使用连接方法作为Containable的替代方法。 Containable最适合限制find语句带来的相关数据量。我想你还没有尝试过我的编辑。

编辑4 :一种可能的解决方案是向表last_updated添加字段Sites。然后可以在第二个条件语句中使用此字段与SiteMeta.created值进行比较。

答案 1 :(得分:0)

尝试这样的事情:

   $this->paginate = array(
        'fields'=>array(
            'Site.*',
            'SiteMeta.*',
            'MAX(SiteMeta.created) as last_date'
         ),
        'group' => 'SiteMeta.key'
        'conditions' => array(
            'SiteMeta.key' => 'pagerank'
        )
    );
    $data = $this->paginate('Site');

或者这个:

   $conditions = array(
        'recursive' => 1,
        'fields'=>array(
            'Site.*',
            'SiteMeta.*',
            'MAX(SiteMeta.created) as last_date'
         ),
        'group' => 'SiteMeta.key'
        'conditions' => array(
            'SiteMeta.key' => 'pagerank'
        )
    );
    $data = $this->Site->find('all', $conditions);

如果不起作用,请检查thisthis。我100%确定可以通过单个查询获得您想要的结果。

答案 2 :(得分:0)

尝试这样的事情(在模型上设置可容纳的内容):

$this->Site->recursive = -1;

$this->paginate = array(
    'conditions' => array(
        'Site.title' => 'title') //or whatever conditions you want... if any
    'contain' => array(
        'SiteMeta' => array(
             'conditions' => array(
                 'SiteMeta.key' => 'pagerank'),
             'limit' => 1,
             'order' => 'SiteMeta.created DESC')));

我使用可包含的东西,实际上我在app_model文件中有这个,所以它适用于所有模型:

var $actsAs = array('Containable');

答案 3 :(得分:0)

许多人认为所有能帮助我完成这一切的人:) 毕竟,我把它弄好了。

最终这对我来说是个伎俩

$this->paginate = array(
                    'joins'=>   array(
                                     array(
                                         'table'=>'site_metas',
                                         'alias'=>'SiteMeta',
                                         'type' =>'inner',
                                         'conditions' => array('Site.id = SiteMeta.site_id'))                                                                       
                                ), 
                    'group' => 'Site.id',
                    'contain' => array(
                        'SiteMeta' => array(
                                'conditions' => array(
                                    'SiteMeta.key' => 'pagerank'),
                                    'limit' => 1,
                                    'order' => SiteMeta.created DESC',
                                    )));

        $sites = $this->paginate();