一个非常好的无极限分类思路

QUOTE:

-- 表的结构 `tab_sort`
-- 

CREATE TABLE `tab_sort` (
  `sortid` int(6) unsigned zerofill NOT NULL auto_increment,
  `sortname` varchar(40) default NULL,
  `description` text,
  `parentpath` varchar(100) default NULL,
  PRIMARY KEY  (`sortid`)
) TYPE=MyISAM AUTO_INCREMENT=28 ;字段说明:
sortid         分类ID//如果你的平级分类很多,可以将此项调大。大到你满意为止
sortname  分类名称
description 分类的描述
parentpath上级分类的路径//如果你的分类等级非常深,可以调大这项,大到你满意为止!

思路:
将sortid连续存储在一个字段里,就可以得到该分类的所有父分类,更大的好处是,只用一次查询就可以将所以分类进行排序。。。而将排好序的分类信息写入一个文件。。以后用时,只要include这个文件就可以了。。。方便吧?

PS:这个类还有很多潜在功能(比如将一批分类转移到其他地方。。)。。

QUOTE:

<?php
/*******************************
作者:ultralqxq
邮箱:66488357@qq.com
QQ群:22431876
转载请保留以上信息
**********************************/
require_once(dirname(__FILE__)."./config.php");
/**************      config.php中相关部分    **************
$db_connect_host= "localhost";
$db_connect_user="root";
$db_connect_pwd="password";
$db_name="db"; //数据库名称
function connect_mysql()
{
        global $db_name,$db_connect_host,$db_connect_user,$db_connect_pwd;
        $conn = mysql_connect($db_connect_host,$db_connect_user,$db_connect_pwd) or die("无法连接MySQL数据库!");
        mysql_select_db($db_name,$conn) or die("无法打开数据库!");
        return $conn;
}

*************************************************/
class sort
{
           var $save_path='../../include/sort/';//生成的分类信息文件的保存路径
        var $sort_info=array();//用来临时保存分类信息
        var $sortid_length=6;//一定要保持跟字段sortid的长度一致
        function sort()
        {
        $conn=connect_mysql();
        }
function set_sortidlength($length=6)
        {
                $this->sortid_length=$length;
        }

QUOTE:

//保存成分类信息文件:

        function getinfo()
        {
                $sqlstring="SELECT *,CONCAT(parentpath,sortid) AS selfpath FROM `tab_sort` ORDER BY selfpath";
                $result=mysql_query($sqlstring) or die("查询失败!");
                while($row=mysql_fetch_array($result))
                {
                        $row['sortname']=str_repeat('☆',strlen($row['selfpath'])/SORTID_LENGTH).$row['sortname'];
                        $this->sort_info[]=(
                        array(
                        "sortid"=>$row['sortid'],
                        "sortname"=>$row['sortname'],
                        "description"=>$row['description'],
                        "parentpath"=>$row['parentpath'],
                        "selfpath"=>$row['selfpath']
                        )
                        );
                }
                return true;
        }
                
        function savetofile($filename='article.php')
        {

                $save_file=$this->save_path.$filename;
$headstr='<?php  $sort_info=';
$endstr='  ?>';

                if($this->getinfo())
                {
                        //将$sort_info分类信息写入文件
                        $handle=fopen($save_file,"w");
                        if(!file_exists($save_file))
                        {
                                die ("文件:".$save_file."创建失败!");
                        }
                        if(!fwrite ($handle,$headstr.var_export($this->sort_info,true).$endstr)) 
                        {
                                die ("文件:".$save_file."写入失败!");
                        }
                        fclose($handle);
                        print("创建文件".$this->save_file."成功!");
                }
        }

QUOTE:

//添加新分类:
function  addsort($sortname,$description="",$parentpath="")
{
        //检查该分类名称是否已存在
        $sqlstring="SELECT * FROM `tab_sort` WHERE `sortname` = '".$sortname."' and `parentpath`='".$parentpath."'";
        $result=mysql_query($sqlstring);
        if(mysql_num_rows($result))
        {
                die("该分类名称已经添加,请选择另一个分类名称");
        }
        //将分类的数据添加到数据库中
        $sqlstring="INSERT INTO `tab_sort` SET `sortname` ='".$sortname."',";
        $sqlstring.="`description`='".$description."',";
        $sqlstring.="`parentpath`='".$parentpath."'";
        mysql_query($sqlstring)or die("数据添加失败!");
        print "数据添加成功!";
}

}

