一个关于分类的问题

ice_berg16 2005-04-14 10:27:04
在大型网站的分类结构中,行业分类一般采用哪种数据库结构比较好?
如果采用中值排序的方法,那么同级分类的排序是怎么操作的?
如移动,向上,向下,置顶等操作,
id rootid deep ordernum title
1 0 1 0 根
2 1 2 32 电脑
3 1 3 64 软件
5 1 4 96 系统应用
6 1 5 112 系统优化
4 1 3 128 硬件

如想将软件和硬件的排序位置交换,怎么做?
...全文
317 20 打赏 收藏 转发到动态 举报
写回复
用AI写文章
20 条回复
切换为时间正序
请发表友善的回复…
发表回复
xuzuning 2005-04-22
  • 打赏
  • 举报
回复

/**
* 公共方法
* 修改置顶的节点内容
* 参数$data的格式为用逗号分开的赋值表达式
**/
function update($id,$data) {
if(! empty($data))
$this->query("update $this->tbl_name set $data where id=$id");
}
/**
* 公共方法
* 在指定填加节点的后面插入节点
**/
function Add($id,$data='') {
return $this->_additem($id,0,$data);
}
/**
* 公共方法
* 向指定节点插入子节点
**/
function AddChild($id,$data='') {
return $this->_additem($id,1,$data);
}
/**
* 公共方法
* 插入节点到组开始处
*/
function AddFirst($id,$data='') {
return $this->_additem($id,2,$data);
}
/**
* 公共方法
* 插入节点到组结束处
**/
function AddLast($id,$data='') {
return $this->_additem($id,3,$data);
}
/**
* 公共方法
* 删除分支
* 删除节点$id及其子节点
**/
function delete($id) {
$this->_locate("id=$id");
$ar = $this->_group();
array_unshift($ar,$id);
$this->query("delete from $this->tbl_name where id in (".join(',',$ar).")");
}
/**
* 公共方法
* 移动分支
* 将节点$ids及其子节点移动到节点$idt处
* 1、当节点$ids和节点$idt具有同一根节点时
* 节点$ids插入到节点$idt之前
* 2、当节点$ids和节点$idt不具有同一根节点时
* 节点$ids作为节点$idt的第一子节点
* 3、当$idt为0时,表示将节点$ids作为顶级节点,并放在最后
**/
function move($ids,$idt=0) {
//读取节点$ids的信息
$this->_locate("id=$ids");
//缓存源节点参数
$root_id = $this->root_id;
$deep = $this->deep;
$ordernum_s = $this->ordernum; //起始的排序键值
//获得子节点id列表
$ar = $this->_group();
$idlist = join(',',$ar);
$ordernum_e = $this->ordernum; //结束的排序键值

if($idt == 0) {
$this->query("select max(ordernum) as maxnum from $this->tbl_name");
$maxnum = $this->fields->maxnum;
$this->query("update $this->tbl_name set root_id=0, deep=0, ordernum=ordernum+$maxnum where id=$ids");
$this->query("update $this->tbl_name set root_id=$ids, deep=deep-$this->deep, ordernum=ordernum+$maxnum where id in ($idlist)");
return;
}

//读取节点$idt的信息
array_unshift($ar,$ids);
$idlist = join(',',$ar);
$this->_locate("id=$idt");
if($ordernum_s <= $this->ordernum && $this->ordernum <= $ordernum_e)
die("不能移动到分支本身 $ordernum_s $this->ordernum $ordernum_e");
$dx = $ordernum_e - $ordernum_s + $this->increment;
if($this->root_id == $root_id) {
$this->_push($dx,$this->ordernum); //下推目标节点的后续节点
$deep = $this->deep - $deep;
if($this->ordernum > $ordernum_e)
$dx = $this->ordernum - $ordernum_s; //目的地在源分支之后
else
$dx = $this->ordernum - $ordernum_s - $dx; //目的地在源分支之前

//安置源分支
$this->query("update $this->tbl_name set ordernum=$dx+ordernum, deep=$deep+deep where id in ($idlist)");
}else {
$this->_locate("ordernum>$this->ordernum");
$num = $this->fields->ordernum;
$this->_push($dx,$num); //下推目标节点的后续节点
$deep = $this->deep + 1 - $deep;
if($this->ordernum > $ordernum_e)
$dx = $num - $ordernum_s; //目的地在源分支之后
else
$dx = $num - $ordernum_s - $dx; //目的地在源分支之前
$root_id = $this->root_id==0?$this->id:$this->root_id;
//安置源分支
$this->query("update $this->tbl_name set root_id=$root_id, ordernum=$dx+ordernum, deep=$deep+deep where id in ($idlist)");
}
}
/**
* 调试用方法
* 列表展示数据分布
**/
function debug($sql='') {
if($sql == '') $sql = $this->get_default_sql();
$this->query($sql);
if($this->eof) return;

echo '<table border>';
echo '<tr>';
foreach($this->fields as $key=>$value)
echo "<th>$key</th>";
echo "</tr>\n";
while(!$this->eof) {
echo '<tr>';
foreach($this->fields as $key=>$value)
if($key == 'id')
echo '<td>'.$this->set_deep($this->fields->id,$this->fields->deep).'</td>';
else
echo "<td>$value</td>";
echo "</tr>\n";
$this->next();
}
echo '</table>';
}
/**
* 格式化输出
**/
function view() {
$sql = $this->get_default_sql();
$this->query($sql);
if($this->eof) return;
echo '<table>';
while(!$this->eof) {
echo '<tr><td>'.$this->set_deep($this->fields->id,$this->fields->deep).'</td></tr>';
$this->next();
}
echo '</table>';
}
/**
* 辅助方法
**/
function set_deep($text,$level) {
if($level == 0) return $text;
return str_repeat('&'.'nbsp;',$level*2).$text;
}
}
?>
xuzuning 2005-04-22
  • 打赏
  • 举报
