1

メンバーが他のユーザーのに参加するクライアントのメンバーシップサイトをプログラムしました。例えば

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

ジョンとジルが一番上にあり、ジョーとファンがジョンの下にあり、ジャネットとジョージがジョーの下にあります。階層化は、手数料を渡すために使用されます。私のクライアントは、特定のユーザーの下にいるユーザーの数を確認できるようにしたいと考えています(少なくとも、8層に制限されています) 。

今のところ、ユーザーテーブルに追加のフィールド `num_below`を追加しました。このフィールドは、誰かがユーザーに参加またはユーザーの下を離れるたびにインクリメントまたはデクリメントされます。

これに関する最初の問題は、データベースの正規化の適切な慣行に違反しているように感じることです。これは、すでにDBにあるデータを保存しているためです。

2つ目は、クライアントが来て「ジョージはフアンの下に参加するつもりだったので、彼を動かしてください」と言うと、毛むくじゃらになるということです。

要求されるたびに以下の数値を動的に計算することを検討しましたが、dbクエリは指数関数的に増加するように見えます。

すべての`num_below`フィールドを調べて修正できる関数を作成しましたrectifySubs()が、メンバーが増えるにつれて、実行するのがますます集中的になります〜

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);
    }
}

何かお勧めはありますか?DBに冗長なデータを入れすぎたくないのですが、毎回8層にするのは、プロセッサに負担がかかりすぎるようです。

- 編集 -

ティアがどのように機能するかについて十分に明確ではなかったので、テーブルを大きくしました。私が編集で取り組んでいる重要な問題は、誰でもそのすぐ下の層に複数の人がいる可能性があるということです。それが理にかなっていることを願っています。

-ソリューション-(「メンバー」クラスのメソッドとしてのカカオのソリューションの実装)

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

4 に答える 4

1
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
于 2011-03-03T17:41:33.080 に答える
0

個人的には、データを事前計算するというあなたの解決策は問題ないと思います。

私が変更する1つのことは、データセット全体を再構築する必要がないという点で、「修正」機能を(オプションで)よりスマートにすることです。人が別のブランチに移動した場合、再計算する必要があるのは、その人の新旧のスーパーだけです。

たとえば、ジョーがボブからアリスに移動した場合、ボブと彼のすべてのスーパーはジョーの「num_below」を失い、次にアリスと彼女のすべてのスーパーはジョーの「num_below」を獲得します。スーパーを調整するために使用される「num_below」は、実際num_below + 1にはジョー自身が彼自身の一部としてカウントされていないためであることに注意してください。

編集:

または、以下を参照してください。

これは別のデータ構造であり、この特定の計算(子の数)を他の計算と一緒に実行する方が簡単ですが、維持するための独自の数のセット(左/右)があります。

于 2011-02-28T05:19:38.420 に答える
0

明確にするために、さらに数人を追加します。あなたが何を必要としているかを理解しています。

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とuseridを取得します

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

しかし、待ってください...もっとあります!


変更された質問に焦点を当てるために、GeorgeとJuanの新しいテーブルの例をJamesとDougに置き換えました

=====ジョージとファンの新しい例

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)

質問

私のクライアントが来て、「ああ、ジョージはフアンの下に参加するつもりだった、彼を動かしてください」と言うと、それは毛むくじゃらになります

答え

SELECT userid、name、subof FROM tablename WHERE name in('Juan'、'George');

//$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

楽しみ!

夜明け

于 2011-03-03T16:48:20.800 に答える
0

次のソリューションは、非再帰的なストアドプロシージャを使用します。

使用例:

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);
于 2011-03-04T01:08:20.440 に答える