At first, this looks like a problem to solve with recursion; but with a few changes to your database schema it could be done with a single query. As far as database queries go, it's not the most efficient way to do it, but it still beats recursion.
The basis for this answer comes from a ruby on rails gem
The idea is to use an ancestry
field instead of parent_id
. The ancestry field contains a reference to all of a record's parents. So for the following tree:
1
2 3
4 5 6 7
8 9 10 11 12 13 14 15
The "ancestry" field would look like this:
Id Ancestry
--------------
1 null or ''
2 '1L'
3 '1R'
4 '2L 1L'
5 '2R 1L'
6 '3L 1R'
7 '3R 1R'
8 '4L 2L 1L'
9 '4R 2L 1L'
10 '5L 2R 1L'
11 '5R 2R 1L'
12 '6L 3L 1R'
13 '6R 3L 1R'
14 '7L 3R 1R'
15 '7R 3R 1R'
So now, getting the count and/or all the children on the left of ID 1 is simple:
SELECT COUNT(id) AS child_count FROM users WHERE ancestry LIKE '%1L%'
SELECT * FROM users WHERE ancestry LIKE '%1L%'
To get the users on the left of ID 2:
SELECT * FROM users WHERE ancestry LIKE '%2L%'
Want the children on the right of ID 1?
SELECT * FROM users WHERE ancestry LIKE '%1R%'
Edit
Just realized that the fuzzy like will get some bad results once ancestry
numbers get into the double digits. To solve this, use some delimiter in front of each ancestor, ie,
Id Ancestry
--------------
1 null or ''
2 '_1L'
3 '_1R'
4 '_2L_1L'
5 '_2R_1L'
6 '_3L_1R'
7 '_3R_1R'
8 '_4L_2L_1L'
9 '_4R_2L_1L'
10 '_5L_2R_1L'
11 '_5R_2R_1L'
12 '_6L_3L_1R'
13 '_6R_3L_1R'
14 '_7L_3R_1R'
15 '_7R_3R_1R'
and then include that delimiter in the query:
SELECT COUNT(id) FROM users WHERE ancestry LIKE '%_1L%'