Codeigniter - selecting children and parents from db - php

I want to pull from my database records corresponding to parent_id, like this:
function getChildren($id, $parent_id) {
$q = $this->db->select('id, name, slug, plat');
$q = $this->db->from('games');
$q = $this->db->where('parent_id',$id);
$q = $this->db->or_where('id',$parent_id);
$q = $this->db->get();
return $q->result_array();
}
It - if it's a children game - get parent_id and search for a game with such id and for other games that has parent_id same as this one. If it's the parent game, it only looks for games with parent_id same as it's id.
The problem is... it's not always working. I have four games in db:
id | parent_id | title
15 | 0 | Abe
19 | 15 | Abe
20 | 0 | RE2
21 | 20 | RE2 DS
First two works, last two - only children (id = 21) shows parent.

You likely could not do that with a relation database. RDBMS are not intended to manage any form of trees.
You can in some simple case, like one-level hierarchy, but as soon as it becomes more complex, it's getting messier and messier.
Keeping your structure, you have to make ONE JOIN per LEVEL, and that means knowing the depth in advance.
A solution to store trees in database is called Nested Tree, it basically stores interval for in each rows, but it is a bit complex to implement by yourself.
Take a look at Wikipedia explanation. There are however library which allows you to programmatically abstract such operations.

Use this query
select
*
from games as g
where parent_id = 0
union all
select
l.*
from games r
left join (select * from games) as l on r.id = t.id
where
r.parent_id != 0

Just after posting my problem, I tried another solution. Funny, I sit with this for more than an hour and after wasting your time, I came with the solution ;)
Anyway, here it is:
function getChildren($id, $parent_id) {
$q = $this->db->select('id, name, slug, plat');
$q = $this->db->from('games');
$q = $this->db->where('parent_id',$id);
$q = $this->db->or_where('id',$parent_id);
$q = $this->db->or_where('parent_id',$parent_id);
$q = $this->db->where('id !=', $id);
$q = $this->db->get();
return $q->result_array();
}

Related

Mysql sub-cat and parent cat with posts [Database schema]

Okay so I have a simple category table and a separate posts table easy right but when the user posts a post I wast think should I store both the sub and parent cat in the posts table but would that not be a lot of data duplication so I instead just store the sub_cat then I use a few PHP functions to query the database for the primary cat and its name.
categories table
ID | cat_name | main_cat
1 | Dinner | 0
2 | Chicken | 1
posts table
ID | title | sub_cat | fields that are not related to Q
1 | test | 2 |
Get parent(main) category
$sub_cat = is from a selection query that gets posts and their sub_cats
function main_cat($sub_cat){
require("conn_posts.php");
$stmt = $conn_posts->prepare("SELECT `main_cat` FROM `cats` WHERE `ID` = ?");
$stmt->bind_param("s", $sub_cat);
$stmt->execute();
$stmt_results = $stmt->get_result(); // get result
while($row_get = $stmt_results->fetch_assoc()){
if($row_get['main_cat'] == 0){
return $sub_cat;
}elseif($row_get['main_cat'] !== ""){
return $row_get['main_cat'];
}
}
}
This function gets any category name as long as the id is valid
function cat_name($cat_number){
require("conn_posts.php");
$stmt = $conn_posts->prepare("SELECT `cat_name` FROM `cats` WHERE `ID` = ?");
$stmt->bind_param("s", $cat_number);
$stmt->execute();
$stmt_results = $stmt->get_result(); // get result
$row_get = $stmt_results->fetch_assoc();
if($stmt_results->num_rows <= 0){
return 0;
}elseif($stmt_results->num_rows == 1){
return $row_get['cat_name'];
}
}
My question is is this a good way to process my posts sub-category and parent category are there better ways of doing what I am currently doing? eg. is my database schema good(by good I mean is it better to just include the parent cat id in the posts table than to do the PHP server-side processing)?
Your database schema is good: it doesn't include any replication, I wouldn't change it. The way you're handling fetching the categories in PHP isn't really optimal though: you should almost always aim to minimize the number of queries as it (in general) will affect performance more than the complexity of a query.
If you're running MySQL 8+, a great way to do this is with a recursive CTE; it will allow you to fetch all parents with one query:
WITH RECURSIVE cte AS (
SELECT id, cat_name, main_cat, 0 as depth FROM categories WHERE ID=3
UNION ALL
SELECT categories.id, categories.cat_name, categories.main_cat, cte.depth+1 as depth
FROM cte inner join categories
ON cte.main_cat = categories.id
)
SELECT cat_name FROM cte order by depth ASC
The number '3' in that query can be replaced by the category you're trying to retrieve. You can check this DB fiddle for a live example. If I see your code, incorporating it into your PHP should be fairly trivial. If not, leave a comment and I'll try to expand.

