确定“以下用户数量”在多层会员数据库中
我为客户编写了一个会员网站,其中会员的加入低于其他用户。例如
userid | name | subof
1 | John | 0
2 | Joe | 1
3 | Jill | 0
4 | Janet | 2
5 | Juan | 1
6 | George| 2
,约翰和吉尔在顶部,乔和胡安在约翰下面,珍妮特和乔治在乔下面。分级用于传递佣金。我的客户希望能够查看有多少用户低于任何给定用户,(至少限制为 8 层)
现在,我已将附加字段“num_below”添加到用户表中,每当有人加入或离开用户时,该字段就会递增或递减。
第一个问题是,感觉它违反了良好的数据库规范化实践~因为它存储的数据已经存在于数据库中。
第二个问题是,当我的客户过来说“哦,乔治”时,它会变得毛茸茸的打算加入胡安的领导下,请移动他”
我考虑过每次要求时动态计算下面的数字,但数据库查询似乎呈指数级增长。
我写了一个 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 层似乎过于占用处理器资源。
-- 编辑 --
我不太清楚这些层是如何工作的,所以我把桌子弄大了。我在编辑中要解决的关键问题是,任何人都可以在其正下方的层级中拥有多个人员。希望这是有道理的。
-- 解决方案 -- (将 Kakao 的解决方案实现为“成员”类的方法)
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];
}
I've programmed a membership site for a client within which members join below other users. e.g.
userid | name | subof
1 | John | 0
2 | Joe | 1
3 | Jill | 0
4 | Janet | 2
5 | Juan | 1
6 | George| 2
John and Jill are at the top, Joe and Juan are below John, and Janet and George are below Joe. The tier-ing is used for passing up commission. My client wants to be able to see How many users are below any given user, (at least it's restricted to 8 tiers out)
For now I have added the additional field `num_below` to the user table, and that field is incremented or decremented whenever someone joins or leaves below the user.
The first problem with this is that it feels like it violates good Database Normalization practices~ cuz it's storing data that's already in the DB
The second is that it get's hairy when my client comes and says "Oh, George meant to join under Juan, please move him"
I considered just dynamically calculating the number below every time it's asked for, but the db queries would seem to grow exponentially.
I've written a rectifySubs()
function that can go through and fix all of the `num_below` fields, but as there are more members it will become more and more intensive to run~
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);
}
}
Any recommendations? I don't want to put too much redundant data in the DB, but going 8 tiers out every time seems way too processor intensive.
-- EDIT --
I wasn't clear enough about how the tiers worked so I made the table bigger. The key issue I'm addressing with the edit is that any person can have multiple people in a tier directly below them. Hope that makes sense.
-- SOLUTION -- (Implementation of Kakao's solution as a method of the 'Member' Class)
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];
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
更新
多个成员以下版本:
Update
Multiple members below version:
就我个人而言,我认为你预先计算数据的解决方案很好。
我要改变的一件事是使“纠正”功能更加智能(可选)而不必重建整个数据集。如果一个人被移动到另一个分支,唯一需要重新计算的是该人的旧超级和新超级。
例如,如果 Joe 从 Bob 转移到 Alice,那么 Bob 和他的所有超级用户都会失去 Joe 的“num_below”,然后 Alice 和她的所有超级用户都会获得 Joe 的“num_below”。请注意,用于调整超级的“num_below”实际上是
num_below + 1
,因为乔本人不被算作他自己的一部分。编辑:
或者,查看:
这是一种不同的数据结构,更容易与其他数据结构一起执行此特定计算(子级数量),但确实有自己的一组数字(左/右)需要维护。
Personally, I think your solution of precomputing the data is just fine.
The one thing I would change is making the "rectify" function more smart in (optionally) not having to rebuild the entire dataset. If a person is moved to another branch the only ones that need to be recomputed are the person's old and new supers.
e.g., if Joe is moved from Bob to Alice, then Bob and all of his supers lost Joe's "num_below" and then Alice and all of her supers gain Joe's "num_below". Note that the "num_below" that is used to adjust the supers is actually
num_below + 1
because of Joe himself isn't counted as part of his own.Edit:
Alternatively, look at:
It's a different data structure that is easier to do this particular computation (number of children) along with others but does have it's own set of numbers (left/right) to maintain.
我添加更多人员只是为了清楚地表明我了解您的需求:
假设您的老板需要乔手下的人员。
你想要得到:Janet、Dawn、James、Doug——对吧?
与其添加新列,不如更改 subof 的定义(在我的示例中我将其设为 varchar)怎么样?
所以你的表应该是这样的:
金字塔的顶部是 0,所以 John 和 Jill 仍然位于顶部。然后你就可以通过 0 之后的序列知道每个人是谁。
这样做,你可以在以下查询中获得所需的结果:
所以你的下一个问题是如何插入一名新兵。好的。比尔在道格手下登场。那么比尔的插入内容是什么?
// 首先获取 subof 和 userid
//然后插入新行 subof.userid
所以现在你有了另一行:
但是等等......还有更多!
将示例替换为詹姆斯和道格,以替换为乔治和胡安的新表,以专注于修改后的问题:
===== 乔治和胡安的新示例
问题
答案
问题
答案
选择用户 ID、姓名、子类别
FROM 表名
WHERE name in('Juan','George');
因此您的自动查询将如下所示:
给您这个新结果:
享受吧!
黎明
I am adding some more people just to be clear I understand what you need:
So say your boss asks for the people under Joe.
You want to get: Janet, Dawn, James, Doug -- right?
Instead of adding a new column, how about changing definition of subof (in my example I have made it a varchar)?
So your table would like this:
The top of the pyramid are 0, so John and Jill are still at the top. Then you know who is under each by the sequences following 0.
Doing it this way you could get the results you need in the following query:
So your next issue is how to insert a new recruit. OK. Bill comes on under Doug. So what would the insert be for Bill?
// First get the subof and userid
//Then insert the new row which would be subof.userid
So now you have another row:
But wait ... there's more!
Replaced example with James and Doug for new table with George and Juan to focus on modified question:
===== new example with George and Juan
QUESTION
ANSWER
QUESTION
ANSWER
SELECT userid, name, subof
FROM tablename
WHERE name in('Juan','George');
so your automated query would look something like this:
Giving you this new result:
Enjoy!
Dawn
以下解决方案使用非递归存储过程:
示例用法:
希望您发现它有帮助 - 下面是完整脚本:)
完整脚本:
the following solution uses a non recursive stored procedure:
example usage:
hope you find it helpful - full script below :)
full script: