Сложный запрос поиска для отношений hasMany в cakePHP 1.3 - PullRequest
1 голос
/ 19 августа 2010

Пару месяцев я был занят фреймворком cakePHP, и мне это очень нравится. В данный момент я работаю над очень новым проектом, и он выполняет свою работу так, как должен (я думаю ...), но я чувствую себя некомфортно из-за написанного мной кода. Фактически я должен оптимизировать свой запрос условий разбивки на страницы, чтобы я сразу получил правильные результаты (сейчас я манипулирую набором результатов с помощью набора вызовов метода Set :: extract.

Я нарисую соответствующие аспекты приложения. У меня есть модель Site, у которой есть отношение hasMany к модели SiteMeta. Эта последняя таблица выглядит следующим образом: id, site_id, ключ, значение, создано.

В этой последней модели я записываю несколько значений сайта в разные периоды. Имя ключа, который я хочу сохранить (например, alexarank, google pagerank, ...), и, конечно же, значение. Через определенный промежуток времени я позволяю моему приложению обновлять эту базу данных, чтобы я мог отслеживать эволюцию этих значений.

Теперь моя проблема заключается в следующем.

На странице обзора различных веб-сайтов (controller => Sites, action => index) я хотел бы показать страницу ТЕКУЩЕГО веб-сайта. Таким образом, мне нужна одна точная запись SiteMeta, где «созданное» поле является наибольшим, а значение в «ключе» должно соответствовать слову «pagerank». Я пробовал несколько вещей, которые я читал в сети, но ни одна из них не работала (сдерживаемые, bindmodel и т. Д.). Вероятно, я делаю что-то не так.

Прямо сейчас я получаю такие результаты, когда делаю $ 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 ]

2 голосов
/ 19 августа 2010

Поскольку вы пытаетесь получить данные в отношении 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');

Вы, конечно же, должны соответствовать этому. Больше для объединений, как уже упоминалось в другом ответе.

Редактировать 1: Это потому, что вы пропускаете вторые «условия».Смотрите мой фрагмент кода.Первые «условия» просто указывают, где происходит соединение, тогда как вторые «условия» делают фактический выбор.

Редактировать 2: Здесь некоторая информация о том, как записывать условия для выбора необходимых данных.Возможно, вы захотите использовать функцию max ваших rdbms в столбце created в уточненном состоянии.

Edit 3: Содержимое и объединения не должны использоваться вместе.Цитата из руководства: Использование соединений с поведением Containable может привести к некоторым ошибкам SQL (дублирующимся таблицам), поэтому вам нужно использовать метод join в качестве альтернативы для Containable, если ваша основная цель - выполнять поиск на основе связанных данных.Containable лучше всего подходит для ограничения количества связанных данных, передаваемых оператором find.Я думаю, что вы еще не пробовали мою правку 2.

Правка 4: Одним из возможных решений может быть добавление поля last_updated к таблице Sites.Это поле затем можно использовать во втором операторе условий для сравнения со значением SiteMeta.created.

0 голосов
/ 19 августа 2010

Многие думают всем, кому удалось помочь мне в этом :) Я все исправил, хе-хе.

В конце концов это был трюк для меня

$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();
0 голосов
/ 19 августа 2010

Попробуйте что-то вроде этого (с настройкой содержимого на ваших моделях):

$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');
0 голосов
/ 19 августа 2010

Попробуйте что-то вроде этого:

   $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);

Если это не работает, проверьте это и это ,Я на 100% уверен, что с помощью одного запроса можно получить желаемый результат.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...