CI Datamapper: Iterate parent child - php

I have categories and posts.
I would like to list all categories (parent), and all related posts (child). Without nested sets, complex queries. Relations setted up.
As i rembember from the documentation:
$categories = new Category();
foreach( $categories->get() as $category )
{
echo $category->name;
foreach( $category->post->get() as $post)
{
echo $post->title;
}
}
Now if i have thousand categories, it will make a query for each category.
What is the best practice in this case?
Create two query for posts and categories and merge them into a tree with php?
Join two tables, one query and create the tree with php?
Somebody's smart idea :)

I would create a dedicated model method that retrieves the data you want, and I'd retrieve it in batches (for pagination). Before I jump into the idea with some example code, the core concept is that you have two tables which you link via an INNER JOIN statement and that you limit your selects in batches. You then display data as you would normally in your view.
Let's say that you have a category table that looks something like this:
id
name
Let's also say that you have a post table that looks something like this:
id
name
category_id
I would now basically retrieve the entries that you need, but also limit my results.
$this->db->select('*');
$this->db->from('category');
$this->db->join('post', 'post.category_id = category.id', 'inner');
$this->db->limit($limit, $offset);
$query = $this->db->get();
Pagination saves you when you have to display a lot of data. If you however, must display all the data at once, consider using Memcached, Gearman or similar caching technology, so that you have better performance.

Related

Codeigniter count number of articles in one category while listing categories

I have some problem while listing categories from database.
First i have a table called "Videos" where i store som videos-information like v_name, v_description and category_name.
In the second table called "Categories" where i store categories-information like c_name and c_description.
OFC i have id's in every table :)
But now i want to list the categories and in the same query count every videoitem in every category.
This is the code and i can't figure out how to do in the model now and later how to show the numbers in the view file, so pleace help me!
Thanks for your time and support :D
$this->db->select('c.*');
$this->db->from('categories as c');
$this->db->join('videos as v', 'c.c_name = v.v_category', 'right');
return $this->db->get()->result_array();
For your code to work you need two changes:
First you join type should be a "left join". Than way you still will get a count result (0) even if a category has no videos yet.
Second you need to group your results to be able to use the aggregate function count().
Try with this:
$this->db
->select('categories.c_name, COUNT(videos.id) as num_videos')
->from('categories')
->join('videos', 'categories.c_name = videos.v_category', 'left')
->group_by('categories.c_name');
Also you should reconsider your DB design. If you have id columns in both tables (wich I assume are the primary key) then you should define the relationship between the tables (foreign keys) using the id column, not the name.

Symfony2, querybuilder's Doctrine request

I have a basic table article linked to a table category by id (classic), I would like to make a doctrine request to retrieve only 5 articles by category (for all categories)
This request return me all articles of categories, I just want 5 for each
public function getArticlesAndCategs(){
$qb = $this->createQueryBuilder('c')
->orderBy('c.id','DESC')
->leftJoin('c.articles', 'a')
->addSelect('a')
->addOrderBy('a.id','DESC')
->getQuery();
return $qb->execute();
}
Can you help me for that?
thank you
This is no easy task unfortunately. At least I haven't found a satisfying solution yet (satysfying for me ;) ).
Three things you can do:
Iterate through the categories and retrieve 5 posts for each category. This of course cause that many db request as many you have categories.
Return all the categories with joined posts and iterate inside php. The drawback - you'll return all the posts, so if you have many, this can mean a lot of memory used.
If you don't have to stick to Doctrine you can try the solution from here: mySQL Returning the top 5 of each category

PHP & MySQL Join

I'm relatively new to coding in general, first year CS student and all of that. I'm using PHP to display a list of school classes categories and a list of assignment posts within each category. The desired output would be something like this:
CATEGORY 1
-Assign Post 1
-Assign Post 2
CATEGORY 2
-Assign Post 0
Using join gives me partial functionality, because it gets all the assign posts, but when I loop through and post the data it posts the Category title more than once. I then tried using group_by but it's only posting one post per category.
I'm not gonna post all my code, simply the db queries. I'll happily post the other code if you think the problem may be there.
//Select our classes, and get assignments from each class
$this->db->select('*');
$this->db->from('categories');
$this->db->join('assignments', 'categories.cat_id = assignments.assign_cat_id');
$this->db->group_by("cat_id");
//Return the classes and assignments in an array
$query = $this->db->get();
return $query->result_array();
Not to tricky right? Any help is of course appreciated :).
UPDATE
I have figured out that group_concat will post the assignment names, and then I can explode the results. Is this the only way, or is there a way to get it to post in a nice friendly array. The only reason I ask is I feel this adds un-necessary code in my view file, where I'm exploding the data.
Thank you!
Dont use group by ...use sort by Category id and while showing/or preparing array of data you can club records as per category..you cant achive this by single SQL