回复

/**
* 公共方法
* 修改置顶的节点内容
* 参数$data的格式为用逗号分开的赋值表达式
**/
function update($id,$data) {
if(! empty($data))
$this->query("update $this->tbl_name set $data where id=$id");
}
/**
* 公共方法
* 在指定填加节点的后面插入节点
**/
function Add($id,$data='') {
return $this->_additem($id,0,$data);
}
/**
* 公共方法
* 向指定节点插入子节点
**/
function AddChild($id,$data='') {
return $this->_additem($id,1,$data);
}
/**
* 公共方法
* 插入节点到组开始处
*/
function AddFirst($id,$data='') {
return $this->_additem($id,2,$data);
}
/**
* 公共方法
* 插入节点到组结束处
**/
function AddLast($id,$data='') {
return $this->_additem($id,3,$data);
}
/**
* 公共方法
* 删除分支
* 删除节点$id及其子节点
**/
function delete($id) {
$this->_locate("id=$id");
$ar = $this->_group();
array_unshift($ar,$id);
$this->query("delete from $this->tbl_name where id in (".join(',',$ar).")");
}
/**
* 公共方法
* 移动分支
* 将节点$ids及其子节点移动到节点$idt处
* 1、当节点$ids和节点$idt具有同一根节点时
* 节点$ids插入到节点$idt之前
* 2、当节点$ids和节点$idt不具有同一根节点时
* 节点$ids作为节点$idt的第一子节点
* 3、当$idt为0时,表示将节点$ids作为顶级节点,并放在最后
**/
function move($ids,$idt=0) {
//读取节点$ids的信息
$this->_locate("id=$ids");
//缓存源节点参数
$root_id = $this->root_id;
$deep = $this->deep;
$ordernum_s = $this->ordernum; //起始的排序键值
//获得子节点id列表
$ar = $this->_group();
$idlist = join(',',$ar);
$ordernum_e = $this->ordernum; //结束的排序键值

if($idt == 0) {
$this->query("select max(ordernum) as maxnum from $this->tbl_name");
$maxnum = $this->fields->maxnum;
$this->query("update $this->tbl_name set root_id=0, deep=0, ordernum=ordernum+$maxnum where id=$ids");
$this->query("update $this->tbl_name set root_id=$ids, deep=deep-$this->deep, ordernum=ordernum+$maxnum where id in ($idlist)");
return;
}

//读取节点$idt的信息
array_unshift($ar,$ids);
$idlist = join(',',$ar);
$this->_locate("id=$idt");
if($ordernum_s <= $this->ordernum && $this->ordernum <= $ordernum_e)
die("不能移动到分支本身 $ordernum_s $this->ordernum $ordernum_e");
$dx = $ordernum_e - $ordernum_s + $this->increment;
if($this->root_id == $root_id) {
$this->_push($dx,$this->ordernum); //下推目标节点的后续节点
$deep = $this->deep - $deep;
if($this->ordernum > $ordernum_e)
$dx = $this->ordernum - $ordernum_s; //目的地在源分支之后
else
$dx = $this->ordernum - $ordernum_s - $dx; //目的地在源分支之前

//安置源分支
$this->query("update $this->tbl_name set ordernum=$dx+ordernum, deep=$deep+deep where id in ($idlist)");
}else {
$this->_locate("ordernum>$this->ordernum");
$num = $this->fields->ordernum;
$this->_push($dx,$num); //下推目标节点的后续节点
$deep = $this->deep + 1 - $deep;
if($this->ordernum > $ordernum_e)
$dx = $num - $ordernum_s; //目的地在源分支之后
else
$dx = $num - $ordernum_s - $dx; //目的地在源分支之前
$root_id = $this->root_id==0?$this->id:$this->root_id;
//安置源分支
$this->query("update $this->tbl_name set root_id=$root_id, ordernum=$dx+ordernum, deep=$deep+deep where id in ($idlist)");
}
}
/**
* 调试用方法
* 列表展示数据分布
**/
function debug($sql='') {
if($sql == '') $sql = $this->get_default_sql();
$this->query($sql);
if($this->eof) return;

echo '<table border>';
echo '<tr>';
foreach($this->fields as $key=>$value)
echo "<th>$key</th>";
echo "</tr>\n";
while(!$this->eof) {
echo '<tr>';
foreach($this->fields as $key=>$value)
if($key == 'id')
echo '<td>'.$this->set_deep($this->fields->id,$this->fields->deep).'</td>';
else
echo "<td>$value</td>";
echo "</tr>\n";
$this->next();
}
echo '</table>';
}
/**
* 格式化输出
**/
function view() {
$sql = $this->get_default_sql();
$this->query($sql);
if($this->eof) return;
echo '<table>';
while(!$this->eof) {
echo '<tr><td>'.$this->set_deep($this->fields->id,$this->fields->deep).'</td></tr>';
$this->next();
}
echo '</table>';
}
/**
* 辅助方法
**/
function set_deep($text,$level) {
if($level == 0) return $text;
return str_repeat('&'.'nbsp;',$level*2).$text;
}
}
?>
xuzuning 2005-04-22
  • 打赏
  • 举报