Join 1 Row to Multiple Rows in PDO

Here is my scenario:
Database Name: Children
+-------------+---------+---------+
| child_id | name | user_id |
+-------------+---------+---------+
1 Beyonce 33
2 Cher 33
3 Madonna 33
4 Eminem 33
Database Name: Parents
+-------------+---------+---------+
| parent_id | child_id | parent_name |
+-------------+---------+---------+
1 1 Obama
2 1 Michelle
3 4 50cents
4 4 Gaga
Desired Output:
+-------------+---------+---------+
| child_id | name | parent Name |
+-------------+---------+---------+
1 Beyonce Obama (Row 1) Michelle (Row 2)
PHP SQL Query in PDO:
$sql = "SELECT Children.child_id, Children.name, Parents.parent_name
FROM Children
LEFT JOIN Parents
ON Children.child_id = Parents.child_id
WHERE Children.user_id = ?
";
$stmt = $db_PDO->prepare($sql);
if($stmt->execute(array($userId))) // $userId defined earlier
{
// Loop through the returned results
$i = 0;
foreach ($stmt as $row) {
$fetchArray[$i] = array (
'childId' => $row['child_id'],
'childName' => $row['name'],
'parentName' => $row['parent_name'],
// How do I save the multiple parents from other rows here ????
);
$i++;
}
}
How can I run a query that Joins 1 row to multiple rows in second table in PDO? I have read other topics here but I am unsure. Is it easier to add a second query that gets the linked parents for each child_id separately in a loop? I am worried that will be too much query. Can someone help me solve this?
Well, took me some fiddling to test it all out but here you go.
Unfortunately one cannot easely pivot tables in mysql but there are alternatives.
http://sqlfiddle.com/#!9/1228f/26
SELECT GROUP_CONCAT(
CONCAT_WS(':', Parents.parent_id,Parents.parent_name) ) FROM Parents where Parents.child_id=1
;
SELECT
Children.child_id,
Children.name,
GROUP_CONCAT(
CONCAT_WS(':', Parents.parent_id,Parents.parent_name) ) as parents
FROM
Children
LEFT JOIN Parents
ON Children.child_id = Parents.child_id
WHERE Children.user_id = 33
Group by Children.child_id
This query uses the group concat to concatenate all resulsts we want into a colon seperated string with the values we want, and comma's between the individual fields.
We could do some tricky magic to make them individual fields but that would break our php because we wouldnt know how much fields each query would return(adopted, orphan, no known parents, etc...)
In php you could feed them into an object
$parents = array();
$loop1 = explode(',',$row['parents']);
foreach($loop1 as $parentset) {
$parentdetail = explode(":",$parentset);// decide yourself how much detail you want in here... I jsut went with name and id.
$parent = new stdClass();
$parent->id = $parentdetail[0];
$parent->name = $parentdetail[1];
array_push($parents,$parent);
}
var_dump($parents);
Execute the below query . You will get the output as required, i just used the group by which will group the records as per the selected column
select a.child_id, name ,group_concat(parent_name) from children a, parents b where a.child_id =b.child_id group by a.child_id
HI this query works only if you are passing child id ,
select a.child_id, name ,group_concat(parent_name ) parent_name from children a, parents b where a.child_id =b.child_id and a.child_id=1
here i am using a function called group_concat which is used for concatinating the rows.It automatically takes the rows whose count is greater than 1.So no need of the extra code again