?> 经楼下几位兄弟提醒,修改了这个方法,已测试!!

QUOTE:

//移动分类方法
//$sortid为待移动根分类的ID
//$old_parentpath为待移动根分类的parentpath
//$new_parentpath为目标分类的selfpath
//“待移动根分类”解释:例如要移动000005和它下面的所有分类,那么待移动根分类就是指000005
//“目标分类”解释:例如将000005和它的子孙分类移动到000002的下面,那么目标分类就是指000002
function movesort($sortid,$old_parentpath,$new_parentpath)
{
        $selfpath=$old_parentpath.$sortid;
        //判断目标分类是否为待移动分类的子孙类
        if(!strstr($new_parentpath,$selfpath))
        {
                die ("移动失败,目标分类为待移动根分类的子孙类!");
        }
        //更新待移动根分类的parentpath
        $sqlstring="UPDATE `tab_sort` SET `parentpath`='".$new_parentpath."' WHERE `sortid` ='".$sortid."'" ;
        mysql_query($sqlstring) or die("数据更新失败!");
        $length=strlen($old_parentpath);
        
        //更新移动分类的所有子孙分类。
        $sqlstring="UPDATE `tab_sort` SET `parentpath` = concat('".$new_parentpath."',substring(parentpath,$length+1)) WHERE parentpath like '".$selfpath."%'";
        mysql_query($sqlstring) or die("数据更新失败!");
        return true;
}[  本帖最后由 ultralqxq 于 2006-5-10 13:19 编辑 ] seraph (2006-4-21 14:46:08)顶下先,再看。 forest (2006-4-21 14:52:11)我也来顶楼主了! ultralqxq (2006-4-21 15:21:29)用到的语句都很简单。。仔细看的话。。应该不难看懂。。所以我也懒得写解释语句了!!
这个类很好的做到了以下三点。。所以我个人觉得很不错:
1。不用递归算法(当分类很多时,递归算法很吃内存。。)
2。删除、插入分类很方便
3。查询次数越少越好!查询语句越简单越好!!!(就是查询速度越快越好) 以风之名 (2006-4-21 17:19:14)支持楼主,看看先 ultralqxq (2006-4-21 20:26:59)我猜很少会有人会去看主楼的代码,还是举个具体例子来帮助理解吧:
关键在于对parentpath的处理:每当插入一个分类时,就将该分类的所属分类(即父类)的parentpath和sortid存入该分类的parentpath。

举例:插入的具体例子,我想插入以下这个样子的分类群(随便想了一个):

QUOTE:

第一个分类
        第二个分类
                第三个分类
                        第七个分类
                第四个分类
                        第五个分类
        第六个分类
                第八个分类
第九个分类得到的结果表现如下:

QUOTE:

sortid                sortname                parentpath                        
000001                第一个分类                                                        
000002                第二个分类                000001                                
000003                第三个分类                000001000002                
000004                第四个分类                000001000002
000005                第五个分类                000001000002000004
000006                第六个分类                000001
000007                第七个分类                000001000002000003
000008                第八个分类                000001000006
000009                第九个分类        下面以“第三个分类”来举例说明:

QUOTE:

1。它的父分类是谁 
2。它的所有上级分类有哪些parentpath:000001000002
截取parentpath的末6位字符串得到000002
所以他的父类是000002,他的所有上级分类有000001、000002(以6位字符切割parentpath)

QUOTE:

3。它处于哪一等级(level)parentpath:000001000002
de
得到selfpath=parentpath.sortid=000001000002000003
strlen($row['selfpath'])/SORTID_LENGTH=3(SORTID_LENGTH定义为6)
所以他处于第三等级

QUOTE:

4。它的兄弟分类有哪些查询parentpath=000001000002的有哪些。那它的兄弟分类就有哪些

QUOTE:

5。它的子孙分类有哪些查询parentpath like '000001000002000003%'(即第三分类的selfpath)

QUOTE:

6。对整个分类群进行排序SELECT *,CONCAT(parentpath,sortid) AS selfpath FROM `".$this->tablename."` ORDER BY selfpath

QUOTE:

7。在“第三个分类”下面添加一个新分类将第三分类的parentpath.sortid即000001000002000003存到新建分类的parentpath里就可以了

QUOTE:

8.删除分类、移动分类。如删除第三个分类,但又想保留第三个分类下面的子孙分类。。于是需要移动到其他分类下面删掉第三个分类那条记录后,可以选择性处理parentpath like '000001000002000003%'的记录(即第三个分类下的所有子孙分类)。
1.可以选择跟着删除:就是 drop * from tab_sort where  parentpath like '000001000002000003%'
2.可以移动到其他分类下面:就是将parentpath前面部分'000001000002000003’替换成新的父分类的parentpath(注意:新的父分类不能是第三个分类的子孙分类,即parentpath不能以'000001000002000003'开头)

以上操作都是一步查询就能得到想要的结果的。。。。
你遇到的无限级分类是怎么处理以上这些问题的??假如都能很轻松的解决掉。。呵,那么我绝对赞同你的分类也非常棒!!

[  本帖最后由 ultralqxq 于 2006-4-21 20:44 编辑 ] 以风之名 (2006-4-21 21:30:59)楼上的同志真是个热心人啊 yyp (2006-4-22 13:36:12)还有一种

主键是各个类别的完整的id,冗余字段是父层数,一样可以用用一条语句解决上边的问题

个人感觉比lz的简单,不过要研究一下 

一会来贴出详细比较

[  本帖最后由 yyp 于 2006-4-22 13:45 编辑 ] dennis (2006-4-25 12:51:32)以前做过这样的东东, 不建议用 parentpath 这样的字串的建立 parent/child 之间的关系.建议用 id.

id  name parent_id
1   aa     
2   bb     1
3   cc     2 lanjianxin (2006-4-25 15:00:02)这么说吧;

1。如果要求得当前类别的下一层分类列表(不需要所有层的)。
例如求000003的下一层分类别表

按照楼主的方法,需要2步查询
(1)需要先求出当前分类000003 得 parentpath 然后 组成查询条件 parentpath+sortid
(2)查询parentpath like '000001000002000003'(即第三分类的selfpath)的到下一层子类的列表数据

2。用传统的子记父方法,则只需要一步查询(省掉了楼主的父id拼串这一步,可以直接like)

3。我想问一下,是查询当前分类的下一层子类列表次数多,还是一次性查询所有子类列表次数多?
就实际需求来说,个人感觉在功能上,可能只查询下一层分类的次数应该远远的比查询所有分类的次数多不知道多少倍。

4。单从一次性查询所有子分类列表这个需求来说,效率比子记父那样好,不过还有一个方法比楼主的方法这样查更省事(当然,只是针对一次查出所有子分类列表,如8楼所说,确实是有那样的方法)。

简单的说,给我的感觉是,因为只显示下一层分类列表,这个需要经常用的,例如用户在浏览产品分类的时候,需要一层层的往下找的,这个可能用的次数是1000次, 而同时需要一次性显示所有子分类列表的功能我却可能只需要用1次就够了。 

那么就实际效率来说
                1000次显示下一层子列表       一次显示所有子类列表
子记父的方法         1000次sql查询                n次,根据树结构深度来看        

楼主的方法                1000*2次sql查询                 2次

其实说到底各有利弊,楼主的思路也只是某一方面比较好,不必这样( 你遇到的无限级分类是怎么处理以上这些问题的??假如都能很轻松的解决掉。。呵,那么我绝对赞同你的分类也非常棒!!) dashan (2006-4-25 16:11:06)没什么特别的
`path` varchar(255) NOT NULL default '',
这样更多 leslee (2006-4-25 18:37:10)偶也不认为会比"传统的"无限分类的设计 有效率提高之处. fakir (2006-4-25 19:12:46)楼主的这个方法我以前的帖子里也提过,但是还是保留了传统的fatherid列,毕竟有些情况会很好用 flappy (2006-4-26 11:52:24)不支持使用嵌套查询。

这些逻辑最好是由程序来完成。 dboyzhang (2006-4-26 12:32:20)个人感觉parentid  category_path并存比较好 ultralqxq (2006-4-30 10:52:27)

QUOTE:

原帖由  lanjianxin 于 2006-4-25 15:00 发表
这么说吧;

1。如果要求得当前类别的下一层分类列表(不需要所有层的)。
例如求000003的下一层分类别表

按照楼主的方法,需要2步查询
(1)需要先求出当前分类000003 得 parentpath 然后 组成查询条件 p ... 呵,这帖子发完后,我又耐心的加了注解,所以后来也就不管了。。刚才有人在群里提起了这帖子,所以过来溜溜。。。
看到你的回帖。。很开心有人这么认真的看我的帖,又回了一个这么好的回帖!!谢谢lanjianxin 
这样的讨论才有意思。。你提的这些问题,我在下一帖回复吧。看起来比较清晰些~~~认真讨论,共同进步啊!!!

而下面的那些回帖只提了一些个人意见,而没有举出具体例子,所以只能当个参考,保留一下。。以后等我遇到一个大型的分类项目时,我再把具体测试数据发上来给大家看。。。。 ultralqxq (2006-4-30 11:13:12)

QUOTE:

1。如果要求得当前类别的下一层分类列表(不需要所有层的)。
例如求000003的下一层分类别表按我的设计思路来做一步到位。查询 parentpath = '000001000002000003'就可以了

PS:'000001000002000003'是由000003的parentpath+sortid得到的,而这一步只是一个连接字符串操作,并不需要查询!所以查询操作只有一次。你下面说我需要1000*2次查询是错误的!

呵。。。我以为你提了好几个具体问题,连续看下去都是围绕这一个问题展开的讨论。。

我来扩展一下你提的问题吧:“如果还想知道000003的下下层的分类列表(不需要其他的任何分类信息)”
还是以000003来举例:查询 parentpath like '000001000002000003??????'  
注解:六个‘?’号就代表了任意一个ID号
再扩展一下你的问题:“如果还想知道000003下的第N层的子孙分类列表(N为任意数)”
$sign=str_repeat('?',6*N);
查询 parentpath like '000001000002000003'_$sign  //连接符是不是下划线啊?哈

PS:这时用fid时会觉得很难实现了。。当然“想知道000003下的第N层的子孙分类列表”的具体应用应该很少出现,但绝不是没有可能出现!!!


我不是特意要去掉fid这个字段的,而是我在我的具体应用里只用一个parentpath就完全足够了(所有查询都是一步到位,而且查询速度绝对不会慢),所以根本不需要fid这个字段了而已。假如你们有需要的话。难道你们不会自己加上去吗??

[  本帖最后由 ultralqxq 于 2006-4-30 12:15 编辑 ] ultralqxq (2006-4-30 11:23:00)

QUOTE:

原帖由  leslee 于 2006-4-25 18:37 发表
偶也不认为会比"传统的"无限分类的设计 有效率提高之处. 有没有提高效率我不清楚,当初我设计这个分类时完全是以我在4楼发的那三条准则来考虑的!!!
但我相信我的这个类在执行速度上绝对不会慢,因为它只是简单的将字符串按字母顺序进行排序。这个操作对SQL来说是小菜一碟了!!!将来我肯定会遇到一个很大型的分类系统。。。到那时我再来测测这个设计的效率怎么样吧!!

[  本帖最后由 ultralqxq 于 2006-4-30 14:50 编辑 ] fengyun (2006-4-30 12:52:22)呵呵
偶来顶下楼主 散步的男人 (2006-5-06 16:52:43)学习了,今天回家实施!!
谢谢ultralqxq halley (2006-5-07 08:33:50)效率绝对是提高的,用order语句来代替递归,3年前我就在im286贴过相似的代码,不过只有5,6行
我用了3年 没发现任何局限性,包括输出数形状,包括列表 都没问题,一句sql搞定

一楼的这个代码有点缺陷,可能是由于path的格式造成的,建议改成1:3:15中间用符号隔开的形式,不要用相同位数,这样白白浪费数据库空间

[  本帖最后由 halley 于 2006-5-7 08:36 编辑 

http://hi.baidu.com/newsbhacker/item/a046c634b99d84e2e7bb7a8f
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值