現在有無限分類表結構如下:
create table OA_GoodType ( ID int not null,--ID欄位,u也可以說是子ID Name varchar(50) null,--名稱 Pid int not null,--父類ID ,如果等於ID的時候就是頂級分類,(也可以設定為0的時候是頂級分類) Path varchar(100) not null,--路徑(父類的集合,全部都包含‘0,’開頭,最後一位元字就是自身的id的值) orderId int null,--排序 constraint PK_OA_GOODTYPE primary key (ID))
上面提到Path欄位都是以 '0,' 開頭,這樣有個好處就是選出某個類(ID為3)下面的子類時直接可以用path like ',3,'
為表示結構關係,插入以下資料:
INSERT INTO OA_GoodType([Id],[Name],[Pid],[Path],[OrderId]) VALUES(1,'被服',1,'0,1',4)INSERT INTO OA_GoodType([Id],[Name],[Pid],[Path],[OrderId]) VALUES(2,'提花被',1,'0,1,2',2)INSERT INTO OA_GoodType([Id],[Name],[Pid],[Path],[OrderId]) VALUES(3,'軍用棉被',1,'0,1,3',1)INSERT INTO OA_GoodType([Id],[Name],[Pid],[Path],[OrderId]) VALUES(4,'軍用棉被1.2米',2,'0,1,2,4',1)INSERT INTO OA_GoodType([Id],[Name],[Pid],[Path],[OrderId]) VALUES(37,'救援工具類',37,'0,37',2)INSERT INTO OA_GoodType([Id],[Name],[Pid],[Path],[OrderId]) VALUES(43,'糧油食品類',43,'0,43',6)
一般來說,對該結構的無限分類,要選出樹狀的結構圖非常簡單:只一條sql語句就夠了:
select * from OA_GoodType order by path asc
但是這樣的語句無法按照OrderID去排序
所以如果有要求排序的話,就必須對錶進行遞迴了,
如下的方法,對資料按照排序產生一顆樹狀的DataTable:
/// <summary> /// dt 要從中產生樹狀資料來源 /// IdField ID欄位列名 /// ParentField 父級ID列名 /// Pid 父級ID值 /// OrderField 排序欄位 /// </summary>public DataTable GetTreeList(DataTable dt, string IdField, string ParentField, int Pid,string OrderField=""){ DataTable newDT = dt.Clone();// 複製dt 的結構,包括所有 dt 架構和約束,並無資料; DataRow[] rows; /* * 如果父級pid等於本ID時表示頂級分類的情況 用以下語句 */ if (Pid==0)//選出頂級的類別 {rows = dt.Select(ParentField + "=" + IdField, OrderField); // 從dt 中查詢合格記錄(選出所有大類); } else//選出屬於所傳ID的子類 {rows = dt.Select(ParentField + "<>" + IdField + " and "+ParentField+"=" + Pid, OrderField); // 從dt 中查詢合格記錄(選出大類pid的子類); } /* * 如果父ID為0的時候表示頂級分類,用以下語句 * rows = dt.Select(ParentField + "=" + Pid, OrderField); */ if (rows.Length > 0) {foreach (DataRow row in rows) // 將查詢的結果添加到dt中;{ newDT.Rows.Add(row.ItemArray); int innerpid = int.Parse(row["id"].ToString()); DataRow[] Childows = dt.Select(ParentField + "<>" + IdField + " and " + ParentField + "=" + innerpid, OrderField);//選出子類 if (Childows.Length > 0)//如果存在子類,對子類進行遞迴 {DataTable innerDT = GetTreeList(dt, IdField, ParentField, innerpid, OrderField);//擷取子類DataTableforeach (DataRow innerrow in innerDT.Rows)//對子類的DataTable添加進總資料來源{ newDT.Rows.Add(innerrow.ItemArray);} }} } dt.Dispose();//現不清楚是否需要該語句,執行該語句也正常顯示 return newDT;}
用法如下:
DataTable dt = comm_table.GetList("select * from OA_GoodType").Tables[0];//重組樹狀資料來源,所以此處的SQL可以不用排序 subCate.DataSource = comm_table.GetTreeList(dt,"id","Pid",0,"OrderID"); subCate.DataBind();//subCate是Repeater控制項
附上對於無限分類的表的一些SQL操作
1、選出某個類所屬的父類路徑(包括名稱和id);對於本表而言,本方法一般是在列出屬於某個類下面的物品詳情的時候,順便列出該物品所屬的一系列分類
create proc getPathInfo @Id varchar(120), --需要選出路徑名稱的記錄的id 或者路徑 @IdField varchar(30), --儲存ID的欄位名 @NameField varchar(30), --儲存名稱的欄位名 @PathField varchar(30), --儲存路徑的欄位名 @Table varchar(30), --要操作的表名 @Split varchar(10)='→', --串連路徑用到的分隔字元 @isPath bit = 0 --傳進來的是路徑的話設定為1 as DECLARE @sql nvarchar(300) DECLARE @Path varchar(50) --儲存父類路徑的id集合 DECLARE @PathName varchar(100) --迴圈時儲存各個父類名稱(如果有的名稱太長可適當增大該欄位) DECLARE @AllPath varchar(500) --儲存最終各個父類名稱形成的字串(如果有的名稱太長可適當增大該欄位) --DECLARE @index int SET @AllPath='' if(@isPath=1)--傳進來的是路徑的時候 SET @Path=@Id else begin SET @sql = 'select @Path='+@PathField+' from '+@Table+' where '+@IdField+'='+@Id EXEC sp_executesql @sql, N'@Path as varchar(50) OUTPUT', @Path OUTPUT end --print(@Path) DECLARE @NameCursor cursor set @sql=N'select '+@NameField+' from '+@Table+' where '+@IdField+' in('+@Path+') order by '+@PathField+' asc'-- 遊標查詢語句 -- 遊標處理 set @sql = N'SET @NameCursor = CURSOR FORWARD_ONLY FOR '+@sql+N' OPEN @NameCursor' EXEC sp_executesql @sql, N'@NameCursor CURSOR OUTPUT', @NameCursor OUTPUT fetch NEXT from @NameCursor into @PathName while @@fetch_status=0 begin SET @AllPath = @AllPath + @Split + @PathName--把路徑用串連符串連起來,由於一開始的時候@AllPath 為空白,所以再返迴路徑的時候要截取掉第一個分隔字元 fetch NEXT from @NameCursor into @PathName end; --對於名字截取掉第一個分隔字元,sqlserver的字串下標以1開始,對於ID,所有的ID都包含了 0,所以要從第三個開始 select substring(@AllPath,len(@Split)+1,len(@AllPath)) as PathName,substring(@Path,3,len(@Path)) as PathID close @NameCursor deallocate @NameCursor
可用語句 exec getPathInfo 20,'id', 'name', 'path', 'OA_GoodType' 去測試以上語句的返回結果
二、選出某個類下面的所有子類id,本方法用處很多,例如一個分類下面有好多個分類,每個分類下面都包含了物品(對於文章系統的話可以說是文章),
那麼一般來說點擊父類的時候都需要列出屬於該分類下面的所有文章,包括其子類或者子類下的子類;
create proc getChildInfo @Id varchar(100), --需要選出路徑名稱的記錄的id (或者路徑) @IdField varchar(30), --儲存ID的欄位名 @NameField varchar(30), --儲存名稱的欄位名 @PathField varchar(30), --儲存路徑的欄位名 @Table varchar(30), --要操作的表名 @Split varchar(10)=',', --串連所有子類用到的分隔字元 @isPath bit = 0 --傳進來的是路徑的話設定為1 as DECLARE @sql nvarchar(300) DECLARE @ChildID varchar(20) --儲存父類路徑的id集合 DECLARE @AllChild varchar(120) SET @AllChild='' -- 遊標查詢語句 if(@isPath=1)--傳進來的是路徑的時候 SET @sql = N'select '+@IdField+N' from '+@Table+N' where '+@PathField+N'='''+@Id+N''' or '+@PathField+N' like '''+@Id+N',%''' else SET @sql = N'select '+@IdField+N' from '+@Table+N' where '+@PathField+N' like ''%,'+@Id+N''' or '+@PathField+N' like ''%,'+@Id+N',%''' DECLARE @IDCursor cursor -- 遊標處理 set @sql = N'SET @IDCursor = CURSOR FORWARD_ONLY FOR '+@sql+N' OPEN @IDCursor' EXEC sp_executesql @sql, N'@IDCursor CURSOR OUTPUT', @IDCursor OUTPUT fetch NEXT from @IDCursor into @ChildID while @@fetch_status=0 begin SET @AllChild = @AllChild + @Split + @ChildID--把路徑用串連符串連起來,由於一開始的時候@AllPath 為空白,所以再返迴路徑的時候要截取掉第一個分隔字元 fetch NEXT from @IDCursor into @ChildID end; --對於名字截取掉第一個分隔字元,sqlserver的字串下標以1開始,對於ID,所有的ID都包含了 0,所以要從第三個開始 select substring(@AllChild,len(@Split)+1,len(@AllChild)) as PathName close @IDCursor deallocate @IDCursor
可用語句 exec getPathInfo 1,'id', 'name', 'path', 'OA_GoodType' 去測試以上語句的返回結果
結束!!!!!!!!!
附:再分享一下無限分類查詢、添加和修改的時候的一些操作:
/* 查詢某個類(假設id是3)下的直接子類*/Select * from OA_GoodType where Pid=3/* 查詢某個類(假設id是3)下的所有子類*/Select * from OA_GoodType where pid=3 or path like '%,3,%'
添加和修改的時候所屬大類的值都使用用path,如下:
頁面上用select 框列出所有的分類:
如過要按照排序去列出來,那麼就用上面的方法先排序,或者直接用:Select * from OA_GoodType order by path asc
然後用這方法列出來一個梯級的select:(因為C#的丟失了,就用PHP的代替了:)假設資料來源是一個數組:$cateArr
$options = '';foreach($step_1[0] as $v){$options .= '<option id="'.$v['id'].'" value="'.$v['path'].'">';$cate_count = intval($v['level']);if($cate_count > 1){//如果有兩級及以上//將類別欄位中的每一級分類替換為4個空格輸出,設定縮排$options .= str_repeat(' ',$cate_count-1);$options .= '|--'.$v['name'].'</option>';}else{$options .= $v['name'].'</option>';}}
然後直接輸出就可以了:
<select name="belong" id="belong" > <option id="0" value="0">頂級分類</option> <?php echo $options?></select>
顯示如下:
這樣在儲存的時候可以這樣進行:
$belong = preg_replace('/[^\d|,]/','',$_POST['belong'];$pid = 0;$level = 1;$path = '';//處理輸入資料if($belong && strpos($belong,',') === false){//如果有選擇地區,但是地區不合法MessageShowAndGoback('所屬地區有誤!');}if($belong == '0'){//是頂級分類的時候,路徑就是 0,+本類id$path_pre = '0,';}else{//不是頂級分類的時候,路徑就是父類路徑 $belong +本類id$path_pre = $belong.',';$Arr = explode(',',$belong);$pid = end($Arr);$level = count($Arr);}$insertArr = array(//這裡多了一個欄位:level,好處就是:當你在只需要某一級分類的時候可以直接獲得:select * from table where level=2;'level'=>$level,'pid'=>$pid,'path'=>$path);//插入相應記錄之後記著更新該記錄的path欄位!因為path欄位的最後一個數字是本記錄的id的值,這個值是自增的,所以只能現插入後更新://假設記錄的自增id是:$pid$sql = 'Update table set path = '.$path_pre.$id.' where id='.$id;
修改的時候比較複雜,要處理的問題比較多,例如:1、不能把本類的所屬分類 修改成它的子類。2、要把本類的子類也一同轉移到新的所屬分類上,那就涉及把它的子類的path改成對應的新的path頁面上列出的分類仍然是用path作為值:
(可以用一下語句設定給分類所屬的分類:)
jQuery("#"+$id).selected=true;
js階段判定是否違反1中所說條件:
把原來的path儲存在一個叫old_path的隱藏欄位中
var path_old = form.old_path.value;var path_new = form.belong.value;if(path_new.indexOf(path_old+",") != -1){//移動到的分類中包含了原來的分類alert('不能移動到本類的子分類下!');return false;}
php儲存的時候要做如下處理:
$belong = preg_replace('/[^\d|,]/','',$this->input->post('belong'));//擷取原來的所屬地區資訊$old_path = preg_replace('/[^\d|,]/','',$this->input->post('old_path'));//根據路徑擷取原來分類所屬地區$parents_arr = explode(',',$old_path);$old_pid = $parents_arr[count($parents_arr)-2];//儲存原來的所屬地區$fixPath = false;//判斷是否有修改所屬地區//1、判斷原來的所屬分類和現在的所屬分類是不是一致$newParends = explode(',',$belong);$newPID = end($newParends);if($old_pid != $newPID){//原來的所屬分類和現在的所屬分類是不是一致//也不是移動到本分類下if($rid != $newPID){$fixPath = true;}}//1、如果有修改所屬地區,需要對該地區下的所有分類進行轉移,更改path以及levelif($fixPath){//有修改所屬分類的時候if(strpos($belong,$old_path) !== false){MessageShowAndGoback('不能移動到本類的子分類下');}$Arr = explode(',',$belong);$updatetArr['pid'] = end($Arr);$updatetArr['level'] = count($Arr);//新的地區等級$updatetArr['path'] = $belong.','.$rid;$old_level = count($parents_arr)-1;//原來地區等級}if($fixPath){//有修改路徑的時候,把他的子類全部都改過來$where = ' (pid =\''.$rid.'\' or path like \''.$old_path.',%\')';$newLevel = $updatetArr['level']-$old_level;$rows = $this->AreaClassifys->Update(array('path'=>"replace(path,'$old_path','".$updatetArr['path']."')",'level'=>"level+$newLevel"),$where,false);//$sql = "update cms_category set path = replace(path,'$src_path','$new_path') where path like '$src_path%' or pid = $rid}
完結