Count total child node on left and right grouped by ranks
Asked Answered
N

1

8

I am working on a project that rewards people base on referrals (MLM)

I have been able to count the total number of child nodes on both left and right side but now I need to be able to update the ranks of users when they have a certain number of users with certain ranks below them on both sides. (I will explain better below:

User Table

 id | name  | parentID| side | rank   |
 4  | Dan   |         |      | starter|
 5  | Chris |   4     | left | starter|
 6  | James |   4     | right| starter|
 7  | Tybe  |   5     | left | starter|
 8  | Rose  |   5     | right| starter|
 9  | Paul  |   6     | left | starter|
10  | Zach  |   6     | right| starter|

Tree table

userID | left | right| leftCount | rightCount|
 4     |  5   |  6   |    3      |    3      |
 5     |  7   |  8   |    1      |    1      |
 6     |  9   |  10  |    1      |    1      |
 7     |      |      |    0      |    0      |
 8     |      |      |    0      |    0      |
 9     |      |      |    0      |    0      |
 10    |      |      |    0      |    0      |

Below is the tree generated from this table

enter image description here

How i update the leftCount and rightCount when a user registers

    $referralid; //who is referring the current user
    $side; //the side the user is to fall under, either left or right

    $temp_referralid = $referralid; 
    $temp_sideCount = $side.'Count'; //leftCount or rightCount

    $temp_side = $side;
    $total_count=1;
    $i=1;
    while($total_count>0){
        $stmt = $db->prepare("SELECT * from tree WHERE id=:id");
        $stmt->bindValue(':id', $temp_referralid);
        $stmt->execute();
        $r = $stmt->fetch(PDO::FETCH_ASSOC);
        $current_temp_sideCount = ($r[$temp_sideCount]+1);

       //This will add (+1) to the leftCount or rightCount 
        //of the referral depending on the side the user 
       //choose to fall under.
        $sql ="UPDATE `tree` SET `$temp_sideCount`=:count WHERE `id` = :id";
        $stmt = $db->prepare($sql);
        $stmt->bindValue(':count', $current_temp_sideCount);
        $stmt->bindValue(':id', $temp_referralid);
        $stmt->execute();

    //if the user has someone that referred them as 
    //only the last person at the top has no referral

    if($temp_referralid!=""){
        //change the $referralid to the person that referred the person 
        //referring this user. this is where the loop will go through 
        //to the last person maintaining the side upward
        $next_referralid = $this->getReferringId($db, $temp_referralid);
        $temp_side = $this->getReferringIdSide($db, $temp_referralid);
        $temp_sideCount = $temp_side.'Count';
        $temp_referralid = $next_referralid;

        $i++;

        }else{
           $total_count=0;
            }

    }

}

Functions used

 getReferringId($db, $id) //gets the referringId of the user passed as 
                         //param from the user table
 getReferringIdSide($db, $id) //gets the side which the user 
                              //is on (left or right) from the user table

All this is working just fine but then I need to implement something and I haven’t found the best way around it.

I need to keep changing the rank for each user if they have attained a stage. see below and example:

 stage 1: starter //just registered
 stage 2: grow // the person has leftCount=3 and rightCount=3
 stage 3: growbig //the person has 7 - grow user on the left 
                  //and 7- grow user on the right 
 state 4: growbigger //the person has 7 - growbig on left and 7 growbig
                     //on the right 

Up to stage 2, I have no problem but upwards is where I cant get my hands on the right logic

Update for example: The numbers of growbig's can come from anywhere on the legs, it shouldn’t just be direct nodes, just a count of ranks all below that user on each sides

UPDATE: Re-asking in a clearer term and specifications