PHP: Doing the same query over and over?

I have a Product class that when instantiated, fetch data from "product" table, it also creates a "Category" object inside it (product->category) that fetch data from the category table. My problem is, what happens when multiple products from the same category are listed, multiple equal queries are made in order to create the category object, since they're from the same category. How to solve this?
Use a JOIN in your SQL:
SELECT *
FROM product
LEFT JOIN category ON product.category_id = category.id;
You'll have to build a layer that intercepts and/or caches your queries. When it sees you're requesting category with a certain ID, it should present that category from its cache, and if it isn't there, it should retreive and then cache it, so during a request the same row won't be queried more than once.
Doing this manually is a no-go, simply pick a decent ORM that will do this for you.
Use factory that creates the objects, and provide the option the data for product/category to be passed in the constructor. The factory loads the data for all products that will be listed, than instantiate the product objects, providing the already fetched data
Inside the loop executing sql query is not good practice.
What you can do is left join product table with the category tabel.
$select = SELECT *
FROM product
LEFT JOIN category ON product.category_id = category.id;
Then execute query and then classify your products into category basis
like you can run loop
$result = mysql_query($select);
while($data = mysql_fetch_assoc($result)){
$product_info['product_name'] = $data['product_name'];
$product_info['price'] = $data['product_price'];
$product[$data['category']][$data['product_id']] = $product_info;
}
This loop will do the magic that it will help you to classify products falling under same category.
And you can display your data in more effective way.

Mysql parent child tables reducing database calls or merge in PHP

If I had 2 tables, say blog_category and blog, each "blog" can belong in a particular category only so a 1-1 relationship based on a key called "blog_category_id".
Now in my code I would do something like:
//Loop through categories such as
foreach($categories as $cat):
//then for each category create an array of all its posts
$posts = $cat->getPosts(); // This would be another DB call to get all posts for the cat
//do stuff with posts
endforeach;
Now to me this seems like it could end up quite expensive in terms of DB calls depending on the size of $categories. Would this still be the best solution to do this? Or would I be able to do something in the code and first retrieve all the categories, then retrieve all the blogs and map them to their corresponding category via the id somehow? This would in theory be only 2 calls to the DB, now size wise the result set for call 2 (the blogs) would definitely be larger, but would the actual DB call be as expensive?
I would normally go for the first option, but I'm just wondering if there would be a better way of approaching this or is it more likely that the extra processing in PHP would be more costly in terms of performance? Also specifically from an MVC perspective, if the model returns the categories, but it should also return the corresponding blogs for that category, I'm not sure how best to structure this, from my understanding, shouldn't the model return all the data required for the view?
Or would I be better off selecting all categories and blogs using inner joins in the first query and create the output I need of this? Perhaps by using a multi-dimensional array?
Thanks
You can use a simple SQL query to get all categories and posts like the following:
SELECT *
FROM posts p
JOIN categories c ON c.id = p.blog_category_id
ORDER BY c.category_name ASC,
p.posted_date DESC
Then when you loop over the returned records assign the current category id to a variable, which you can use to compare against the next records category. If the category is different then print the category title before printing the record. It is important to note that for this to work you need to get the posts ordered by category first and then post so that all posts in the same category are together.
So for example:
$category_id = null;
foreach($posts as $post) {
if($post['blog_category_id'] != $category_id) {
$category_id = $post['blog_category_id'];
echo '<h2>' . $post['category_name'] . '</h2>';
}
echo '<h3>' . $post['post_title'] . '</h3>';
echo $post['blog_content'];
}
Note: as you have not posted up the schema of these two tables I have had to make up column names that are similar to what I would expect to see in code like this. So the code above will not work with your code without some adjustments to account for this.
The best solution depends on what you are going to do with data.
Lazy loading
Load data when you need it. It's a good solution when you have, for instance, 20 categories and you load posts for only 2 of them. However, if you need to load posts for all of them it won't be efficient at all... It's called a n+1 queries (and it's really bad).
Eager loading
On the other hand, if you have to access to almost all of your posts, you should do an eager loading.
-- Load all your data in a query
SELECT *
FROM categories c
INNER JOIN posts p ON c.id = p.category_id;
// Basic example in JSON of how to format your result
{
'cat1': ['post1', 'post2'],
'cat2': ['post5', 'post4', 'post5'],
...
}
What to do?
In your case I would say an eager loading because you load everything in a loop. But if you don't access to the most of your data, you should re-design your model to perform a lazy loading in such a way that the SQL query to load posts for a specific category is actually performed when a view try to access them.
What do you think?

Categories