Creating a mysql query syntax with JOINING tables

I'm having trouble getting some data.... I was wondering if someone can help me,
I have 4 tables (likes, follow, comment, users)
I want to be able to populate my page when a user likes/comments/follows/etc... (if a user is following a particular user).
likes
idlikes idusers iditem
1 1 5
2 2 4
3 2 22
follow
idfollow idusers_follower idusers idbusiness
1 1 2
2 1 3
3 1 4
4 4 2
5 4 1
comment
idcomments idusers text
1 1 asfd
2 2 safd
users
idusers
1
2
3
4
For example if I am id user #1, I'm following users #2, #3, #4
My page would populate to show:
#2 likes item #4, #22.
#4 is following #2 (because I'm following #4, this is why its showing)
#2 comments "safd"
I'm not sure what is the best way to display this? I currently have multiple functions querying on table at a time, and I'm working on merging the arrays together? Or should I use join tables? Which I'm trying now...
Get users that I'm following.
$feeds = new feed();
$meID = 1;
$query = "SELECT idusers FROM follow WHERE iduserse_follower = ?";
$users = $dbh -> prepare($query);
$users -> execute(array($meID));
while($following = $users -> fetch(PDO::FETCH_ASSOC)){
$follow = $following['idusers']; //This will get all of useres I'm following
$populate = $feeds->feed_all($follow); // from function
}
Query
class feed()
{
public function feed_all($idusers)
{
// SYNTAX HELP //////////////////////
$query = "SELECT
f.idusers_follower,
f.idusers,
l.iditem,
c.text
FROM follow f, users u
JOIN likes l
ON l.idusers = f.idusers
JOIN comment c
ON c.idusers = f.idusers
WHERE f.idusers_follower = ? AND f.idusers_follower = l.idusers AND f.idusers_follower = c.idusers AND f.idusers = u.idusers"
$pop = $dbh->prepare($query);
$pop ->execute($idusers);
// while loop to return value
while($row = $pop -> fetch(PDO::FETCH_ASSOC))
{
$feed_data[]=$row;
}
return $feed_data;
}
}
This is where I'm stuck. :( I'm not even sure if I'm doing the statement right?
++++++++++ EDIT: ++++++++++++
I have edited to add idbusiness
Now since I'm following #4, it would also show up that #4 is following #1.
Your current approach of performing three separate queries is as good as any; you can combine them into a single resultset using UNION, which would be useful if you wanted to sort the combined results by some field (e.g. activity timestamp) and/or limit the combined results:
SELECT idusers, 'likes' AS what, likes.iditem AS detail
FROM likes JOIN follow USING (idusers)
WHERE follow.idusers_follower = 1
UNION ALL
SELECT f1.idusers, 'follows', f2.idusers
FROM follow f1 JOIN follow f2 ON f1.idusers = f2.idusers_follower
WHERE f1.idusers_follower = 1
UNION ALL
SELECT idusers, 'commented', comment.text
FROM comment JOIN follow USING (idusers)
WHERE follow.idusers_follower = 1
See it on sqlfiddle.

Tree Traversal recursive calculation