回复
<?php
/**
* 树形数据结构操作类
* 与一般的树形类不同,也与我以前发布的树形类不同。
* 这个类将精力全部放到建树的过程中,取出时只需简单的查询即可,并无任何计算量。
* 数据组织采用“中值排序法”的数据结构,其原理可在网上找到。
* 采用动态修改排序键值的算法,避免了“中值排序法”的有限深度的约束
*
* 这个类提供以下主要方法:
* Add 向指定的节点(用id指示,下同)添加一个同级节点
* AddChild 向指定的节点添加一个子节点
* AddFirst 向指定的节点所在组的开始处添加一个节点
* AddLast 向指定的节点所在组的结束处添加一个节点
* delete 删除指定的节点及其子节点
* move 移动以指定节点开始的分支(以下简称分支)到目标节点处
* 1、当目标节点为空时,分支从根开始
* 2、当目标节点与分支同属于一个根节点时,分支插入到目标前(即挤占目标节点的原位置)
* 3、当目标节点与分支不属于同一根节点时,分支做为目标节点的第一子节点
* 这个类同样适合用于bbs,类中已预制置查询语句。只需设置属性mode等于BBS即可。
* 当用于bbs是,就只需要用到Add 加贴、AddChild 回复、delete 删贴,这几个方法。
*
* 可容纳的记录数和树的深度完全取决于排序字段(ordernum)的数据类型和操作系统的最大文件尺寸的约束。理论上是无限级别的。
*
**/
class Tree_cortrol extends DB {
var $mode = 'TREE'; //工作模式,当做为论坛控制时请改为 BBS
var $increment = 4; //排序基数的2的幂指数,计算在构造函数中进行
var $db_name = 'test'; //**** 数据库名,请填写真实库名。或在$_CONFIG数组中定义,下同
var $tbl_name = 'tree1'; //**** 数据表名,请填写真实表名
var $default_sql = array(
'BBS' => 'select * from $this->tbl_name order by root_id+id-sign(root_id)*id desc,ordernum',
'TREE' => 'select * from $this->tbl_name order by ordernum'
);
/**
* 构造函数
**/
function Tree_cortrol($param='') {
$this->DB($param);
$this->increment = pow(2,$this->increment);
global $_CONFIG;
if(is_array($_CONFIG))
foreach($_CONFIG as $k=>$v)
$this->$k = $v;
}
function get_default_sql() {
$v = $this->default_sql[$this->mode];
return eval("return \"$v\";");
}
/**
* 内部方法
* 计算插值,并实现插入时的动态调整排序键
**/
function _epenthesis($a,$b) {
if($a < $b) list($a,$b) = array($b,$a);
if($a - $b < 2) {
$this->_push($this->increment,$a);
$a += $this->increment;
}
return floor(($a+$b)/2);
}
/**
* 内部方法
* 向后调整排序键
**/
function _push($dx,$expr) {
$this->query("update $this->tbl_name set ordernum=ordernum+$dx where ordernum>=$expr");
}
/**
* 内部方法
* 完成所有的插入算法
**/
function _additem($id,$deep,$data) {
if($id == 0) {
$this->query("select max(ordernum) as maxnum from $this->tbl_name");
if($this->fields->maxnum == '') $this->ordernum = 0;
else $this->ordernum = $this->fields->maxnum + $this->increment;
$this->root_id = 0;
$this->deep = 0;
return $this->_insert($data);
}
$this->_locate("id=$id") or die("没有此节点:$id");
switch($deep) {
case 0:
$this->_group();
break;
case 1:
if($this->root_id == 0) $this->root_id = $this->id;
$this->deep += $deep;
$this->_locate("ordernum>$this->ordernum");
break;
case 2:
$this->_locate("root_id=$this->root_id and deep=$this->deep and ordernum<=$this->ordernum");
$this->ordernum = $this->fields->ordernum;
$this->_locate("ordernum<=$this->ordernum",'desc');
break;
case 3:
$this->_group(1);
break;
default:
return 0;
}
//计算插值
$this->ordernum = $this->_epenthesis($this->fields->ordernum,$this->ordernum);
return $this->_insert($data);
}
/**
* 内部方法
* 获取组信息:
* $type = 1 取得当前节点的子节点,定位到下一个组节点处
* $type = 2 定位到组结束处
**/
function _group($type=0) {
$id = array();
if($this->root_id == 0)
$this->_locate("root_id=$this->id and ordernum>$this->ordernum");
else
$this->_locate("root_id=$this->root_id and ordernum>$this->ordernum");
while(! $this->eof) {
if($type == 0 && $this->fields->deep <= $this->deep) return $id;
if($type == 1 && $this->fields->deep < $this->deep) return $id;
$id[] = $this->fields->id;
$this->ordernum = $this->fields->ordernum;
$this->next();
}
$this->_locate("ordernum>$this->ordernum");
return $id;
}
/**
* 内部方法
* 执行特定条件查询
**/
function _locate($expr,$ext='') {
$this->query("select id,root_id,deep,ordernum from $this->tbl_name where $expr order by ordernum $ext");
//如果是id定位,则设置公共信息
if(! $this->eof && preg_match("/^id=\d+$/",$expr)) {
$this->id = $this->fields->id;
$this->root_id = $this->fields->root_id;
$this->deep = $this->fields->deep;
$this->ordernum = $this->fields->ordernum;
}
if($this->eof && preg_match("/^ordernum>\d+$/",$expr))
$this->fields->ordernum = $this->ordernum + $this->increment;
return ! $this->eof;
}
/**
* 内部方法
* 插入数据到数据表,返回插入点的id
* 根据参数执行用户扩展的插入动作
**/
function _insert($data) {
$data['root_id'] = $this->root_id;
$data['deep'] = $this->deep;
$data['ordernum'] = $this->ordernum;
$this->insert($data);
return $this->insert_id;
}
flyonet 2005-04-22
  • 打赏
  • 举报
回复
顶顶看
xuzuning 2005-04-22
  • 打赏
  • 举报
回复
<?php
require_once 'adodb/tohtml.inc.php';
require_once 'adodb/adodb.inc.php';
$ADODB_FETCH_MODE = ADODB_FETCH_ASSOC;

/**
* 一个简单的数据库类
* 你需要根据你所使用的数据库来修改这个类
* 你也可以扩展这个类,或者从你正在使用的数据库类中派生出这个类
* 但必须约定提供query和next方法,并正确设置属性eof和insert_id属性
* 这里是一个从ADODB构造出来的类
**/
class DB extends Object {
var $conn; //数据库连字
var $result; //查询连接字
var $insert_id = 0; //最后插入的id
var $eof = true; //查询结果的指针状态,无记录或到达记录尾时为true
var $fields; //对象,保存查询结果。之所以用对象是为了统一书写格式
/**
* 构造函数
* 可以接受一个传入的参数,用于重建数据表
**/
function DB($param='') {
$this->conn = &ADONewConnection('mssql');
// 侦错
$this->conn->debug = false;

// DSN 四项基本资料设定
$mch = "localhost";
$user = "sa";
$pwd = "";
$database = "test";
$this->conn->Connect($mch, $user, $pwd, $database);

if($param != '') $this->_init($param);
}
/**
* 执行一个查询
**/
function query($sql) {
$this->result = &$this->conn->Execute($sql);

// 若 $rs 为 false,则输出错误讯息
if (!$this->result)
exit($this->conn->ErrorMsg());
if(eregi("^select ",$sql)) {
if(!($this->eof = $this->result->EOF)) {
$this->fields = $this->fields = $this->result->fields;
settype($this->fields,'object');
}
}else {
if(eregi("^insert ",$sql))
$this->insert_id = $this->conn->Insert_ID();
}
}
/**
* 从查询结果中取下一个记录到fields
**/
function next() {
if(!($this->eof = $this->result->EOF)) {
$this->fields = $this->result->FetchRow();
settype($this->fields,'object');
}
}
/**
* 执行一个重建数据表的sql指令集
* sql指令集可以是保存在文件中的,也可以保存在变量中
**/
function _init($sql='') {
if($sql == '') return;
if(glob($sql))
$sql = file_get_contents($sql);
$ar = split(';',preg_replace("/[\r\n]+/","",$sql));
foreach($ar as $v)
if(! preg_match("/^#|^$/",$v)) $this->query($v);
}
function insert($ar=array()) {
// 产生一笔空记录
$sql = "select * from $this->tbl_name where 1=0";
$rs = $this->conn->Execute($sql);

// 用一个空阵列来装要更新的资料
$r = $this->fields;
settype($r,'array');
foreach($ar as $k=>$v)
$r[$k] = $v;
unset($r['id']);
// 用 GetInsertSQL 方法来制作一个完整的 sql 命令
$insertSQL = $this->conn->GetInsertSQL($rs, $r);

// 执行插入
$this->conn->Execute($insertSQL);
$this->insert_id = $this->conn->Insert_ID();
}
function update($ar=array()) {
$r = $this->fields;
settype($r,'array');
unset($r['id']);
foreach($ar as $k=>$v)
$r[$k] = $v;
// 用 GetInsertSQL 方法来制作一个完整的 sql 命令
$insertSQL = $this->conn->GetUpdateSQL($this->result, $r);

// 执行修改
$this->conn->Execute($insertSQL);
}
function html() {
echo rs2html($this->result);
}
}
?>
xuzuning 2005-04-22
  • 打赏
  • 举报
回复
调整分类(包括移动节点)的操作是有限的,所以所谓“效率”是不值得一提的。
况且在数据库中执行一条有过滤条件的update语句时,操作的是一条记录还是多条记录所需的时间悬殊不是很大,尤其是对有索引的字段
zhutimy 2005-04-22
  • 打赏
  • 举报
回复
严重关注!~
ice_berg16 2005-04-22
  • 打赏
  • 举报
回复
怎么掉这么快?up
zairwolf 2005-04-22
  • 打赏
  • 举报
回复
iceberg,ckong已经解密了。你看看admin下面的setforum.php就知道了。
helloyou0 2005-04-22
  • 打赏
  • 举报
回复
这个要看一下。

请教一下,这种情况下,不用事务,如果中途发生意外造成数据不一致的情况怎么办?
ice_berg16 2005-04-22
  • 打赏
  • 举报
回复
研究一下!!不懂再问
ice_berg16 2005-04-21
  • 打赏
  • 举报
回复

如果在移动子节点时,发现前后两个节点的差小于2还要修改后面的,我感觉数据库比较大的时候会不会效率很低?

对于移动节点这块,唠叨的代码方不方便透露点?
我想把这块整理一下,做一个无限分类的总结。
xuzuning 2005-04-21
  • 打赏
  • 举报
回复
现在的问题是,硬件下面没有子分类,如果有子分类,只移动该结点结果是不对的,
难道需要将所有子结点也都进行相应的ordernum的修改吗?

是的!这是必然的
coolstr 2005-04-21
  • 打赏
  • 举报
回复
引用
==================================
现在的问题是,硬件下面没有子分类,如果有子分类,只移动该结点结果是不对的,
难道需要将所有子结点也都进行相应的ordernum的修改吗?
=============================================================

查询的时候得到 表(id rootid deep ordernum title)中的 ordernum ,根据这个值来排序。
这样做可能会慢一点,不过也是一个解决办法啊。
ice_berg16 2005-04-21
  • 打赏
  • 举报
回复
to zairwolf(君子兰)
我安装完你的论坛,index.php显示一片空白,怎么回事?
想看看header.php中的内容,可惜加密了。
zairwolf 2005-04-15
  • 打赏
  • 举报
回复
稻草人你可以看看我的心空:ckong.cn。
就是中值排序。你看看我后台的版面处理怎么管理的你就明白了。用我那种方法更简单。
ice_berg16 2005-04-15
  • 打赏
  • 举报
回复
upup,在帮忙看看啊
ziyou_jo 2005-04-14
  • 打赏
  • 举报
回复
不知道这个能否对你有帮助。我以前写的

$pieces = explode("-", $_POST['list_order']);
$list_order=$pieces[0];
$showid=$pieces[1];
$id_zhu=$list_order-1;

//print $list_order;exit;
if($id_zhu==0){$public_class->msg('已是顶层,不能在上移动!');exit;}

//修改被移动-往下移动
$B_sql = "update SW_web_list set list_order='$list_order' where SW_userid='".$_SESSION['SW_userid']."' and list_order='$id_zhu'";

//修改主移动-往上移动
$Z_sql = "update SW_web_list set list_order='$id_zhu' where SW_userid='".$_SESSION['SW_userid']."' and id='$showid'";

if(mysql_query($B_sql) and mysql_query($Z_sql))
{
print "<meta http-equiv=Refresh content=0;url=list_order.php?order=$id_zhu>";
}else{
$public_class->msg('发生未知错误,请关闭重试!');
}
xuzuning 2005-04-14
  • 打赏
  • 举报
回复
id rootid deep ordernum title
1 0 1 0 根
2 1 2 32 电脑
4 1 3 48 硬件 ******************
3 1 3 64 软件
5 1 4 96 系统应用
6 1 5 112 系统优化

修改id=4 的ordernum为ordernum(id=2)和ordernum(id=3)的中值
注意,若ordernum为ordernum(id=2)和ordernum(id=3)的差小于2时,应先调整ordernum(id=3)及以后的ordernum,以便能容纳ordernum(id=4)的新值

记得我在春节前发过一个思考题,但是响应者廖廖。所以也就没有再贴出我的代码了
ice_berg16 2005-04-14
  • 打赏
  • 举报
回复
不好意思,楼上的,我没看明白你写的.
to xuzuning:
是的,当时我根据我的分析写过一些代码,不知道你看了没有.
现在的问题是,硬件下面没有子分类,如果有子分类,只移动该结点结果是不对的,
难道需要将所有子结点也都进行相应的ordernum的修改吗?

21,886

社区成员

发帖
与我相关
我的任务
社区描述
从PHP安装配置,PHP入门,PHP基础到PHP应用
社区管理员
  • 基础编程社区
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