its a binary tree (2:2) which means a person can only have two people directly under them (one on the left and another on the right.

enter image description here

With the picture above my table looks like this

Tree table

userID | left (userid) | right (userid)|  rank
 8     |     4         |  12           |
 4     |     2         |  6            |
 12    |     10        |  14           |
 2     |     1         |   3           |
 6     |     5         |   7           |
 10    |     9         |  11           |
 14    |     14        |  15           |

Note: its not compulsory for a user to have anyone under him on any side or both. it means if a user have nobody under him then the tree (branch) ends there, if he has one person directly on the left and none on the right it means the left branch can continue to grow.

The logic to grade each user base on their growths and how they have managed to balance the growth on each side is the problem.

The logic

Rank 1: supervisor: user must have at 3 users on its right branch and 3 users on the left branch.

Rank 2: controller: user must have 7 users who are 'supervisors' on it's left and 7 users who have become supervisors on the right too.

Rank 3: Senior Controller: user must have 7 users who are 'controller' on the left branch and have 7 'controller' on the right too.

Rank 4: Ambassador: user must have 7 users who are senior controller on its left and 7 senior controllers on the right

Rank 5: Senior Ambassador: user must have 7 users who are ambassadors on the left and same on the right.

Rank 6: Grand Ambassador: user must have 7 users who are senior ambassadors on his both sides.

Explanation: let me pick on rank and explain it:

Rank: Ambassador- if user with ID 3 has 45 people on its right hand side and 7 of the people on its right branch are senior controllers and also on the left he has 67 people and 7 are already senior controllers then user with ID 3 should be upgraded to 'ambassador'

@blag

Nihility answered 21/9, 2017 at 15:7 Comment(12)
Can you change your table schema ? this one is far from the best for this case ...Venterea
@Venterea sure I can, do you have a suggestion?Nihility
ok, yes I think I've got one.Venterea
Question, by " 3right /3 left" you mean 6 children ?Venterea
@blag yes but they must be balance ...left side must have at least 3 and right side at least 3Nihility
What >_<" ; I don't really get you case : you can only have 2 children ?Venterea
I've updated my answer, take a look ;)Venterea
@blag I am testing it out, thanksNihility
Can you upgrade to MariaDB 10.2 or MySQL 8? That way you could write a "recursive CTE".Cruck
@Rick James I can upgrade to MySQL 8 if that's the only way I can get this to workNihility
@Nihility - I don't know if it is the only way. But I suspect that it might be more straightforward to use a CTE. And it may need some subqueries and pivoting. It's not a trivial query!Cruck
New version that fully answer you question. I've done the add trigger that check the <=2 child and do the update for growbig, growbigger is exactly the same, just change the 'grow' in the join.Venterea
V
3

This is more likely how I would take this problem (using http://mikehillyer.com/articles/managing-hierarchical-data-in-mysql/ ):

The new table schema is :

'id'| 'name' | 'left'| 'right'| 'rank'   
4   | 'Dan'  | 1     | 14     | 'starter'
5   | 'Chris'| 2     | 7      | 'starter'
6   | 'James'| 8     | 13     | 'starter'
7   | 'Tybe' | 3     | 4      | 'starter'
8   | 'Rose' | 5     | 6      | 'starter'
9   | 'Paul' | 9     | 10     | 'starter'
10  | 'Zach' | 11    | 12     | 'starter'

The full version :

Note, I use the following value to avoid using a bigger dataset

-- on each side 
set @grow = 1 // -- 1 child R & L
set @growbig = 2 // -- 2 grow child R & L

SQL Fiddle

PROCEDURE

CREATE PROCEDURE p(IN p_parent varchar(15), IN p_children varchar(15))
BEGIN
  IF NOT EXISTS (
    select parent.*, count(distinct dc.id)  direct_child
    from u parent
    left join u as dc on(parent.left=dc.left-1 or parent.right=dc.right+1)
    WHERE parent.name = p_parent
    group by parent.id
    having count(distinct dc.id) =2
    )
  THEN
      SET @myLeft =(SELECT `left` FROM u WHERE name = p_parent);

      UPDATE u SET `right` = `right` + 2 WHERE `right` > @myLeft;
      UPDATE u SET `left` = `left` + 2 WHERE `left` > @myLeft;

      INSERT INTO u(`name`, `left`, `right`) 
      VALUES(p_children, @myLeft + 1, @myLeft + 2);

  END IF;
END
//


call p('Tybe','Marjorie') //
call p('Tybe','Vernon') //
call p('Rose','Marc') //
call p('Rose','Peter') //
call p('Marc','Lily') //
call p('Marc','Ignotus') //
call p('Ignotus','Aragorn') //
call p('Ignotus','Arwen') //
call p('Peter','Chase') //
call p('Peter','Foreman') //
call p('Chase','Pétunia') //
call p('Chase','Dudley') //
call p('Foreman','Bob') //
call p('Foreman','Taub') //
call p('Paul','Lisa') //
call p('Paul','Bilbo') //
call p('Lisa','House') //
call p('Lisa','Gandalf') //
call p('Gandalf','Frodo') //
call p('Gandalf','Legolas') //
call p('House','Thirteen') //
call p('House','Wilson') //
call p('Thirteen','Ginny') //
call p('Thirteen','Harry') //
call p('Wilson','Kutner') //
call p('Wilson','Master') //
call p('Master','Adams') //
call p('Master','Park') //
call p('Zach','Ary') //

grow

set @grow = 1 //
update u
set rank = 'grow'
where u.id in (
    select id from ( 
        select id 
        from (
            select p.id id, p.name name, lc.id lcid, null rcid
            from  u p
            inner join u l on (p.left = l.left-1 and p.right <> l.right+1)
            inner join u lc on (lc.left >= l.left and lc.right <= l.right)
            inner join u r on (p.right = r.right+1 and p.left <> r.left-1)

            union all

            select p.id id, p.name name, null lcid, rc.id rcid
            from  u p
            inner join u l on (p.left = l.left-1 and p.right <> l.right+1)
            inner join u r on (p.right = r.right+1 and p.left <> r.left-1)
            inner join u rc on (rc.left >= r.left and rc.right <= r.right)
        ) p
        group by p.id
        having 
            count(distinct lcid) >= @grow
            and count(distinct rcid) >= @grow
    ) z
)
//

growbig

set @growbig = 2 //
update u
set rank = 'growbig'
where u.id in (
    select id from ( 
        select id 
        from (
            select p.id id, p.name name, lc.id lcid, null rcid
            from  u p
            inner join u l on (p.left = l.left-1 and p.right <> l.right+1)
            inner join u lc on (lc.rank ='grow' and lc.left >= l.left and lc.right <= l.right)
            inner join u r on (p.right = r.right+1 and p.left <> r.left-1)

            union all

            select p.id id, p.name name, null lcid, rc.id rcid
            from  u p
            inner join u l on (p.left = l.left-1 and p.right <> l.right+1)
            inner join u r on (p.right = r.right+1 and p.left <> r.left-1)
            inner join u rc on (rc.rank ='grow' and rc.left >= r.left and rc.right <= r.right)
        ) p
        group by p.id
        having 
            count(distinct lcid) >= @growbig
            and count(distinct rcid) >= @growbig
    ) z
)

//

Query 1:

-- output parent that have both right and left child
select parent.*, count(distinct dc.id)  direct_child
from u parent
left join u as dc on(parent.left=dc.left-1 or parent.right=dc.right+1)
group by parent.id
having count(distinct dc.id) =2

Results:

| id |     name | left | right |    rank | direct_child |
|----|----------|------|-------|---------|--------------|
|  4 |      Dan |    1 |    72 | growbig |            2 |
|  5 |    Chris |    2 |    35 |    grow |            2 |
|  6 |    James |   36 |    71 |    grow |            2 |
|  7 |     Tybe |    3 |     8 |    grow |            2 |
|  8 |     Rose |    9 |    34 | growbig |            2 |
|  9 |     Paul |   37 |    66 |    grow |            2 |
| 13 |     Marc |   24 |    33 |    grow |            2 |
| 14 |    Peter |   10 |    23 |    grow |            2 |
| 16 |  Ignotus |   25 |    30 |    grow |            2 |
| 19 |    Chase |   17 |    22 |    grow |            2 |
| 20 |  Foreman |   11 |    16 |    grow |            2 |
| 25 |     Lisa |   40 |    65 |    grow |            2 |
| 27 |    House |   47 |    64 |    grow |            2 |
| 28 |  Gandalf |   41 |    46 |    grow |            2 |
| 31 | Thirteen |   58 |    63 |    grow |            2 |
| 32 |   Wilson |   48 |    57 |    grow |            2 |
| 36 |   Master |   49 |    54 |    grow |            2 |

Query 2:

-- show the tree
SELECT CONCAT( REPEAT('|...', COUNT(parent.name) - 1), node.id, ' ', node.name,' /',node.rank) AS name 
FROM u AS node,
        u AS parent  
WHERE node.left BETWEEN parent.left AND parent.right
GROUP BY node.name
ORDER BY node.left

Results:

|                                          name |
|-----------------------------------------------|
|4 Dan /growbig                                 |
||...5 Chris /grow                              |
||...|...7 Tybe /grow                           |
||...|...|...12 Vernon /starter                 |
||...|...|...11 Marjorie /starter               |
||...|...8 Rose /growbig                        |
||...|...|...14 Peter /grow                     |
||...|...|...|...20 Foreman /grow               |
||...|...|...|...|...24 Taub /starter           |
||...|...|...|...|...23 Bob /starter            |
||...|...|...|...19 Chase /grow                 |
||...|...|...|...|...22 Dudley /starter         |
||...|...|...|...|...21 Pétunia /starter        |
||...|...|...13 Marc /grow                      |
||...|...|...|...16 Ignotus /grow               |
||...|...|...|...|...18 Arwen /starter          |
||...|...|...|...|...17 Aragorn /starter        |
||...|...|...|...15 Lily /starter               |
||...6 James /grow                              |
||...|...9 Paul /grow                           |
||...|...|...26 Bilbo /starter                  |
||...|...|...25 Lisa /grow                      |
||...|...|...|...28 Gandalf /grow               |
||...|...|...|...|...30 Legolas /starter        |
||...|...|...|...|...29 Frodo /starter          |
||...|...|...|...27 House /grow                 |
||...|...|...|...|...32 Wilson /grow            |
||...|...|...|...|...|...36 Master /grow        |
||...|...|...|...|...|...|...38 Park /starter   |
||...|...|...|...|...|...|...37 Adams /starter  |
||...|...|...|...|...|...35 Kutner /starter     |
||...|...|...|...|...31 Thirteen /grow          |
||...|...|...|...|...|...34 Harry /starter      |
||...|...|...|...|...|...33 Ginny /starter      |
||...|...10 Zach /starter                       |
||...|...|...39 Ary /starter                    |

Query 3:

-- show parent + child data
select *,(l.right - l.left -1)/2 l , (r.right - r.left -1)/2 r from u p
inner join u l on (p.left = l.left-1 and p.right <> l.right+1)
inner join u r on (p.right = r.right+1 and p.left <> r.left-1)

Results:

| id |     name | left | right |    rank | id |    name | left | right |    rank | id |     name | left | right |    rank |  l |  r |
|----|----------|------|-------|---------|----|---------|------|-------|---------|----|----------|------|-------|---------|----|----|
|  4 |      Dan |    1 |    72 | growbig |  5 |   Chris |    2 |    35 |    grow |  6 |    James |   36 |    71 |    grow | 16 | 17 |
|  5 |    Chris |    2 |    35 |    grow |  7 |    Tybe |    3 |     8 |    grow |  8 |     Rose |    9 |    34 | growbig |  2 | 12 |
|  6 |    James |   36 |    71 |    grow |  9 |    Paul |   37 |    66 |    grow | 10 |     Zach |   67 |    70 | starter | 14 |  1 |
|  7 |     Tybe |    3 |     8 |    grow | 12 |  Vernon |    4 |     5 | starter | 11 | Marjorie |    6 |     7 | starter |  0 |  0 |
|  8 |     Rose |    9 |    34 | growbig | 14 |   Peter |   10 |    23 |    grow | 13 |     Marc |   24 |    33 |    grow |  6 |  4 |
| 13 |     Marc |   24 |    33 |    grow | 16 | Ignotus |   25 |    30 |    grow | 15 |     Lily |   31 |    32 | starter |  2 |  0 |
| 16 |  Ignotus |   25 |    30 |    grow | 18 |   Arwen |   26 |    27 | starter | 17 |  Aragorn |   28 |    29 | starter |  0 |  0 |
| 14 |    Peter |   10 |    23 |    grow | 20 | Foreman |   11 |    16 |    grow | 19 |    Chase |   17 |    22 |    grow |  2 |  2 |
| 19 |    Chase |   17 |    22 |    grow | 22 |  Dudley |   18 |    19 | starter | 21 |  Pétunia |   20 |    21 | starter |  0 |  0 |
| 20 |  Foreman |   11 |    16 |    grow | 24 |    Taub |   12 |    13 | starter | 23 |      Bob |   14 |    15 | starter |  0 |  0 |
|  9 |     Paul |   37 |    66 |    grow | 26 |   Bilbo |   38 |    39 | starter | 25 |     Lisa |   40 |    65 |    grow |  0 | 12 |
| 25 |     Lisa |   40 |    65 |    grow | 28 | Gandalf |   41 |    46 |    grow | 27 |    House |   47 |    64 |    grow |  2 |  8 |
| 28 |  Gandalf |   41 |    46 |    grow | 30 | Legolas |   42 |    43 | starter | 29 |    Frodo |   44 |    45 | starter |  0 |  0 |
| 27 |    House |   47 |    64 |    grow | 32 |  Wilson |   48 |    57 |    grow | 31 | Thirteen |   58 |    63 |    grow |  4 |  2 |
| 31 | Thirteen |   58 |    63 |    grow | 34 |   Harry |   59 |    60 | starter | 33 |    Ginny |   61 |    62 | starter |  0 |  0 |
| 32 |   Wilson |   48 |    57 |    grow | 36 |  Master |   49 |    54 |    grow | 35 |   Kutner |   55 |    56 | starter |  2 |  0 |
| 36 |   Master |   49 |    54 |    grow | 38 |    Park |   50 |    51 | starter | 37 |    Adams |   52 |    53 | starter |  0 |  0 |
Venterea answered 21/9, 2017 at 21:25 Comment(16)
I appreciate your input which is an eye opener to the right route. let me explain further: this is a binary tree 2:2 i need to know who referral someone (parent) i need to know on what side is the person attached to the parent (left or right), if a user is added to the system this will affect the total number of people of left side or right side of ALL the users above e.g if a user is attached to its parents on the left side then the total count of left side of the parent is +1 and if the parent is on the right side of its parent then the parent's right side count is +1 and so onNihility
@Nihility you need to allow only 2 children for each parent ? for now the problem with your schema is the recursive function you need to use every time you want to work with indirect children of N deep...Venterea
@Nihility update, I'll add the part for growbig tomorrow (gmt). With the having 2 child you can unsure you keep a 2x2 matrix before insert (use a stored procedure)Venterea
@Brag thanks, I will check this out, love the solutionNihility
you have been really helpful, any chance discuss further on this issue? Thanks.Nihility
@Nihility Of course, do you have another question ?Venterea
I will update the question to explain more/better. I was close to getting it to work but i suspect you didn't get my question well, so i will just ask again in clear terms.Nihility
i have updated the question, will appreciate your help a lot.Nihility
@Nihility I was wrong for the grow/controller, Take a look at sqlfiddle.com/#!9/ada1a/1 for the query and at this pastebin.com/WiwP3TVE for the tree it produce (note that I'm at 500 row and far from getting my first Senior Controller as I've only got 7 controller)Venterea
You have been very helpful, i do appreciate always. I have tested on my local server and it doesn't work there. i will explain everything here. On the tree table, lets take an example: user with ID 5 gets registered on the right hand of userID 2 automatically a row is now created for userID 5 with left NULL and right NULL. see my data example on a picture i66.tinypic.com/zuqhkg.png also take a look at the sql for that picture here sqlfiddle.com/#!9/95d2ec/3 i changed left to lleft and right to rrighit because my local server says those are reserved wordsNihility
from the sql on sqlfiddle.com/#!9/95d2ec/3 after running the first query for supervisor update i expect user id 1, 2, 3, 4, 5, 6, 7, and 8 to be update (rank) from starter to supervisors because they all have upto 3 starters on the right and left, but nothing happensNihility
please is there something I need to fix to get it to work? I have spent the last 15 hours trying to get it to workNihility
@Nihility Ok, that was what I feared :-/ You missed the whole point on this solution... To make it work you should change the table structure itself : You use ID to link parent and children, I don't. Why ? because with ID you MUST use recursive function. There is no way to find how many children a user have without travelling through every children he have. With my solution, you can pick children of a parent just by using a BETWEEN parent.right and parent.left.Venterea
so the left and right column are just the numbers of users (count) a user has on its left and right side?Nihility
I just looked at it and yes you are right but I can't seems to get how you then link parents to child while registering it into the database. Minding that some users can have no child or have just a child. Also the feature of being able to pick any free spot and register a member there will not work. As currently we want to be able to pick any free spot on the tree and register a user there. Thanks if you can help out. Appreciate youNihility
@Nihility take a look at this mikehillyer.com/articles/managing-hierarchical-data-in-mysql it'll explain you the whole point of this solution. My procedure p() allow you to insert only two children. And if you search a parent with less than 2 child, take a look at the first query here : sqlfiddle.com/#!9/a6b647/2Venterea

© 2022 - 2024 — McMap. All rights reserved.