views:

88

answers:

3

I will try my best to be both succinct and fully explanatory in this predicament.

On a site I manage, we allow a manager to view their "Recruiting Downline" which entails a list of all agents they personally recruited, as well as the recruits that particular agent (and so on and so on) brought to the Team.

For example:

  • I recruit two Agents, A and B.
  • A recruits two agents, C and D.
  • B recruits two agents, E and F.
  • D recruits two agents G and H.
  • C, E, and F do nothing.

alt text

In the database, every individual agent record has a field for 'referring agent', which lists their recruited agent.

  • As the top level, when I click "My Recruits", I am shown a list of all sub agents (because they ALL fall under my umbrella).
  • A is able to view C, D, G, and H.
  • B is only able to view E and F as they are his only downline recruits and they have brought nobody on board.

While this functionality works great it is flawed for two reasons:

Because of the way our PHP scripts are built, we are unable to sort the commission level data as a whole. Example: Even as the top man and I can see everybody, sorting by 'commission level' sorts my immediate agents by the criteria, then their downline as an item, then continues the sort based on my criteria. This is difficult to understand so to demonstrate, assume the table below displays the 'commission level' for ALL agents:

  • A, 7
  • B, 6
  • C, 5
  • D, 6
  • E, 5
  • F, 2
  • G, 5
  • H, 1

Note: an agent can NEVER recruit another agent at a higher level than they sit but they can recruit at ANY level below them (e.g. a 7 can recruit at 1,2,3,4,5,6 while a 3 can only recruit a 1,2).

FROM MY (high level) perspective,

While it would make sense for the data to be 'sorted by commission level' as: A, D, B, G, C, E, F, H - this is not the case.

