My current config of tables is as follows
PagesTable
$this->belongsToMany('Keywords', ['through' => 'PagesKeywords']);
PagesKeywordsTable
Schema: id, page_id, keyword_id, relevance
$this->belongsTo('Pages');
$this->belongsTo('Keywords');
KeywordsTable
$this->belongsToMany('Pages', ['through' => 'PagesKeywords']);
Now heres what i'm trying to do..
Find pages via keywords, using an array to be precise then order by PagesKeywords.relevance
(This is basically storing how many time that keyword is repeated per page, so no duplicate keywords in join table)
I've currently got this working fine except it groups the results of keywords by the keyword itself, where as I need them to be grouped by Pages.id
Here is what i have in my Pages controller, search action:
$keywords = explode(" ", $this->request->query['q']);
$query = $this->Pages->Keywords->find()
->where(['keyword IN' => $keywords])
->contain(['Pages' => [
'queryBuilder' => function ($q) {
return $q->order([
'PagesKeywords.relevance' =>'DESC'
])->group(['Pages.id']);
}
]]);
$pages = array();
foreach($query as $result) {
$pages[] = $result;
}
I know this seems like a backward way to do things but its the only way I seemed to be able to order by _joinTable (PagesKeywords.relevance)
This returns the results I need but now it needs to be grouped by Pages.id which is where this whole thing goes to pot..
Just to be clear the structure I want is:
Page data 1
Page data 2
Page data 3
Page data 4
Where as its currently returning:
Keyword "google"
------- Page data 1
------- Page data 2
------- Page data 3
------- Page data 4
Keyword "something"
------- Page data 1
------- Page data 2
------- Page data 3
------- Page data 4
If you are able to help me thats great!
Thanks
If you're having issues with complex queries with an ORM, I find it always easier to figure out the SQL I need to get the results I require, then adapt that to the ORM.
The query you're looking for would be like this (Using MySQL engine... MySQL Handles field selects more liberally in GROUP BY clauses than other SQL engines )
SELECT Pages.*, COUNT(DISTINCT(PagesKeywords.keyword_id)) AS KeywordCount
FROM pages Pages
INNER JOIN pages_keywords PagesKeywords ON (PagesKeywords.page_id = Pages.id)
INNER JOIN keywords Keywords ON (Keywords.id = PagesKeywords.keyword_id)
WHERE Keywords.name IN ('keyword1','keyword2')
GROUP BY Pages.id
This will give you all pages that contain the keyword and KeywordCount will contain the number of distinct attached Keyword.id's
So a finder method for this would look like ( going ad-hoc here so my syntax might be shaky )
** Inside your PagesTable model **
public function findPageKeywordRank(Query $q, array $options) {
$q->select(['Pages.*','KeywordCount'=>$q->func()->count('DISTINCT(PagesKeywords.keyword_id)'])
->join([
'PagesKeywords'=>[
'table'=>'pages_keywords',
'type'=>'inner',
'conditions'=>['PagesKeywords.page_id = Pages.id']
],
'Keywords'=>[
'table'=>'keywords',
'type'=>'inner',
'conditions'=>['Keywords.id = PagesKeywords.keyword_id']
]
])
->group(['Pages.id']);
return $q;
}
Then you can all your finder query
$pages = TableRegistry::get("Pages")->find('PageKeywordRank')
->where(['Keywords.name'=>['keyword1','keyword2']]);