We have users, questions and unlimited levels of categories. The users can get some points from questions. Questions can have multiple categories.
What I want to do is to calculate the top users per category: It's simply total points taken from the questions under that category AND it's sub-categories too.
So, I have these tables:
questions
--------------
id
title
question
categories
--------------
id
parent_id
category
lft
rgt
question_categories
--------------
question_id
category_id
users
--------------
id
username
user_points
--------------
id
user_id
question_id
point_type
points
user_category
--------------
user_id
category_id
points
What I want to do is to calculate user_category.points value.
Summing up the points for each category is easy but including the sub-categories is getting complicated.
What might be the best way to do this?
Example calculation:
Let's say the categories are:
Programming
PHP
Zend Framework
Symfony
Java
Ruby on Rails
Assume that the user got 3 points from Zend Framework, 2 points from PHP, 5 points from java and 1 point from Rails. The points for this user per categories will be:
Programming 11 (5+5+1)
PHP 5 (2+3)
Zend Framework 3
Symfony
Java 5
Ruby on Rails 1
Perhaps it would be best to use tags instead of a hierarchy. For instance, anything with a "Zend Framework" will also have "PHP" and "Programming" tags. This also helps when some categories can appear in multiple places. For instance, I can use ajax in jQuery and also Javascript. Then, add 1 to each tag listed in the category for the user.
I would create a user_categories table in which I would store 3 values: user_id, category_id and user_score. It's easy to maintain (need only to INSERT or UPDATE) and it's also easy to query for top-users of every category.
If you're only going to sum per top-level category, then you should add a field to your categories table called root_id (holding the id of the transitive parent of the category).
Then your sum would be calculated as:
select up.user_id, ctg.root_id, sum(up.points)
from user_points up
join question_categories qc on up.question_id = qc.question_id
join categories ctg on qc.category_id = ctg.id
group by up.user_id, ctg.root_id
This php and SQL should get you the top 3 users for each category including sub categories:
$query = "SELECT id, parent_id FROM categories";
$parent = array();
...fetch mysql data loop depending on what connection you use, mysqli or pdo...
{
$parent[$result['id']] = $result['parent_id'];
}
$childs = array();
foreach($parent as $id => $parrent_id)
{
$childs[$parrent_id][$id] = $id;
$next_parrent_id = $parrent_id;
while($next_parrent_id = $parent[$next_parrent_id])
{
$childs[$next_parrent_id][$id] = $id;
}
}
foreach($parent as $id => $parrent_id)
{
$current_categories = array($id => $id) + $childs[$id];
$query = "SELECT user_id, username, SUM(points) AS total_points
FROM user_points
LEFT JOIN users ON (user_id = users.id)
LEFT JOIN question_categories USING (question_id)
WHERE category_id IN (" . implode(', ', $current_categories). ")
ORDER BY total_points DESC
LIMIT 3";
...fetch mysql data loop...
}

MySQL INNER JOIN. Pull data from one table with data from another

I am just getting started in learning how to do INNER JOINS correctly and I can't think of the best/easiest way to do this.
I am building a url shortener and I am trying to build a query that will get all long_url.destination's matching a slug "test". One slug might point to multiple long_url.destination's(URL shuffling, GEO matching, etc...). So I need the slug to get all long_url.destination's with the same short_url.slug.
Before I was running another query to get the short_id from the slug, then running another query to select all rows in long_url that had a matching short_id.
I think it might be quicker if I use an inner join, but I am unsure how to properly set it up.
I want to get all destination columns in table long_url with only the slug data in short_url without having to run a separate query to get the short_id from the slug.
Table: short_url
Columns: short_id | slug | enabled | timestamp
example: 1 test 1 1323343922
Table: long_url
Columns: long_id | short_id | destination | geo | enabled | timestamp
example: 1 1 http://www.test.com US 1 132334922
example: 2 1 http://www.test.co.uk UK 1 132334922
I got this so far:
SELECT destination, geo FROM long_url INNER JOIN short_url
ON long_url.short_id = short_url.short_id WHERE enabled = 1;
function get_long_urls($slug) {
$query = "SELECT....";
$stmt = $db->prepare($query);
$stmt->execute(array(':slug' => $slug));
$results = $stmt->fetchAll(PDO::FETCH_ASSOC);
return (array) $results:
}
example $results = array(
'http://www.test.com' => 'US',
'http://www.test.co.uk' => 'UK',
);
Thanks for any help.
select long_url.destination
, long_url.geo
from long_url
inner
join short_url
on long_url.short_id = short_url.short_id
where short_url.slug = :slug
and long_url.enabled = 1
You don't need to qualify all column names like I did, because in this particular query there wasn't any ambiguity. All I really did is add a bound parameter placeholder.
SELECT destination, geo FROM long_url LEFT JOIN short_url
ON (long_url.short_id = short_url.short_id) WHERE enabled = 1

Categories