I have a tree representation in mysql table based on id, depth, parent_id and path. Every root record within this table has a depth of 0, parent_id != null and path representation based on hex value of ID padded left with 0.
Every element of the tree is constructed by specifying depth = parent.depth + 1, path = parent.path + hex(id), parent_id = parent.id (pseudo code) for example:
id path depth parent_id assigned_user_id
------------------------------------------------------------
1 001 0 NULL NULL
2 002 0 NULL 1
3 001003 1 1 2
4 002004 1 2 1
5 001003005 2 3 2
6 001003005006 3 5 2
7 002004007 2 4 1
8 002004008 2 4 2
9 002004009 2 4 2
10 00200400800A 3 8 2
and so on...
The problem is how to get the records for specific user id limited to the shortest path in the same branch. For example for assigned_user_id = 2 retrive:
id path depth parent_id assigned_user_id
------------------------------------------------------------
3 001003 1 1 2
8 002004008 2 4 2
9 002004009 2 4 2
Instead of:
id path depth parent_id assigned_user_id
------------------------------------------------------------
3 001003 1 1 2
5 001003005 2 3 2
6 001003005006 3 5 2
8 002004008 2 4 2
9 002004009 2 4 2
10 00200400800A 3 8 2
SELECT t1.*
FROM atable t1
LEFT JOIN atable t2
ON t2.assigned_user_id = t1.assigned_user_id AND
t2.path = LEFT(t1.path, CHAR_LENGTH(t2.path)) AND
t2.id <> t1.id
WHERE t1.assigned_user_id = 2
AND t2.id IS NULL
If I get you right, it might be enough to exclude rows whose parent_id is among the ids selected. This is because if the parent and child is selected, they must be in the same branch. The parent's path will be shorter, therefore it's OK to exclude the child.
Something like:
SELECT *
FROM x
WHERE assigned_user_id = 2
AND parent_id NOT IN (SELECT id FROM x WHERE assigned_user_id = 2)
If you would have a tree like this (numbers are your assigned user ids):
A1 G2
/ \ / \
B2 C2 H2 I2
| \ | | \
D2 E2 L1 J2 K2
|
M2
B2, C2, G2 and M2 would be selected. I'm still not sure if this was your intention, though.
I would try something like this:
SELECT * FROM PATHS WHERE ASSIGNED_USER_ID = 2
AND NOT PARENT_ID IN (SELECT ID FROM PATHS WHERE ASSIGNED_USER_ID = 2)
Basically the idea is to select top parent nodes for the given user.
Idea behind this: B is shorter than A if A starts with B. Maybe there's something better than LIKE to do this "begins with".
SELECT a.* FROM node AS a
WHERE a.assigned_user_id = ?
AND NOT EXIST
(SELECT * FROM node AS b
WHERE b.assigned_user_id = ?
AND LENGTH(a.path) > LENGTH(b.path)
AND a.path LIKE CONCAT(b.path, '%') )
Both ? are mapped to the desired user id.
EDIT
Forgot to include the assigned_user_id. Changed the code.
2nd EDIT
Changed code to avoid the case of b=a.
Have you tried something like this?
select child.assigned_user_id, child.id
from node as child
left join node as parent
on child.path like CONCAT(parent.path, '%')
and child.assigned_user_id = parent.assigned_user_id
and child.id <> parent.id
group by child.assigned_user_id, child.id
having max(parent.id is null) = true
(Not sure it'll work exactly as above, but basically: to left join on the path in order to extract the full list of parents, and then to aggregate in such a way that you only keep the nodes without any parents when grouped by assigned_user_id.)
Related
I'm struggling to create a single query to retrieve all descendants for a given parent id.
I have the following table structure:
menu_id | parent_id | title
1 0 Text 1
2 0 Text 2
3 2 Text 2.1
4 2 Text 2.2
5 2 Text 2.3
6 0 Text 3
7 6 Text 3.1
8 6 Text 3.2
9 6 Text 3.3
10 6 Text 3.4
11 10 Text 3.4.1
12 10 Text 3.4.2
13 10 Text 3.4.3
14 10 Text 3.4.4
I want to achieve the following result, for given parent id 6:
menu_id
6
7
8
9
10
11
12
13
14
Yet, I'm not able to go trough all descendants, but to the first node..
This is the query that I managed to create until now:
SELECT T2.menu_id
FROM (
SELECT
#r AS _id,
(SELECT #r := menu_id FROM bo_admin_menu WHERE parent_id = _id) AS menu_id,
#l := #l + 1 AS lvl
FROM
(SELECT #r := 6, #l := 0) vars,
bo_admin_menu h
WHERE #r <> 0) T1
JOIN bo_admin_menu T2
ON T1._id = T2.parent_id
ORDER BY T1.lvl DESC
Could someone point me to the correct path?
In your case, why not just use like on the title column (ignoring parent column), like so:
select menu_id
from bo_admin_menu t2 join bo_admin_menu t1 on
t2.`title` like CONCAT(t1.title, '%')
where t1.menu_id = 6
order by t2.menu_id
Is it possible to limit number of records based on parent/child relationship;
I have a simple comments table:
id parent_id
==============
1 0
2 1
3 1
4 0
5 4
6 4
and my query:
SELECT
id,
parent_id
FROM
comments
ORDER BY parent_id LIMIT 0,5;
The problem is, since comments are nested, it will cut of in a middle of child comment. What I would like is, to select x number of items, but to include children as well.
SELECT kid.*
FROM 0_a AS kid
JOIN (
SELECT id FROM 0_a WHERE root_id = 0 LIMIT 5
) AS p ON kid.id = p.id OR kid.root_id = p.id
ORDER BY kid.id
I have a table categories which contains the columns ID & ParentID I would like to add a field called Level, which states which level in the category tree each category is.
I think i found my solution but it is in sql not mysql. So i have been converting it to the correct syntax. However, i think im missing a step. So, here is my code:
ALTER TABLE categories DROP Level;
ALTER TABLE categories ADD Level INT NULL;
UPDATE categories
SET Level = 0
WHERE ParentID IS NULL;
UPDATE categories AS A
INNER JOIN categories B ON A.ParentID = B.ID
SET A.Level = B.Level + 1
WHERE A.Level IS NULL AND
B.Level IS NOT NULL;
I think the problem may lie in the fact that in my DB The order of the categories do not come in any specific order, what i mean is as follows:
ID ParentID
2 NULL 0
4 55
7 2
.....more categories
55 2
So what i would like it do do is:
ID Parent Level
2 NULL 0
3 55 2
7 2 1
....
55 2 1
However, i think, but i might be wrong, is that i need to either order by ParentID first before i do the last operation, or my query is missformed.
I am not getting any errors however, but just not getting the results i am expecting this is what im getting;
ID Parent Level
2 NULL 0
3 55 NULL
7 2 1
....
55 2 1
Any ideas?
The question is how many levels do u have?
If they are 3 levels than u can do it like this
ALTER TABLE categories DROP Level;
ALTER TABLE categories ADD Level INT NULL;
UPDATE categories SET Level = 0 WHERE ParentID IS NULL;
UPDATE categories SET level = 1 where parentID = 2;
UPDATE categories SET level = 2 where parentID > 2;
I need to know if there is a possible way doing this with out subquery..
Here is my table structure:
id-name-father_id
1 joe 0
2 mark 0
3 muller 0
4 miki 2
5 timi 2
6 moses 2
7 david 1
8 momo 0
9 daniel 0
10 ermi 3
My table logic is
0 means he is not a child of some one
1+ mean that he is son of man in that row.
Note: if some one have a child, he still
will have 0 in father id (it's mean there is not grand-fathers in my table)
My query is :
SELECT id, name, count(id=father_id) as sons
WHERE father_id = 0
What I want to get is a list of non-children (father_id=0) and sum
the childrens it has.
Is there a way to get the results without a subquery?
This should do it (MySQL):
SELECT `parents`.`id`, `parents`.`name`, COUNT(`children`.*) AS sons
FROM `people` AS parents
LEFT JOIN `people` AS children ON `parents`.`id` = `children`.`father_id`
WHERE `parents`.`father_id` = 0
GROUP BY `parents`.`id`
According to Gary we need to add name to GROUP BY in other SQL databases:
SELECT `parents`.`id`, `parents`.`name`, COUNT(`children`.*) AS sons
FROM `people` AS parents
LEFT JOIN `people` AS children ON `parents`.`id` = `children`.`father_id`
WHERE `parents`.`father_id` = 0
GROUP BY `parents`.`id`, `parents`.`name`
We are joing the table with itself here. So we join all parents with their children.
This will lead to a result like that:
parents.id parents.name children.id children.name
1 joe 7 david
2 mark 4 miki
2 mark 5 timi
2 mark 6 moses
3 muller 10 ermi
8 momo - - # left join allows this line
9 daniel - -
But now we have each parent several times. So we are GROUP'ing the whole thing over the parent’s id, which will result in the following:
parents.id parents.name COUNT(children.*)
1 joe 1
2 mark 3
3 muller 1
8 momo 0
9 daniel 0
You should be able to do it without any joins or sub-queries as follows:
select case father_id when 0 then id else father_id end id,
max(case father_id when 0 then name end) name,
sum(sign(father_id)) sons
from table
group by case father_id when 0 then id else father_id
I have written a query which returns all records with some many-to-many joins correctly for the entire set or an individual article using WHERE a.id = ?
SELECT a.id, date_added, title, content, category_id, person_id, organization_id, c.name AS category_name, firstname, lastname, o.name AS organization_name
FROM articles AS a
LEFT OUTER JOIN articles_categories AS ac ON a.id=ac.article_id
LEFT OUTER JOIN categories AS c ON c.id=ac.category_id
LEFT OUTER JOIN articles_people AS ap ON a.id=ap.article_id
LEFT OUTER JOIN people AS p ON p.id=ap.person_id
LEFT OUTER JOIN articles_organizations AS ao ON a.id=ao.article_id
LEFT OUTER JOIN organizations AS o ON o.id=ao.organization_id
ORDER BY date_added
BUT!
I've hit a brick wall trying to work out how to limit the articles to a specific number of IDs, for working with pagination.
I'm ideally trying to use as simple and clear SQL statements as possible because I'm using the codeigniter framework with their active record class.
http://codeigniter.com/user_guide/database/active_record.html
Would really appreciate some help as I don't want to revert to using multiple queries for this as I've tried to reduce it down to a single query for database efficiency.
Have search around and tried some alternatives but nothing seems to work. Many thanks!
For example the results I return are like this
---------------------------------------------------------------------
id title category_id person_id organization_id
---------------------------------------------------------------------
1 test 1 1 1
1 test 2 1 1
1 test 1 2 1
1 test 1 1 2
1 test 5 1 1
1 test 8 1 1
1 test 1 4 1
1 test 1 4 2
1 test 1 1 1
2 test 2 2 1 1
2 test 2 1 2 1
2 test 2 1 1 2
2 test 2 5 1 1
2 test 2 8 1 1
2 test 2 1 4 1
2 test 2 1 4 2
I need the results like this so that I can create sub-arrays in the php like this:
$articles = $query->result_array();
$output = array();
foreach ($articles as $article) {
// set up article details
$article_id = $article['id'];
// add article details
$output[$article_id]['article_id'] = $article_id;
$output[$article_id]['date_added'] = $article['date_added'];
$output[$article_id]['title'] = $article['title'];
$output[$article_id]['content'] = $article['content'];
// set up people details and add people array with details if exists
if (isset($article['person_id'])) {
$person_id = $article['person_id'];
$output[$article_id]['people'][$person_id]['person_id'] = $person_id;
$output[$article_id]['people'][$person_id]['lastname'] = $article['lastname'];
$output[$article_id]['people'][$person_id]['firstname'] = $article['firstname'];
}
// set up organizations details and add organizations array with details if exists
if (isset($article['organization_id'])) {
$organization_id = $article['organization_id'];
$output[$article_id]['organizations'][$organization_id]['organization_id'] = $organization_id;
$output[$article_id]['organizations'][$organization_id]['organization_name'] = $article['organization_name'];
}
// set up categories details and add categories array with details if exists
if (isset($article['category_id'])) {
$category_id = $article['category_id'];
$output[$article_id]['categories'][$category_id]['category_id'] = $category_id;
$output[$article_id]['categories'][$category_id]['category_name'] = $article['category_name'];
}
}
But if I just use LIMIT (with offset etc) 1
the results I get are
---------------------------------------------------------------------
id title category_id person_id organization_id
---------------------------------------------------------------------
1 test 1 1 1
instead of
---------------------------------------------------------------------
id title category_id person_id organization_id
---------------------------------------------------------------------
1 test 1 1 1
1 test 2 1 1
1 test 1 2 1
1 test 1 1 2
1 test 5 1 1
1 test 8 1 1
1 test 1 4 1
1 test 1 4 2
1 test 1 1 1
which is my desired result.
OK, so finally I worked out how it is possible.
Thought i'd include it here in case anyone else has the same problem.
Changing this line
FROM articles AS a
to this
FROM (SELECT * FROM articles LIMIT 5,3) AS a
does what I wanted.
So, why don't you use OFFSET 0,10 and LIMIT *number_of_results* in the SQL Query? (if I understood the question)
Specific number of IDs... WHERE ID IN (2,4,6,8)... ?
Are you using codeigniter's pagination?
http://codeigniter.com/user_guide/libraries/pagination.html
You can easily limit the number of records that are being returned using the MySQL LIMIT clause. This can be achieved like the following with your sample query.
SELECT a.id, date_added, title, content, category_id, person_id, organization_id, c.name AS category_name, firstname, lastname, o.name AS organization_name
FROM articles AS a
LEFT OUTER JOIN articles_categories AS ac ON a.id=ac.article_id LEFT OUTER JOIN categories AS c ON c.id=ac.category_id
LEFT OUTER JOIN articles_people AS ap ON a.id=ap.article_id LEFT OUTER JOIN people AS p ON p.id=ap.person_id
LEFT OUTER JOIN articles_organizations AS ao ON a.id=ao.article_id LEFT OUTER JOIN organizations AS o ON o.id=ao.organization_id
ORDER BY date_added
LIMIT 10
Where 10 is the number of records you wish to display. The MySQL LIMIT clause allows you to specify a limit of the number of records and an initial offset. Like so:
LIMIT <offset>,<limit>
In your case <offset> would be the current page * the number of records on a page. <limit> would be the number of records you would like to display per page.