Определение «Количество пользователей ниже» в многоуровневой базе данных участников - PullRequest
1 голос
/ 28 февраля 2011

Я запрограммировал сайт членства для клиента, в котором участники присоединяются к ниже других пользователей.например,

userid | name  | subof
1      | John  | 0
2      | Joe   | 1
3      | Jill  | 0
4      | Janet | 2
5      | Juan  | 1
6      | George| 2

Джон и Джилл находятся сверху, Джо и Хуан ниже Джона, а Джанет и Джордж ниже Джо.Этап используется для уплаты комиссии.Мой клиент хочет видеть, сколько пользователей ниже любого данного пользователя, (по крайней мере, оно ограничено 8 уровнями)

На данный момент я добавил дополнительное поле `num_below`в пользовательскую таблицу, и это поле увеличивается или уменьшается всякий раз, когда кто-то присоединяется или уходит ниже пользователя.

Первая проблема с этим заключается в том, что он чувствует, что он нарушает хорошие методы нормализации баз данных ~ потому что он хранит данные, которые уже естьв БД

Во-вторых, он получает волосатый , когда приходит мой клиент и говорит: «О, Джордж хотел присоединиться к Хуану, пожалуйста, переместите его» * ​​1016 *

Я подумалпросто динамически вычислять число ниже каждый раз, когда его запрашивают, но кажется, что запросы к БД растут экспоненциально.

Я написал rectifySubs() функцию, которая может пройти и исправить все поля `num_below`, но по мере того, как будет больше членов, будет становиться все интенсивнее запускать ~

function rectifySubs(){
    $NumBelow=array();//UID=>NUM_BELOW
    $SubOf=array();//UID=>IS_A_SUB_OF_UID
    $Uids=array();//UID
    $r=mysql_query("SELECT uid,subof FROM user");
    if(!$r || mysql_num_rows($r)==0){return 'Invalid';}
    while(list($uid,$subof)=mysql_fetch_row($r)){
        $NumBelow[$uid]=0;
        $SubOf[$uid]=$subof;
        $Uids[]=$uid;
    }
    mysql_free_result($r);

    $RungsUp=8;
    foreach($Uids as $uid){
        $r=1;
        $parent=$SubOf[$uid];
        while($parent>0 && $r<=$RungsUp){
            $NumBelow[$parent]+=1;
            $parent=$SubOf[$parent];
            $r++;
        }
    }
    $QueryByNum=array();
    foreach($NumBelow as $uid=>$num){
        if(!isset($QueryByNum[$num])){$QueryByNum[$num]=array();}
        $QueryByNum[$num][]=$uid;
    }
    unset($QueryByNum[0]);
    mysql_query("UPDATE user SET below=0");
    foreach($QueryByNum as $num=>$uids){
        $where=$or='';
        foreach($uids as $uid){
            $where.=$or."`uid`=".$uid;
            $or=" OR ";
        }
        mysql_query("UPDATE user SET below=".$num." WHERE ".$where);
    }
}

Есть какие-нибудь рекомендации?Я не хочу помещать слишком много избыточных данных в БД, но каждый раз выход на 8 уровней кажется слишком ресурсоемким.

- РЕДАКТИРОВАТЬ -

Мне было непонятноДостаточно о том, как работают ярусы, поэтому я увеличил стол.Ключевой вопрос, который я решаю при редактировании, заключается в том, что любой человек может иметь несколько человек на уровне, расположенном непосредственно под ними.Надеюсь, что это имеет смысл.

- РЕШЕНИЕ - (Реализация решения Какао как метода класса 'Member')

protected function getNumBelowAtLevel($i=1,$force=false){
    $i=abs((int)$i);
    if($i<=1){return 0;}//Level 1 is just the member themselves
    if($force || !isset($this->numBelow[$i])){
        $Us='';
        $Sels='';
        $Lefts='';
        $Groups='';
        $comma='';
        $nl='';
        for($k=1;$k<=$i-1;$k++){
            $j=$k==1?'0':$k-1;
            $Us.=$comma.'u'.$k;
            $Sels.=$comma.$nl.'m'.$k.'.mid as u'.$k;
            $Lefts.=$nl.'left join members as m'.$k.' on m'.$k.'.subof = m'.$j.'.mid';
            $Groups.=$comma.'u'.$k;

            $nl="\n\t\t\t\t\t";
            $comma=', ';
        }
        $sql="select count(*) - 1 as users_below
from (
    select distinct {$Us}
        from (
            select 
                {$Sels}
            from members as m0
                {$Lefts}
            where m0.mid = {$this->id}
                group by {$Groups} with rollup
            ) d
    ) a";
        if(DEBUG){var_dump($sql);}
        $r=mysql_query($sql);
        list($this->numBelow[$i])=mysql_fetch_row($r);
    }
    return $this->numBelow[$i];
}

