I'm having some problems with creating pagination with a HABTM relationship. First, the tables and relationships:
requests (id, to_location_id, from_location_id)
locations (id, name)
items_locations (id, item_id, location_id)
items (id, name)
So, a Request has a Location the request is coming from and a Location the Request is going to. For this question, I'm only concerned about the "to" location.
Request --belongsTo--> Location* --hasAndBelongsToMany--> Item
(* as "ToLocation")
In my RequestController, I want to paginate all the Items in a Request's ToLocation.
// RequestsController
var $paginate = array(
'Item' => array(
'limit' => 5,
'contain' => array(
"Location"
)
)
);
// RequestController::add()
$locationId = 21;
$items = $this->paginate('Item', array(
"Location.id" => $locationId
));
And this is failing, because it is generating this SQL:
SELECT COUNT(*) AS count FROM items Item WHERE Location.id = 21
I can't figure out how to make it actually use the "contain" argument of $paginate...
Any ideas?
after 3 days searching, I found the way
var $paginate = array('Post'=>array('group'=>'Post.id'));
It's recomended to add group, because sometimes we will get duplicte posts in different categories
$this->Post->bindModel(array('hasOne'=>array('CategoriesPost')), false);
$out = $this->paginate('Post', array('CategoriesPost.category_id'=>array(1,4,7,6)));
Add false to use bind model to all queries, not only to the following
To paginate HABTM, you need to temporarily bind 'hasOne' join model to model which you paginate:
// prepare to paginate Item
$this->Item->bindModel(array('hasOne'=>array('ItemsLocation')));
$contain['ItemsLocation']=array();
$conditions[]=array('ItemsLocation.location_id'=>$locationId);
$order = array('Item.created' => 'desc'); // set order
...
$items = $this->paginate('Item', compact('conditions','contain','order'));
I've been able to get it working somewhat, but the solution doesn't feel very cakey at all.
$items = $this->paginate(
$this->Request->ToLocation->Item,
array(
"Item.id IN ("
. "SELECT item_id FROM items_locations "
. "WHERE location_id = " . $locationId
. ")"
)
);
Related
I want to do a search of data and get the number of entries it has on another table
$query = $this->find();
$query->select([
'name',
'code',
'count' => "(
SELECT
COUNT(products_branches.id)
FROM
products_branches
INNER JOIN
branches
ON branches.company_id = products_branches.company_id
AND branches.code = products_branches.branch_code
WHERE
products_branches.deleted = 0
AND products_branches.carried = 1
AND products_branches.company_id = $company_id
AND products_branches.branch_code = code
)",
]);
is there a way that I could use the code fetch in the select and use at as one of the condition in the search condition of the subquery?
I want to search the number of entries each name and code has on product_branches table
What you're looking to do is possible, but you've got a few more steps to complete. You need to make sure you create an Association between the products_branches and branches table first. Then you can use the where() function to do what you want to do. Something like this:
$q = $this-find('all')
->select(['Branches.name', 'Branches.code'])
->contain(['ProductsBranches'])
->where(['ProductsBranches.deleted' => 0,
'ProductsBranches.carried' => 1,
'ProductsBranches.company_id' => $company_id,
'ProductsBranches.branch_code' => $code]);
$count = $q->all()->count();
So, I have been looking around and working with Elgg 1.8 for quite some time now, and while I am getting the hang of it, I am still a complete novice.
I have had this idea to sort my table which grabs certain information from elgg itself, specifically the groups and such. I want to sort everything by its tabs (name, total, difference, date) and for name and date elgg has been rather straight forward with that. However, I am not quite sure if I can do the same with the total and difference because of that. Take note of the code below.
$GroupArray = elgg_get_entities(array(
'type' => 'group',
//try to get this to use the order of the totalmembers
'order_by' => $Order . " " . $Sort,
'joins' => 'JOIN ' . $db_prefix . 'groups_entity ge',
'limit' => 10,
'pagination' => true,
'offset' => $Offset,
));
This $GroupArray captures all of the group stats which allows me to add all of the necessary information into the table, and the $Order and $Sort variables are dynamically added by clicking on the tabs in the table. Name and Date are already initialized in elgg with ge.name and e.time_created, but I need to do some trickery to get the members and total working the same way.
$TotalMembers = elgg_get_entities_from_relationship(array(
'relationship' => 'member',
'relationship_guid' => $Reports->guid,
'inverse_relationship' => true,
'type' => 'user',
'limit' => 20,
'joins' => array("JOIN {$db_prefix}users_entity u ON e.guid=u.guid"),
'order_by' => 'u.name ASC',
'count' => true,
'relationship_created_time_lower'
));
This is how I get the total amount of members for each column and group, it comes after the group array as that is what allows it to be grabbed. My issue now is trying to that the $TotalMembers 'order_by' to be put into the $Order variable, essentially sorting by the min to max total members for each group.
I have searched far and wide to no avail, and im not sure if my idea is plausible or if it even works as I think it can.
Is anyone able to give me a push in the right direction?
Well, it turns out it is possible, you just need to take apart Elgg's code and replace it with the Sql would otherwise create for you
$allQuery =
"SELECT DISTINCT e.guid, ge.name,
(SELECT count(DISTINCT e.guid)
FROM elgg_entities e
JOIN elgg_users_entity u
ON e.guid=u.guid
JOIN elgg_entity_relationships r
on r.guid_one = e.guid
WHERE (r.relationship = 'member'
AND r.time_created >=1507476424
AND r.guid_two = ge.guid)
AND ((e.type = 'user'))
AND (e.site_guid IN (1))
AND ( (1 = 1) and e.enabled='yes')) as totalMembers
FROM elgg_entities e JOIN elgg_groups_entity ge
ON e.guid=ge.guid
WHERE ((e.type = 'group'))
AND (e.site_guid IN (1))
AND ( (1 = 1) and e.enabled='yes')
ORDER BY totalMembers $Sort
LIMIT $Offset, 10";
$allData = get_data($allQuery);
This just takes the two different elgg statements which are really sql statements and joins them together so that it is now possible to search by the max avaliable for the total members query.
I need to render a view with mixed data in it with a pagination limit of 6 items per page.
In this case I have a Controller that renders a view and passes an array of mixed data such as an array of Catalogs and Products.
$products = new Products;
$productsCriteria = new CDbCriteria;
$catalogs = new Catalogs;
$catalogsCriteria = new CDbCriteria;
$productsCriteria->condition = "status = 1 AND category_id = :category_param";
$productsCriteria->params = array(':category_param' => $id); //$id is passed as a parameter to the action
$productsCriteria->order = "created_on DESC";
$catalogsCriteria->condition = "status = 1 AND category_id = :category_param";
$catalogsCriteria->params = array(':category_param' => $id);
$catalogsCriteria->order = "created_on DESC";
$productsCount = $products->count($productsCriteria);
$catalogsCount = $catalogs->count($catalogsCriteria);
$pages = new CPagination($productsCount + $catalogsCount);
$pages->pageSize = 6;
$pages->applyLimit($productsCriteria);
$pages->applyLimit($catalogsCriteria);
$productResult = $products->findAll($productsCriteria);
$catalogResult = $catalogs->findAll($catalogsCriteria);
$result = array_merge($productResult, $catalogResult);
$this->render('list', array('data' => $result, 'pages' => $pages, 'productsCount' => $productsCount, 'catalogsCount' => $catalogsCount));
This works as it is, but it renders 6 items of each type. It renders 6 Catalogs and then it renders 6 Products per page. What I need is that it renders all of the Catalogs first, 6 per page, regardless of how many pages it needs and then it renders all of the Products, again, 6 per page and regardless of how many pages it needs.
On another note, the Products and Catalogs are in a many to many relationship. I've also tried extracting Products the following way:
$productsCriteria->join = ", catalog_product_relation cpr WHERE status = 1 AND category_id = :category_param AND cpr.product_id != t.id";
I used the
cpr.products_id != t.id
since I need to list Products that don't belong to any Catalog.
I used join since it only appends the string to the criteria without adding any MySQL tokens like condition does.
Merging the two criteria doesn't work since they're different data models.
I am trying to make a page with a list of posts, and underneath each post all the comments belonging to that post. Initially I wanted to use just one query to retrieve all the posts + comments using the SQL's JOIN, but I find it impossible to for example retrieve a post with multiple comments. It only displays the posts with a maximum of 1 comment per post, or it just show a post multiple times depending on the amount of comments.
In this related question, somebody talked about using 2 queries:
How to print posts and comments with only one sql query
But how do I do this?
I've got the query and a while loop for posts, but I obviously don't want to run a query for comments for each post inside that loop.
$getPost = mysql_query('SELECT p.post_id,
p.user_id,
p.username,
p.content
FROM post p
ORDER BY p.post_id DESC');
while($row = mysql_fetch_array($getPost))
{
...
}
Table structure (reply is the table for storing comments):
POST (post_id (primary key), user_id, username, content, timestamp)
REPLY (reply_id (primary key), post_id, username, reply_content, timestamp)
You can do it in a single query, which is OK if the amount of data in your original posts is small:
$getPost = mysql_query('SELECT
p.*,
r.reply_id, r.username r_username, r.reply_content, r.timestamp r_timestamp
FROM post p
left join reply r
ORDER BY p.post_id DESC'
);
$posts = array();
$last_id = 0;
while($row = mysql_fetch_array($getPost))
{
if ($last_id != $row['post_id']) {
$posts[] = array(
'post_id' => $row['post_id'],
'user_id' => $row['user_id'],
'username' => $row['username'],
'content' => $row['content'],
'timestamp' => $row['timestamp'],
'comments' => array()
);
}
$posts[sizeof($posts) - 1]['comments'][] = array(
'reply_id' => $row['reply_id'],
'username' => $row['r_username'],
'reply_content' => $row['reply_content'],
'timestamp' = $row['r_timestamp']
);
}
Otherwise, break it into two queries like so:
$getPost = mysql_query('SELECT
p.*,
FROM post p
ORDER BY p.post_id DESC'
);
$rows = array();
$ids = array();
$index = array();
while($row = mysql_fetch_assoc($getPost)) {
$row['comments'] = array();
$rows[] = $row;
$ids[] = $row['post_id'];
$index[$row['post_id']] = sizeof($rows) - 1;
}
$getComments = mysql_query('select r.* from replies r where r.post_id in ("'
. join('","', $ids)
. '")');
while ($row = mysq_fetch_assoc($getComments)) {
$rows[$index[$row['post_id']]]['comments'][] = $row;
}
... Or something like that. Either option allows you to litter your first query with WHERE clauses and so forth to your heart's content. The advantage of the 2nd approach is that you don't re-transmit your original post data for each comment!
In order to also get those posts without comments, you need to use a LEFT OUTER JOIN. In that case, any row in the first table without any corresponding rows in the second table will be paired with a row consisting of null values.
SELECT * FROM posts
LEFT OUTER JOIN comments ON posts~post_id = comments~post_id;
By the way: there is also a RIGHT OUTER JOIN. When you would use that, you would get all comments, including those where the parent post got lost somehow, but no posts without comments.
I'm trying to filter some car parts depending on the categories they are related to.
A part can have many categories (in the code they are called tags), so I chose the HABTM relation with a join table.
Filtering works so far, but only with an OR condition with cake using the SQL command IN.
But I'm trying to filter only the parts that have all the selected categories, so I need to use an AND condition on the category array.
Here's the extracted code from the controller:
$this->Part->bindModel(array('hasOne' => array('PartsTagsJoin')));
$params['conditions'] = array('AND' => array('PartsTagsJoin.tag_id' => $selectedCats));
$params['group'] = array('Part.id');
$parts = $this->Part->find('all',$params);
$this->set('parts',$parts);
$selectedCats is an array like this: array(1,2,3,4,5);
The SQL output is:
'SELECT `Part`.`id`, `Part`.`name`, `Part`.`image`, `Part`.`image_dir`, `Part`.`time_created`, `Part`.`time_edited`, `Part`.`user_id`, `Part`.`editor_id`, `Part`.`notice`, `User`.`id`, `User`.`username`, `User`.`password`, `User`.`usergroup_id`, `User`.`realname`, `PartsTagsJoin`.`id`, `PartsTagsJoin`.`part_id`, `PartsTagsJoin`.`tag_id`
FROM `c33rdfloor`.`parts` AS `Part`
LEFT JOIN `c33rdfloor`.`users` AS `User` ON (`Part`.`user_id` = `User`.`id`)
LEFT JOIN `c33rdfloor`.`parts_tags_join` AS `PartsTagsJoin` ON (`PartsTagsJoin`.`part_id` = `Part`.`id`)
WHERE `PartsTagsJoin`.`tag_id` IN (1, 4, 8, 24)'
How can I filter the parts that have every id that is committed through the $selectedCats Array.
Thank you in advance for your help.
I've got it working thanks to this blog post:
http://nuts-and-bolts-of-cakephp.com/2008/08/06/habtm-and-join-trickery-with-cakephp/
It seems to be a little tricky to filter entries with all selected tags:
The key in achieving an AND relation is to get the count of the selected cats and match it with the ones of the query inside the group parameter.
This line did it:
$params['group'] = array('Part.id','Part.name HAVING COUNT(*) = '.$numCount);
In the End the code looked like this (for people interested in a solution):
// Unbinds the old hasAndBelongsToMany relation and adds a new relation for the output
$this->Part->unbindModel(array('hasAndBelongsToMany'=>array('PartsTag')));
$this->Part->bindModel(array('hasOne'=>array(
'PartsTagsJoin'=>array(
'foreignKey'=>false,
'type'=>'INNER',
'conditions'=>array('PartsTagsJoin.part_id = Part.id')
),
'PartsTag'=>array(
'foreignKey'=>false,
'type'=>'INNER',
'conditions'=>array(
'PartsTag.id = PartsTagsJoin.tag_id',
'PartsTag.id'=>$selectedCats
)))));
$numCount = count($selectedCats); // count of the selected tags
// groups the entries to the ones that got at least the count of the selected tags -> here the 'AND' magic happens
$params['group'] = array('Part.id','Part.name HAVING COUNT(*) = '.$numCount);
$parts = $this->Part->find('all', $params); // sends the query with the params
$this->set('tagCategories', $categories);
$this->set('selectedCats', $selectedCats);
$this->Part->recursive = 4;
$this->set('parts',$parts);