Instead (view from top agent's perspective mind you) is: A, D, G, H, C, B, E, F

basically, every while loop depends on the DIRECT upline agent number to determine who falls next in line.

I understand this is 'very' difficult to understand but let me know if I can provide any additional understanding into our current 'sort' issue.

+1  A: 

Sounds like you're trying to implement tree like structures in your DB. Have you considered using using Celko trees:

http://dev.mysql.com/tech-resources/articles/hierarchical-data.html

Personally, I'd look to implement most of the ordering, selecting aspects of this kind of project in the DB. Note that Celko trees aren't really suitable for very large datasets.

Robin
@Robin - Thank you for the link. I only read through briefly initially and will focus more but at first glance, this appears to be an example or method when the parameters are 'known'. When a 'new' agent comes on board, we only know the direct parent (without doing several agent number lookups of course). I'm a bit stumped and hence the overall issue I'm having.
JM4
@Robin - see my exchange with @artefacto above, the sample link makes perfect sense but I dont feel it applies. You can have several items which are categorized as a televisions but only a few which are 'plasmas'. Everybody is classified as an 'agent' but there are ONLY specific levels
JM4
A: 

So the problem is that you don't store the "commission level" (which I take to be the number of nodes whose distance is < ∞) in the database?

You have two options:

  • Change your schema so that this is easily retrievable. See this article.
  • If it's not an option calculating it using only SQL in MySQL may not be possible because you don't have iterative queries (WITH RECURSIVE clause). You have to make multiple queries in PHP.
Artefacto
@artefacto - I do store commission levels in the DB for each agent. Again, if this were a single DB lookup and sort, it is no problem but your point about 'multiple lookups' is my direct issue - I am having to lookup every single agents referring parent and list out because of it.
JM4
see my comments below on the same link Robin posted. In the example they discuss, the total number of values when creating and placing the tables are known at the time of table creation. Mine are not. When a new agent is enrolled, we only know his parent's agent number at the time. Of course we can do several look-ups to find the PRIMARY parent) but that isn't the issue here. I am forced to use multiple queries to display the hierarchy as is.
JM4
@JM4 Calculating the number of reachable nodes from a given one is trivial with the nested set model (see link). It's just a matter of doing (right - left - 1)/2.
Artefacto
the numbering schema for nested nodes is unrealistic given that we may have 400 or 40,000 level 5 agents. It is unpredictable and unknown so it seems the parent model is the only way to use this.
JM4
@JM4 You can't do with just one SQL query without a schema change. Sorry. Use multiple queries or upgrade to PostgreSQL :p ... well, or use stored procedures.
Artefacto
@artefacto - I suppose the big difference (in my mind) between the Adjacency List Model (which I think applies here honestly) and the Nested Set Model is that the latter, a subportion represents the whole while it 'doesnt' necessarily in our example. An mp3 player is a portable electronic, which is an electronic device. In my sample, while a level 1's production will affect his entire upline, he/she only reports to his/her direct parent
JM4
@artefacto - your last post was my fear, unfortunately.
JM4
@JM4 The models are just two different ways to store the same graph. There's no other difference. It's just a matter of choosing a data structure over which most of the operations we do are efficient.
Artefacto
@artefacto - looking at your first sentence though - we DO in fact store the commission level for each individual agent.
JM4
@JM4 OK, I still didn't get what the "comission level" is. It certainly is not the number of nodes that are reachable from a given node.
Artefacto
It is the commission level an agent is paid at. Level 8 equals 45%, level 7 equals 40% and so on. The percentage is irrelevant, only that it indicates a 'more valuable' agent to us and he is usually more senior
JM4
@JM4 OK, I should reformulate. How do you calculate it?
Artefacto
@Artefacto - it is determined when the agent is simply input into our system. I have an extremely simple online form which takes (for simplicity sake) 4 fields: commission level (I input 1-8, its my call); agent name; agent number; referring agent number (always exists). All of the information listed is either collected or made up (agent no=123456789, referring agent no=987654321)
JM4
@JM4 OK, then I don't get your problem. What type of sorting are you unable to do? I'm sorry, I don't understand. Does this question help? http://stackoverflow.com/questions/3236434/sql-cluster-similar-values-of-column-b-yet-order-by-column-a
Artefacto
@artefacto - you keep treating the information like there is one user with admin privileges. Think of it like this - I can view all users, You can only view all users who are at a level lower than you WHO ALSO were either recruited directly by you or recruited by one of your recruits. This causes the use of several queries (SELECT * FROM agents WHERE COMMID='7' AND AGTREFFERER='$agtref') where $agtref is the ID of the top referring agent, in example above would be a,b,c,d,e,f,g - excludes H because nobody can be lower than level 1 and he cannot refer anybody as such).
JM4
I will see if I can populate a DB with dummy data and give a quick screen shot of exactly what issue I'm facing - perhaps reading the text is very difficult
JM4
+1  A: 