Ответы [ 4 ]

1 голос
/ 03 марта 2011
select (case 
   when m1.userid is null then 0
   when m2.userid is null then 1
   when m3.userid is null then 2
   when m4.userid is null then 3
   when m5.userid is null then 4
   when m6.userid is null then 5
   when m7.userid is null then 6
   when m8.userid is null then 7
   else 8 end
   ) as users_below

from members as m0
left join members as m1 on m1.subof = m0.userid
left join members as m2 on m2.subof = m1.userid
left join members as m3 on m3.subof = m2.userid
left join members as m4 on m4.subof = m3.userid
left join members as m5 on m5.subof = m4.userid
left join members as m6 on m6.subof = m5.userid
left join members as m7 on m7.subof = m6.userid
left join members as m8 on m8.subof = m7.userid

where m0.userid = 1

Обновление

Несколько членов ниже версии:

select count(*) - 1 as users_below
from (
   select distinct u1, u2, u3, u4, u5, u6, u7
   from (
      select 
         m1.userid as u1, 
         m2.userid as u2, 
         m3.userid as u3,
         m4.userid as u4,
         m5.userid as u5,
         m6.userid as u6,
         m7.userid as u7

      from members as m0
      left join members as m1 on m1.subof = m0.userid
      left join members as m2 on m2.subof = m1.userid
      left join members as m3 on m3.subof = m2.userid
      left join members as m4 on m4.subof = m3.userid
      left join members as m5 on m5.subof = m4.userid
      left join members as m6 on m6.subof = m5.userid
      left join members as m7 on m7.subof = m6.userid

      where m0.userid = 1
      group by u1, u2, u3, u4, u5, u6, u7 with rollup
   ) d
) a
0 голосов
/ 04 марта 2011

следующее решение использует нерекурсивную хранимую процедуру:

пример использования:

call employees_hier(1);

+-----------+
| num_below |
+-----------+
|         7 |
+-----------+
1 row in set (0.00 sec)

надеюсь, что вы найдете это полезным - полный скрипт ниже:)

полный скрипт:

drop table if exists employees;
create table employees
(
emp_id smallint unsigned not null auto_increment primary key,
name varchar(255) not null,
boss_id smallint unsigned null,
key (boss_id)
)
engine = innodb;

insert into employees (name, boss_id) values
('f00',null), 
  ('ali later',1), 
  ('megan fox',1), 
      ('jessica alba',3), 
      ('eva longoria',3), 
         ('keira knightley',5), 
            ('liv tyler',6), 
            ('sophie marceau',7);

drop procedure if exists employees_hier;

delimiter #

create procedure employees_hier
(
in p_emp_id smallint unsigned
)
begin

declare v_done tinyint unsigned default(0);
declare v_dpth smallint unsigned default(0);

create temporary table hier(
 boss_id smallint unsigned, 
 emp_id smallint unsigned, 
 depth smallint unsigned
)engine = memory;

insert into hier select boss_id, emp_id, v_dpth from employees where emp_id = p_emp_id;

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

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

while not v_done do

    if exists( select 1 from employees e inner join hier on e.boss_id = hier.emp_id and hier.depth = v_dpth) then

        insert into hier select e.boss_id, e.emp_id, v_dpth + 1 
            from employees e inner join emps on e.boss_id = emps.emp_id and emps.depth = v_dpth;

        set v_dpth = v_dpth + 1;            

        truncate table emps;
        insert into emps select * from hier where depth = v_dpth;

    else
        set v_done = 1;
    end if;

end while;

select count(*) as num_below from hier where depth > 0;

/*
-- use this if you want to return the employees instead

select 
 e.emp_id,
 e.name as emp_name,
 p.emp_id as boss_emp_id,
 p.name as boss_name,
 hier.depth
from 
 hier
inner join employees e on hier.emp_id = e.emp_id
left outer join employees p on hier.boss_id = p.emp_id;
*/

drop temporary table if exists hier;
drop temporary table if exists emps;

end #

delimiter ;

-- call this sproc from your php

call employees_hier(1);
call employees_hier(2);
call employees_hier(3);
call employees_hier(5);
call employees_hier(6);
call employees_hier(7);
0 голосов
/ 03 марта 2011

Я добавляю еще людей, чтобы было ясно, я понимаю, что вам нужно:

userid | name  | subof
1      | John  | 0
2      | Joe   | 1
3      | Jill  | 0
4      | Janet | 2
5      | Dawn  | 4
6      | James | 4
7      | Mary  | 3
8      | Doug  | 6

Так скажи, твой босс просит людей под Джо. Вы хотите получить: Джанет, Дон, Джеймс, Даг - верно?

