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']]);
Related
I'm working on a simple search project where I'm returning the results. The search function appears to work however, the total and page return the wrong values. The total field returns the total number of rows inside the data, not the total number of results from the search and the page is always {}.
Here's the model->function I've created:
public function search($string)
{
$results = $this->select('*')->orLike('title', $string)->orLike('excerpt', $string);
if ( empty( $results ) )
{
return [];
} else
{
$data = [
'results' => $results->paginate(2),
'total' => $results->countAllResults(),
'page' => $this->pager,
];
return $data;
}
}
What's puzzling is if I place the total field above the results value the count works, but then the result fields returns everything in the database at paginate(2).
Ok, I managed to solve this query by adding two separate queries to the database. The processing cost appears to be minimal and it should be alright when caching the responses. As it turns out you can chain queries but only in a particular order and if you use grouping (see ->groupStart() )
$results = $this->select('title, image, categories, id, excerpt')->groupStart()->like('title', $search)->orLike('excerpt', $search)->groupEnd()->where('status','live')->paginate(2);
$total = $this->select('title, image, categories, id, excerpt')->groupStart()->like('title', $search)->orLike('excerpt', $search)->groupEnd()->where('status','live')->countAllResults();
Some may argue the inefficiency of the two queries, but this works for my use case :) Hope this helps anyone else stuck on a similar problem.
I have some tables in my database and I'm trying to transact between them. My aim is to show the project name and the employees in that project in the form of cards on my projects page as in the image below. (Other than the method I mentioned, if there are better ways to do this, I ask you to share.)
When trying to do this, the first thing that came to my mind was to use leftJoin. Together with the projects, I thought of pulling the users in that project together and showing them by using foreach nested.
If I just want to shoot projects, there will be no problem and the first 4 projects come and everything works fine. But since I needed to attract the users in that project along with my projects, when I used leftJoin, I got an output as you can see below. I had to use pagination as I needed to show 4 projects per page, and when I used pagination, the lines that came in were inconsistent. While waiting for 4 projects and their employees to arrive, the first 4 lines came. (3 employees of 1 project and the first employee of the other project have arrived.)
My code:
$query = Project::query();
$query->select('prj_name', 'us_name');
$query->where('up_type_id', 3);
$query->leftJoin('user_positions', 'projects.prj_id', '=', 'user_positions.up_source_id');
$query->leftJoin('users', 'user_positions.up_user_id', '=', 'users.us_id');
$data['projectList'] = $query->paginate(4)->toArray();
return view('pages.projects.index', $data);
Output:
[data] => Array
(
[0] => Array
(
[prj_name] => Project Name - 2
[us_name] => Tracey Dyer
)
[1] => Array
(
[prj_name] => Project Name - 2
[us_name] => Joseph Dickens
)
[2] => Array
(
[prj_name] => Project Name - 2
[us_name] => Steven Ogden
)
[3] => Array
(
[prj_name] => Project Name - 3
[us_name] => Warren Nash
)
)
My tables:
Additional info :
It is not directly related to my question, but I would like to state it for informational purposes. My table named "user_position_types" represents fields within the company. The "up_type_id" column in the "user_positions" table specifies the partition type (such as Department, Project).
If this column is 1, it means department and "source_id" indicates which department it is. If it is 3, it means project and again "source_id" indicates which project it is.
I would like to thank everyone who answered in advance.
You may consider using Eloquent relationship instead of left join. In fact left join wouldn't work that well in your case since you will have to group all users in the same project from the rows afterwards. Example of Eloquent relationship using whereHas and with as follows.
$data['projectList'] = Project::select('prj_name', 'us_name')
->whereHas('users, function($query) {
$query->where('users_positions.up_type_id', 3);
})
->with('users')
->paginate(4);
The whereHas part filters only projects with users relationship and has pivot field up_type_id 3.
And with part will load users corresponding to the project.
You will also need to add a users relationship in Project model.
public function users()
{
return $this->belongsToMany(User::class, 'user_positions', 'prj_id', 'us_id')
->withPivot('up_type_id');
}
With this you can show your projects and users like so.
foreach($data['projectList'] as $project) {
$project->prj_name;
foreach($project->users as $user) {
$user->us_name;
}
}
In case you would like to have either projects with no users and projects with users from up_type_id = 3, you may use where closure and filter from both conditions (use orWhereHas).
$data['projectList'] = Project::select('prj_name', 'us_name')
->where(function ($query) {
$query->doesntHave('users')
->orWhereHas('users, function($query) {
$query->where('user_positions.up_type_id', 3);
});
})
->with('users')
->paginate(4);
So I'm working on this/these problem(s), and there's just a lot going on for recently using codeigniter..
So, I have these specific tables:
sect_tbl ----------- sub_tbl
sect_id ----------- sub_code
2 --------------------ST20
3 --------------------AS13
3 --------------------DA11
1 --------------------PE40
2 --------------------SE10
3 --------------------PE20
4 --------------------RT40
I made sect_id the foreign key in sub_tbl.
the display - a side menu:
link of sect_ids:
1
2
3
4
I want to make it so, that if I click the link of sect_id 3, I'll be redirected to a new page with its corresponding set of sub_code -- "AS13, DA11, PE20" as seen in the table above.
Expected outcome is something like this:
sub_code
(in sect_id-3)
| AS13 |
| DA11 |
| PE20 |
Model: (most definitely not right )
function fetch_view($data){
$query = $this->db->query("SELECT a.sub_code, a.sub_name, a.sect_id,
b.block_name, b.year_level FROM subject as a JOIN section as b
WHERE a.sect_id = b.sect_id ");
return $query->result();
}
Controller:
$data["subjects"] = $this->view_model->fetch_view($this->session-
userdata('user_id'));
view: (this lacking since I cant get around much further with foreach loop yet )
<?php
foreach ($subjects as $row) {
?>
<?php echo $row->year_level.$row->block_name; ?>
<?php
}
?>
-> I tried working my way with href to redirect me and then be able to view it dynamically but it backfired. Thanks a lot to those who could spare their time to share solutions.
If you say you want all sub codes where sect ID = 3
$sect_id = 3;
I simplified your query, but the main problem was that you had no use of "on x = y" for your join
$query = $this->db->query("
SELECT
sec.year_level,
sec.block_name,
sub.sub_code,
FROM section as sec
JOIN subject as sub
ON sec.sect_id = sub.sect_id
WHERE sec.sect_id = ?
", [ $sect_id ] );
if( $query->num_rows() > 0 )
return $query->result();
return [];
Then, because I don't have your actual table schemas, I couldn't suggest if your view is correct, but in general, something like this should work:
$this->load->helper('url');
foreach( $subjects as $row )
{
echo anchor( 'subs/show/' . $row->sub_code, $row->year_level . $row->block_name );
}
Also, as a side note, try to always follow a code style with proper indentation. It makes things that are wrong stand out and easier to fix.
I have a rather unique set of conditions and orders in which I need to retrieve data from a "sellers" table for an application I'm building in Zend framework.
The client is basically requesting an application where the directory page lists sellers in a very particular order, which is:
Sellers who have been approved in the last 7 days (then order by #4 below)
Then, selllers who have paid for upgraded features on the site, and are more the 7 days old (then order by #4 below)
Then, Sellers who are more than 7 days old and are more than 7 days old (then order by #4 below)
For all of the above, secondary order by would be their launch date, then alpha by business name
I'm trying to figure out the most effective way to write an action helper that will return the data in the correct sequence above, knowing that some of my views only need 1,2 (and 4), whereas other views within the application will need all 4.
Right now, I've been writing two or three separate queries, and passing them to 2 or 3 partialloop's inside the view, but I strive for properly written code, and would like to either combine my 3 queries into one object I can pass to one partial loop, or.... write one query. How can this be done?
Here's my helper at the moment:
class Plugin_Controller_Action_Helper_ListSellers extends Zend_Controller_Action_Helper_Abstract
{
//put your code here
public function direct($regulars = false, $filter = false)
{
$dateMod = $this->dateMod = new DateTime();
$dateMod->modify('-7 days');
$formattedDate = $dateMod->format('Y-m-d H:i:s');
// get sellers initialized in last 7 days
$sellerTable = new Application_Model_DbTable_Seller();
// get sellers initialized in last 7 days
$select = $sellerTable->select()->setIntegrityCheck(false);
$select->from(array('b' => 'seller'),array('sellerID', 'businessName','sellerPicture'));
// select firstName, lastName, picture from user table, and businessName and sellerID from seller table. All records from seller table
$select->join(array('u' => 'user'), 's.userID = u.userID', array('firstName', 'lastName'));
$select->order('s.launchDate DESC','s.businessName ASC');
$select->where('s.active = 1 AND s.contentApproval = 1 AND s.paymentApproval = 1');
$select->where('s.launchDate > ?', $formattedDate);
if($filter){ $select->where('s.categoryID = ?', $filter);}
$newSellers = $sellerTable->fetchAll($select);
$query = $sellerTable->select()->setIntegrityCheck(false);
$query->from(array('b' => 'seller'),array('sellerID', 'businessName','sellerPicture'));
// select firstName, lastName, picture from user table, and businessName and sellerID from seller table. All records from seller table
$query->join(array('u' => 'user'), 's.userID = u.userID', array('firstName', 'lastName'));
$query->order('s.launchDate DESC','s.businessName ASC');
$query->where('s.active = 1 AND s.contentApproval = 1 AND s.paymentApproval = 1 AND s.featured = 1');
$query->where('s.launchDate < ?', $formattedDate);
if($filter){ $select->where('s.categoryID = ?', $filter);}
$featuredSellers = $sellerTable->fetchAll($query);
if($regulars){
$where = $sellerTable->select()->setIntegrityCheck(false);
$where->from(array('b' => 'seller'),array('sellerID', 'businessName','sellerPicture'));
// select firstName, lastName, picture from user table, and businessName and sellerID from seller table. All records from seller table
$where->join(array('u' => 'user'), 's.userID = u.userID', array('firstName', 'lastName'));
$where->order('s.launchDate DESC','s.businessName ASC');
$where->where('s.active = 1 AND s.contentApproval = 1 AND s.paymentApproval = 1 AND s.featured IS NULL');
$where->where('s.launchDate < ?', $formattedDate);
$regularSellers = $sellerTable->fetchAll($where);
}
}
}
I don't see any limits being applied to your queries. So does that mean you really want to select all matching records? For scalability reasons I'd guess that the answer should be no, there will be limits applied. In this case, you may just have to do 3 different queries.
But if there are no limits to be applied, then you could do a single simple query that selects all sellers, unfiltered and unsorted, and do your sorting and filtering in view helpers or just in your views.
Regardless, I recommend not putting database queries inside your controller layer, assuming you want to use the Model-View-Controller pattern which Zend is built for. Controllers should be thin. Your models should handle all database queries and just spit out the results into your controllers. I use the Data Mapper pattern extensively. Something like:
$mapper = new Application_Model_SellerMapper();
$newSellers = $mapper->fetchNewSellers();
$featuredSellers = $mapper->fetchFeaturedSellers();
$regularSellers = $mapper->fetchRegularSellers();
Each of your fetchX() methods would return an array of Application_Model_Seller instances, rather than Zend_Db_Table_Row instances.
This way you maintain Separation of Concerns and Single Responsibility Principle better, for more maintainable code. Even if you're the only developer on the project over the long-term, 6 months from now you won't remember what you wrote and why. And if someone else comes on the project, clarity becomes really important.
EDIT::
Maybe I should be asking what the proper way to get a result set from the database is. When you have 5 joins where there is a 1:M relationship, do you go to the database 5 different times for the data??
I asked this question about an hour ago but haven't been able to get an answer that was fitting. I went ahead and wrote some code that does exactly what I need but am looking for a better way to do it
This array gives me multiple rows of which only some are needed once and others are needed many times. I need to filter these as I have done below but want a better way of doing this if possible.
Array
(
[0] => Array
(
[cid] => one line
[model] => one line
[mfgr] => one line
[color] => one line
[orderid] => one line
[product] => many lines
[location] => many lines
)
[1] => Array
(
.. repeats for as many rows as were found
)
)
This code works perfectly but again, I think there is a more efficient way of doing this. Is there a PHP function that will allow me to clean this up a bit?
// these are the two columns that produce more than 1 result.
$product = '';
$orderid = '';
foreach($res as $key)
{
// these produce many results but I only need one.
$cid = $key['cid'];
$model = $key['model'];
$mfgr = $key['mfgr'];
$color = $key['color'];
$orderid = $key['orderid'];
// these are the two columns that produce more than 1 result.
if($key['flag'] == 'product')
{
$product .= $key['content'];
}
if($key['flag'] == 'orderid')
{
$orderid .= $key['content'];
}
}
// my variables from above in string format:
Here is the requested SQL
SELECT
cid,
model,
mfgr,
color,
orderid,
product,
flag
FROM products Inner Join bluas ON products.cid = bluas.cid
WHERE bluas.cid = 332
ORDER BY bluas.location ASC
Without seeing your database structure it's a bit hard to decipher how you actually want to manipulate your data.
Perhaps this is what you're looking for though?
SELECT p.cid, p.model, p.mfgr, p.color, p.orderid, p.product, p.flag, GROUP_CONCAT(p.content SEPARATOR ', ')
FROM products AS p
INNER JOIN bluas AS b ON p.cid = b.cid
WHERE b.cid = 332
GROUP BY p.cid, p.flag
ORDER BY b.location ASC
So now for each product cid each flag will have an entry consisting of a comma separated list instead of there being many repeating for each flag entry.
Then after you're done with the string you can quickly turn it into an array for further manipulation by doing something like:
explode(', ', $key['content']);
Again it's really hard to tell what information you're trying to pull without seeing your database structure. Your SQL query also doesn't really match up with your code, like I don't even see you grabbing content.
At any rate I'm pretty sure some combination of GROUP BY and GROUP_CONCAT (more info) is what you're looking for.
If you can share more of your database structure and go into more detail of what information exactly you're trying to pull and how you want it formatted I can probably help you with the SQL if you need.