i think i understood you. you want to sort by commission_level within a given agent hierarchy. the following may help (http://pastie.org/1111097)

drop table if exists agent;

create table agent
(
agent_id int unsigned not null auto_increment primary key,
name varchar(32) not null,
commission_level tinyint unsigned default 0,
parent_agent_id int unsigned default null
)
engine = innodb;

insert into agent (name, commission_level, parent_agent_id) values

('I', 99, null),
  ('A', 7, 1),
  ('B', 6, 1),
    ('C', 5, 2),
    ('D', 6, 2),
    ('E', 5, 3),
    ('F', 2, 3),
      ('G', 5, 5),
      ('H', 1, 5);


delimiter ;

drop procedure if exists agent_hier;

delimiter #

create procedure agent_hier
(
in p_agent_id int unsigned
)
proc_main:begin

declare done tinyint unsigned default 0;
declare dpth smallint unsigned default 0;


create temporary table hier(
 parent_agent_id int unsigned, 
 agent_id int unsigned, 
 depth smallint unsigned default 0
)engine = memory;

insert into hier values (p_agent_id, p_agent_id, dpth);

/* http://dev.mysql.com/doc/refman/5.0/en/temporary-table-problems.html */

create temporary table tmp engine=memory select * from hier;

while done <> 1 do

    if exists( select 1 from agent a inner join hier on a.parent_agent_id = hier.agent_id and hier.depth = dpth) then

        insert into hier 
            select a.parent_agent_id, a.agent_id, dpth + 1 from agent a
            inner join tmp on a.parent_agent_id = tmp.agent_id and tmp.depth = dpth;

        set dpth = dpth + 1;            

        truncate table tmp;
        insert into tmp select * from hier where depth = dpth;

    else
        set done = 1;
    end if;

end while;


select 
 a.agent_id,
 a.name as agent_name,
 if(a.agent_id = b.agent_id, null, b.agent_id) as parent_agent_id,
 if(a.agent_id = b.agent_id, null, b.name) as parent_agent_name,
 hier.depth,
 a.commission_level
from 
 hier
inner join agent a on hier.agent_id = a.agent_id
inner join agent b on hier.parent_agent_id = b.agent_id
order by
 -- dont want to sort by depth but by commision instead - i think ??
 -- hier.depth, hier.agent_id; 
 a.commission_level desc;

drop temporary table if exists hier;
drop temporary table if exists tmp;

end proc_main #


delimiter ;


/*

select * from agent;

call agent_hier(1);
call agent_hier(2);
call agent_hier(3);
call agent_hier(5);
*/
f00
@f00 - I am not sure I quite follow your code. The SQL statement shown does create a single table named 'agent' but stops there - is there PHP code included here or am I missing something?
JM4
why dont you run the entire script into a test db and play with it. if it does what you want it to you would simply call the stored procedure from php.call agent_hier(1);call agent_hier(3);etc.
f00
@f00 - i did download the .sql file and import into my DB
JM4
well as i said i might have misunderstood your requirements. when i run the stored procedure for agent I (agent_id = 1 in my example) i get the following result: http://i37.tinypic.com/351za5z.jpg
f00
@f00 - I am not sure what you are running your sql script in. Putting into PHP MYADMIN only returns a table named Admin. The overall format you show is correct ultimately - I will be inputting and outputting my results using PHP so agents can view their downline (this is not for administrative purposes)
JM4
i've never used phpmyadmin for anything so i am unsure how you would go about creating the sample table and stored procedure using it and subsequently calling the stored procedure. i would imagine you have some "input query" form into which you could run the script. maybe you might have to split the script into several parts. 1.create table, 2. add data, 3. create stored proc, 4. run stored proc. Personally i use Toad http://toadsoft.com/toadmysql/
f00
does this look more familiar to you ? http://pastie.org/1111272
f00
@f00 - haha, not really but i completely understand what you are saying and the overall though is correct but it is not really what I'm trying to achieve. Unfortunately I cannot take a screenshot of our existing site because it is filled with personal information but ultimately the results returned are displayed in html formatted tables on screen to a general user logging into their online website.
JM4
you can call the stored procedure called agent_hier from php, you get a resultset, you loop the resultset and render HTML - simples.
f00
does this help http://pastie.org/1111293
f00
@f00 - I think that makes perfect sense. I need to understand how to run the stored procedure straight from PHP (that or how it is stored in the mysql db itself). before I could test
JM4
ok cool - well hope it helps you out.
f00
I will let you know as soon as I can. It all seems to make great sense. I appreciate your time!
JM4
@f00 - i am receiving errors when trying to import the sql into Navicat as well. Error posted is: [Err] 1064 - You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'delimiter ;/*select * from agent;call agent_hier(1);call agent_hier(2);' at line 65
JM4
must be a bug in navicat as the script runs fine from mysql's own console and runs fine from within Toad. http://pastie.org/1112665if there were errors in the script i wrote for you then i wouldnt be sitting here calling the stored proc and getting results !!
f00
ok, i downloaded and installed navicrap and attempted to run my script which gives me the same error as you. i modified the script so navicrap can execute it - run this http://pastie.org/1112694. not sure why navicrap has an issue with delimiters, maybe if you've paid for this product you should report the bug. hope this helps :)
f00
haha - @f00 you da man - i cant stop laughing about navicrap
JM4
@f00 - alright, got everything in an run - ran into some 'expected' issues but resolved that - now getting Query failed with error: PROCEDURE db.agent_hier can't return a result set in the given contextyou ever had that issue?
JM4
can you run the stored proc from the navicat query window ? call agent_hier(1); if you're getting that error from php using mysql extension then it's probably a version issue as you can't call stored procs using mysql extension prior to version 5.2 (i think) so you will have to use mysqli extension to make the call instead
f00