Вместо добавления нового столбца, как насчет изменения определения subof (в моем примере я сделал это varchar)?

Итак, ваш стол будет выглядеть так:

userid  name    subof   
1           John    0
2           Joe     0.1
3           Jill    0
4           Janet   0.1.2
5           Dawn    0.1.2.4
6           James   0.1.2.4
7           Mary    0.3
8           Doug    0.1.2.4.6

Вершина пирамиды равна 0, поэтому Джон и Джилл все еще наверху. Тогда вы знаете, кто находится под каждым из последовательностей, следующих за 0.

  • изменил john и jill на 0 вместо 0.0, чтобы упростить обновления

Делая так, вы можете получить нужные вам результаты в следующем запросе:

    select * from temporary WHERE subof like '0.1.2%' ORDER BY userid ASC;
//this is joe's subof +'.'+ his userid

Итак, ваш следующий вопрос - как вставить нового новобранца. ХОРОШО. Билл приходит под Дуга. Так что же будет за вставка для Билла?

// Сначала получите subof и идентификатор пользователя

SELECT subof, userid 
FROM tablename 
WHERE name = 'doug'; #answer 0.1.2.4.6
$subof, $userid = mysql_fetch; //pseudo code

// Затем вставьте новую строку, которая будет subof.userid

INSERT into tablename (userid, name, subof) VALUES ('9', 'Bill', '$subof.userid');

Так что теперь у вас есть еще один ряд:

9   Bill    0.1.2.4.6.8

Но подождите ... это еще не все!


Заменен пример с Джеймсом и Дугом для новой таблицы с Джорджем и Хуаном, чтобы сосредоточиться на измененном вопросе :

===== Новый пример с Джорджем и Хуаном

userid | name  | subof
1      | John  | 0
2      | Joe   | 0.1
3      | Jill  | 0
4      | Janet | 0.1.2
5      | Juan  | 0.1
6      | George| 0.1.2

Джон и Джилл наверху, Джо и Хуан ниже Джона, а Джанет и Джордж ниже Джо. Уровень является используется для передачи комиссии.

ВОПРОС

Мой клиент хочет видеть как многие пользователи ниже любого данного пользователя, (по крайней мере, это ограничено 8 уровнями вне)

ОТВЕТ

    SELECT count(*) 
    FROM tablename 
    WHERE subof LIKE 'subof_of_any_given_user+that_users_userid%';
//get those under Joe by using '0.1.2' (Joe's subof + his userid)

ВОПРОС

становится волосатым, когда мой клиент приходит и говорит "О, Джордж хотел присоединиться к Хуану, пожалуйста, двигайтесь его "

ОТВЕТ

ВЫБЕРИТЕ ИД пользователя, имя, подчиненное имя ОТ таблицы ГДЕ имя в («Хуан», «Джордж»);

//$juan_userid = 5
//$juan_subof = 0.1
//$updatevalue = $juan_subof.'.'.$juan_userid; //0.1.5

//$george_userid = 6
//$george_subof = 0.1.2
/$subofmatch = $george_subof.'.'.$george_userid; //0.1.2.6

чтобы ваш автоматический запрос выглядел примерно так:

UPDATE tablename 
SET subof = (REPLACE(subof, '$george_subof', '$updatevalue')) 
WHERE (subof like '$subofmatch%' OR userid = '$george_userid')



 // here it is with number values to make it easier to understand //  
    UPDATE tablename 
    SET subof = (REPLACE(subof, '0.1.2', '0.1.5')) 
    WHERE (subof like '0.1.2.6%' OR userid = '6');

Даю вам новый результат:

userid  name    subof   
1           John    0
2           Joe     0.1
3           Jill    0
4           Janet   0.1.2
5           Juan    0.1
6           George  0.1.5

Наслаждайтесь!

Рассвет

0 голосов
/ 28 февраля 2011

Лично я думаю, что ваше решение по предварительному вычислению данных просто отлично.

Единственное, что я хотел бы изменить, - это сделать функцию «исправления» более умной (необязательно), чтобы не пришлось перестраивать весь набор данных.Если человека переводят в другую ветвь, единственные, которые необходимо пересчитать, это его старые и новые суперсуперсы.

Например, если Джо переводят из Боба в Алису, то Боб и все его суперсупер теряют Джо"num_below", а затем Алиса и все ее суперы получают Джо "num_below".Обратите внимание, что «num_below», который используется для настройки суперпунктов, на самом деле num_below + 1, поскольку сам Джо не считается частью его собственного.

Редактировать:

В качестве альтернативы посмотрите на:

Это другая структура данных, которая проще выполнять это конкретное вычисление (количество детей) вместе с другими, но имеет свой собственный наборцифры (влево / вправо), чтобы сохранить.

...