C#無限分類,遞歸重新生成排好序的樹狀數據源

現在有無限分類表結構如下:

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);//獲取子類DataTable
		foreach (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以及level
if($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
}
完結
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章