T-SQL无限级分解
SQL非递归分级

sqlserver另类非递归的无限级分类(存储过程版) 网络上很多无限级的分类,但无非是两种,一种是递归算法,一种是非递归算法。
下面是我统计的几种方案:第一种方案(递归式):简单的表结构为:CategoryID int(4),CategoryName nvarchar(50),ParentID int(4),Depth int(4)这样根据ParentID一级级的运用递归找他的上级目录。
还有可以为了方便添加CategoryLeft,CategoryRight保存他的上级目录或下级目录第二种方案:设置一个varchar类型的CategoryPath字段来保存目录的完整路径,将父目录id用符号分隔开来。
比如:1,5,8,10第三种方案:每级分类递增两位数字的方法示例:一级分类:01,02,03,04...二级分类:0101,0102,0103,0104...三级分类:010101,010102,010103...分析一下,其实第三种方案并不能真正意义上做无限级的分类,而第二种方案,虽然比较容易得到各上级及下级的分类信息。
但,添加和转移分类的时候操作将很麻烦。
而且,也完全违反了数据库设计范式。
其实我也一直在用第二种方案的。
为了查找方便,我有时都在新闻表里加上CategoryID和CategoryPath而我今天要说的算法其实是第二种方案的改进版,一般做分类都是使用一个表格来保存分类信息。
而我这里,要新建两个表格,一个表格是保存分类信息表,一个保存分类关系表。
表结构如下:表1:tomi_CategoryCategoryID int(4), '编号CategoryName nvarchar(50), '分类名称Depth int(4), '深度表2:tomi_CategoryBindCategoryID int(4),BindCategoryID int(4),Depth int(4),添加,编辑,删除操作有点麻烦。
T-SQL简介及基本语法

T-SQL简介及基本语法⼀、T-SQL概述SQL Server⽤于操作数据库的编程语⾔为Transaction-SQL,简称T-SQL。
T-SQL与PL/SQL不同,并没有固定的程序结构。
T-SQL包括以下4个部分:DDL:定义和管理数据库及其对象,例如create、alter和drop等。
DML:实现对数据库表各对象的操作,例如insert、update等。
DCL:数据控制语⾔,实现对数据库进⾏安全管理和权限管理等控制,例如grant、revoke、deny等。
附加的语⾔元素。
T-SQL的附加语⾔元素,包括变量、运算符、函数、注释和流程控制语句等。
在T-SQL中,命令和语句的书写是不区分⼤⼩写的。
⼆、T-SQL编程基础1、标识符①T-SQL规则标识符由字母、数字、下划线、@、#、$符号组成,其中字母可以是a-z或A-Z,也可以是来⾃其他语⾔的字母字符。
⾸字符不能为数字和$。
标识符不允许是T-SQL保留字。
标识符内不允许有空格和特殊字符长度⼩于128②界定标识符 对于不符合标识符规则的标识符,则要使⽤界定符⽅括号([])或双引号(“”)将标识符括起来。
如标识符[My Table]、“select”内分别使⽤了空格和保留字select。
2、数据类型在SQL Server中提供了多种系统数据类型。
除了系统数据类型外,还可以⾃定义数据类型。
①系统数据类型(1)精确数字数据类型int 存储整型数值,存储数值范围为-231~231-1。
bigint bigint⽐int能存储更⼤的数值,存储数值范围为-263~263-1。
smallint 数据类型的范围数值⽐int更⼩,在-215~215-1之间。
定义这种数据类型的时候⼀定要⼩⼼,要确定存储的数据不会超过smallint所能存储的数值范围。
tinyint 数据类型的范围数值⽐smallint更⼩,存储从 0 到 255 的整型数据。
decimal/numeric decimal[(p,s)]和numeric[(p,s)]这两种数据类型⽤于存储相同精度和范围的数据(⼩数点的左、右两边存储的数值位数相同),所能存储的数值范围为-1038+1~1038-1。
SQL Server 存储层级数据实现无限级分类

由于数据库存储的数据都是以平面方式存储,所以目前大部分论坛和其他程序都是用递归来展现层次数据的,如果分类的层次十分深的话那么使用的递归次数相当可观,对性能的影响也非常大。
最近要做一个分类信息的平台就遇到这个问题了,那么如何实现快速的展现分层数据呢?MYSQL 的开发者帮我们想到了一个算法,这个算法目前唯一的问题就是尚未实现分类排序,我们可以通过右值的反向排序实现先入先出的排序。
在这里我们需要了解的是如何用 SQL Server 来实现,我们就以省市县数据库为例来实现:如图所示我们将一个树节点的左右各编上号码,就可以看出一些规律,山西的左右值为(8,17),那么所有左值大于8,右值小于17的节点都是属于山西的子节点。
稷山先的左右值为(14,15),那么他的所有父节点就是左值小于14,右值大于15的节点,怎么样,用这个方法实现的无限级分类性能绝对是顶呱呱的。
一次查询就可以查出属于某个节点的数据以及他子节点的数据。
这个算是我见过性能最高的无限级分类算法。
其他算法跟这个对比基本没有任何优势。
我们先建立一个数据表,结构如下图(LID 为左值,RID 为右值,Tree 为节点深度,Name 和 ID 就不多说了,节点的索引和名称)我们可以使用下面的存储过程来获得一个节点和其子节点:1.CREATE PROCEDURE CLSP_ZoneSelect2.(3. @Root INT,4. @Tree INT5.)6.AS7. SELECT Z.ID,Z.Tree,8. FROM CL_ZoneData AS Z,CL_ZoneData AS P9. WHERE P.ID = @Root10. AND Z.LID >= P.LID AND Z.RID <= P.RID11. AND (@Tree = 0 OR Z.Tree <= P.Tree + @Tree)12. ORDER BY Z.LID ASC13.GO我们可以用下面这个存储过程来在一个节点下插入新的子节点:1.CREATE PROCEDURE CLSP_ZoneInsert2.(3. @Root INT,4. @Name NVARCHAR(50)5.)6.AS7. DECLARE @RID AS INT,@NID AS INT,@Tree AS INT8.9. SET @RID = 110. SET @NID = 011. SET @Tree = 112.13. IF @Root = 014. BEGIN15. SELECT TOP 1 @RID = RID + 116. FROM CL_CateData ORDER BY RID DESC17. END18. ELSE19. BEGIN20. SELECT @RID = RID, @Tree = Tree + 121. FROM CL_ZoneData WHERE ID = @Root22. END23.24. IF @Root = 0 OR @RID > 125. BEGIN26. UPDATE CL_ZoneData SET RID = RID + 2 WHERE RID >= @RID27. UPDATE CL_ZoneData SET LID = LID + 2 WHERE LID > @RID28.29. INSERT INTO CL_ZoneData(LID,RID,Tree,Name)30. VALUES (@RID,@RID + 1,@Tree,@Name)31.32. SET @NID = SCOPE_IDENTITY()33. END34. SELECT @NID35.GO删除一个节点可以用下面的存储过程:1.CREATE PROCEDURE CLSP_ZoneDelete2.(3. @ID INT4.)5.AS6. DECLARE @LID AS INT, @RID AS INT, @WID AS INT, @DID AS INT7. SET @DID = 08. SELECT @DID = ID, @LID = LID, @RID = RID, @WID = RID - LID + 1 FROM CL_ZoneData WHERE ID = @ID9. IF @DID != 010. BEGIN11. DELETE FROM CL_ZoneData WHERE LID BETWEEN @LID AND @RID12. UPDATE CL_ZoneData SET RID = RID - @WID WHERE RID > @RID13. UPDATE CL_ZoneData SET LID = LID - @WID WHERE LID > @RID14. END15. SELECT @DID16.GO。
09SQL无限分级结构 管家婆 分类路径

查询分类2底下的所有分类
============================底下是测试数据=============================
--添加分类表
create table tab
INSERT INTO [article]([aid],[id],[aname])VALUES(1,7,'7这是名称777777777777777777777777')
INSERT INTO [article]([aid],[id],[aname])VALUES(1,9,'9这是名称999999999999999999999999')
INSERT INTO [tab]([id],[fid],[name],[path])VALUES(6,1,'分类1-1',',6,1,')
INSERT INTO [tab]([id],[fid],[name],[path])VALUES(7,4,'分类7-4-2',',7,4,2,')
INSERT INTO [tab]([id],[fid],[name],[path])VALUES(8,5,'分类8-5-2',',8,5,2,')
(
aid int,
id int,
aname varchar(100),
)
go
INSERT INTO [article]([aid],[id],[aname])VALUES(1,1,'1这是名称1111111111111111111')
T-SQL语法

T-SQL语法⼀、数据库存储结构SQL Server 7.0中的每个数据库有多个操作系统⽂件组成,数据库的所有资料、对象和数据库操作⽇志均存储在这些操作系统⽂件中。
根据这些⽂件的作⽤不同,可以将它们划分为以下三类:主数据⽂件:每个数据库有且只有⼀个主数据⽂件,它是数据库和其它数据⽂件的起点。
主数据⽂件的扩展名⼀般为.mdf;辅数据⽂件:⽤于存储主数据⽂件中未存储的剩余资料和数据库对象,⼀个数据库可以没有辅数据⽂件,但也可以同时拥有多个辅数据⽂件。
辅数据⽂件的多少主要根据数据库的⼤⼩、磁盘存储情况和存储性能要求⽽设置。
辅数据⽂件的扩展名⼀般为.ndf;⽇志⽂件:存储数据库的事务⽇志信息,当数据库损坏时,管理员使⽤事务⽇志恢复数据库。
⽇志⽂件的扩展名⼀般为.ldf。
每个数据库中⾄少两个⽂件:主数据⽂件和⽇志⽂件。
SQL Server数据库⽂件除操作系统所赋予的物理⽂件名称外,还有⼀个逻辑名称。
数据库的逻辑名称应⽤于Transact-SQL语句中。
例如,对于master系统数据库,master为其逻辑名称,使⽤Transact-SQL语句操作数据库时,均使⽤该名称。
⽽对应的物理⽂件名称为master.mdf、其⽇志⽂件名称为master.ldf。
为了管理⽅便,可将多个数据库⽂件组织为⼀组,称作数据库⽂件组。
⽂件组能够控制各个⽂件的存放位置,其中的每个⽂件常建⽴在不同的硬盘驱动器上,这样可以减轻每个磁盘驱动器的存储压⼒,提⾼数据库的存储效率,从⽽达到提⾼系统性能的⽬的。
SQL Server采⽤⽐例填充策略使⽤⽂件组中的每个⽂件提供的存储空间。
在SQL Server中建⽴⽂件和⽂件组时,应注意以下两点:每个⽂件或⽂件组只能属于⼀个数据库,每个⽂件也只能成为⼀个⽂件组的成员,⽂件和⽂件组不能跨数据库使⽤;⽇志⽂件是独⽴的,它不能成为⽂件组的成员。
也就是说,数据库的资料内容和⽇志内容不能存⼊相同的⽂件或⽂件组。
SQL笔记(2)-T-SQL设计模式

SQL笔记(2)-T-SQL设计模式⼀、ITERATOR(迭代) 此模式提供⼀种在相似对象列表中遍历对象的标准化⽅法。
在SQL SERVER中的同义词是游标。
DECLARE tables CURSOR FOR SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES FOR READ ONLYDECLARE @table varchar(40)OPEN tablesFETCH tables INTO @tableWHILE (@@FETCH_STATUS = 0)BEGINEXEC sp_help @tableFETCH tables INTO @tableENDCLOSE tablesDEALLOCATE tables注:游标的清理代码:在CLOSE后紧跟DEALLOCATE,实际上可以只运⾏DEALLOCATE,并且游标也能⾃动关闭。
但这不是最⾃然,也不是最常见的⽅法。
可以理解为:CLOSE抵消OPEN,DEALLOCATE与DECLARE则相反,这样可让代码保持对称且合乎逻辑。
⼆、INTERSECTOR(交集)此模式是表⽰集合交集的⼀种模板。
1、推荐⽅法:SELECT panyname,o.orderidFROM customer c INNER JOIN orders o ON c.customerid = o.customerid2、旧式语法(不推荐使⽤)SELECT panyname,o.orderidFROM customer c ,orders oWHERE c.customerid = o.customerid注:实现集合交集还有许多变种⽅法。
但是惯例⽅法就是⽅法1,⽅法2在实现左(右)联接时,条件的表⽰及结果都可能出现问题,SQL SERVER的后续版本将会取消此种联接⽅式。
三、QUALIFIER(限定)限定数据等价于筛选查询所返回的⾏数。
T-SQL查询处理详解
T-SQL查询处理详解(续)首先简单提一下T-SQL。
T-SQL的正式名称是Transact-SQL,是ANSI和ISO SQL标准的Microsoft SQL Server扩展,而PL/SQL是ORACLE对SQL标准的扩展。
对于T-SQL编程,用得最广泛的,莫过于查询(Querying)。
要想写出高质量、高性能的查询语句,必须深入地了解逻辑查询处理。
一、逻辑查询处理的各个阶段(5)SELECT DISTINCT TOP(<top_specification>) <select_list>(1)FROM <left_table><join_type> JOIN <right_table> ON <on_predicate>(2)WHERE <where_predicate>(3)GROUP BY <group_by_specification>(4)HAVING <having_predicate>(6)ORDER BY <order_by_list>上边语句是一个普通格式的查询语句,基本包含了所有的查询条件和关键字。
你可能会发现前边的序号并不是按顺序来的,被你说对了,这是SQL与其他编程语言不同的最明显特征,就是它的执行顺序并不是按照编写顺序来的。
上边的序号,就是查询语句在执行过程中的逻辑处理顺序。
下面简单介绍一下各个阶段都干了啥事。
(1)FROM 阶段FROM阶段标识出查询的来源表,并处理表运算符。
在涉及到联接运算的查询中(各种join),主要有以下几个步骤:a.求笛卡尔积。
不论是什么类型的联接运算,首先都是执行交叉连接(cross join),求笛卡儿积,生成虚拟表VT1-J1。
b.ON筛选器。
这个阶段对上个步骤生成的VT1-J1进行筛选,根据ON子句中出现的谓词进行筛选,让谓词取值为true的行通过了考验,插入到VT1-J2。
T-SQL语句操作数据库——基本操作
--考号 --学号ABLE 表名
例如:
DROP TABLE peoInfo
三、使用SQL语句创建和删除约束
约束的目的是确保表中数据的完整性。
常用的约束类型:
主键约束(Primary Key constraint):要求主键列数据唯一,并且不允许为空。 唯一约束(Unique Constraint):要求该列唯一,允许为空,但只能出现一个空值。 检查约束(Check Constraint):某列取值范围显示、格式限制等,如有关年龄的约束。 默认约束(Default Constraint):某列的默认值,如我们的性别默认为“男”。 外键约束(Foreign Key Constraint):用于在两表之间建立关系,需要指定引用主表的那一列。
示例:
/*--删除peoInfo表中地址默认约束的语句--*/ ALTER TABLE peoInfo DROP CONSTRAINT DF_peoAddress
/*--添加默认约束--*/ ALTER TABLE peoInfo ADD CONSTRAINT DF_peoAddress DEFAULT ('地址不详') FOR peoAddress
/*--添加检查约束--*/ ALTER TABLE peoInfo ADD CONSTRAINT CK_stuAge CHECK(peoAge BETWEEN 15 AND 40)
二、创建和删除表
1、创建表的语法如下:
CREATE TABLE 表名 (
字段1 数据类型 列的特征, 字段2 数据类型 列的特征, ... )
示例:
需求:创建学员信息表peoInfo。
USE people GO CREATE TABLE peoInfo (
t-sql的流程控制语句使用
T-SQL的流程控制语句使用1. 概述T-SQL(Transact-SQL)是一种用于Microsoft SQL Server数据库管理系统的编程语言。
它扩展了标准的SQL语言,增加了许多面向过程的编程功能,其中包括流程控制语句。
流程控制语句可帮助我们实现条件判断、循环和异常处理等功能,使得我们的T-SQL代码更灵活和强大。
在本文档中,我们将讨论T-SQL中常用的流程控制语句及其使用方法。
2. 条件判断条件判断语句用于根据给定的条件执行相应的代码块。
T-SQL中常用的条件判断语句有IF语句和CASE语句。
2.1 IF语句IF语句按照给定的条件执行特定的代码块。
语法如下:IF condition{ sql_statement | statement_block }[ ELSE{ sql_statement | statement_block } ]•condition:要判断的条件,可以是一个布尔表达式或变量。
•sql_statement:要执行的SQL语句。
•statement_block:包含多个SQL语句的代码块。
示例:IF @score >=90PRINT '优秀'ELSE IF @score >=80PRINT '良好'ELSE IF @score >=60PRINT '及格'ELSEPRINT '不及格'2.2 CASE语句CASE语句根据给定的条件选择执行相应的代码块。
它有两种形式:简单CASE 表达式和搜索CASE表达式。
2.2.1 简单CASE表达式简单CASE表达式基于对单个表达式的比较来选择执行代码块。
语法如下:CASE expressionWHEN value THEN { sql_statement | statement_block }[ WHEN value THEN { sql_statement | statement_block } ]...[ ELSE { sql_statement | statement_block } ]END•expression:要比较的表达式。
DB-SQLServer:T-SQL语法----T-SQL控制流语句
DB-SQLServer:T-SQL语法----T-SQL控制流语句 批处理: ⼀个批处理段是由⼀个或者多个语句组成的⼀个批处理,之所以叫批处理是因为所有语句⼀次性被提交到⼀个SQL实例。
批处理是分批提交到SQL Server⽰例,因此在不同的批处理⾥局部变量不可访问。
在不同批处理中,流程控制语句不能跨批处理。
如果想让多个语句分多次提交到SQL实例,则需要使⽤GO关键字。
GO关键字本⾝并不是⼀个SQL语句,GO关键字可以看作是⼀个批处理结束的标识符,当遇到GO关键字时,当前GO之前的语句会作为⼀个批处理直接传到SQL实例执⾏。
不同的批处理局部变量不可访问,例如: DECLARE @i int; SET @i = 1; GO --分批了 PRINT @i --@i在这个批⾥未定义 输出:消息137,级别 15,状态 2,第 1 ⾏必须声明标量变量 "@i"。
控制流语句不能跨批处理,例如: DECLARE @i int; SET @i = 1; IF(@i = 1) PRINT('1'); GO --分批了 ELSE PRINT('不知道'); --ELSE找不到IF了,控制流语句不跨批,因此报错。
输出结果如下:1消息 156,级别 15,状态 1,第 1 ⾏关键字 'ELSE' 附近有语法错误。
控制流语句也称为流程控制语句,是和⾼级编程语⾔中的类似功能⼀致的,引⼊控制流语句将使T-SQL代码有顺序执⾏转变为按控制执⾏。
1、程序块语句BEGIN...END 程序块语句⽤于将多条T-SQL语句封装起来构成⼀个程序块。
SQLServer在处理时,将整个程序块视为⼀条T-SQL语句执⾏。
begin <T-SQL命令⾏或程序块>end 经常与while或if...else组合起来使⽤,可以相互嵌套。
2、判断语句IF...ELSE if...else语句⽤于条件测试,系统将根据条件满⾜与否来决定如何执⾏语句,else⼦句是可选的。
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
我们在做分类处理的时候,总会遇到递归的处理,比如说地区就是一个例子,中国--北京--西城区,我们可以把这样的信息存储在一个数据表中,用ParentID区分根节点和叶子节点。
假如我们要做导航,得到了”西城区”,但是还要得到他的父级,或夫父级,一种方式是用程序来处理,也是很简单,另一种方式就是用数据库的功能。
既然数据库能完成这件事,何必在用程序呢?在SqlServer2005以前的版本中,也能处理这种情况,不过当时用的是存储过程,代码也比较多,表设计的时候,还的加一个表示“深度”的字段,当时我也写过相关的文章,参见:存储过程实现无限级分类(1)存储过程实现无限级分类(2)存储过程实现无限级分类(3)但是,在学习SqlServer2005新加的特性的时候,注意到WITH语句能实现这样的功能,并且代码简单,简练,最重要的是好理解。
下面是一张递归的表结构图:里边的parentID记录的就是他们之间的关系;比如我们要查询id = 10008,并且parentID 为10008的数据怎么办呢?如:呵呵,不留悬念了,直接上代码吧!其实挺容易理解的:WITH CategoryInfo AS(SELECT id,text,parentid FROM Recursive WHERE id=10008UNION ALLSELECT a.id,a.text,a.parentid FROM Recursive AS a,CategoryInfo AS b WHERE a.parentid= b.id)SELECT*FROM CategoryInfo看看是不是简单,简练,好理解!赶快试试吧!这是从上向下查,如果从下向上查,怎么查呢,自己举一反三吧!在做考题的的时候,涉及到分类,虽然不是无限级的,但是,为了以后扩展用,想做成无限级,在网上找找了,一个用存储过程作的,虽然添加,编辑,移动,用的是存储过程,可是在读出来的时候只用了一条Select 语句,感觉挺爽的,下面我把存储过程列出来,我只用到了添加,编辑,没有用到移动。
1,表结构============================================= ======表结构:表名:Tb_Column表结构(所有字段非空):Column_ID int 主键(注:非标识)Column_Name nvarchar(50)分类名称Parent_ID int 父分类ID(默认值0)Column_Path nvarchar(1000) 分类路径Column_Depth int分类深度(默认值0)Column_Order int排序(默认值0)Column_Intro nvarchar(1000)分类说明============================================= ===2.添加的存储过程CREATE PROCEDURE sp_Column_Insert(@Parent_ID int,@Column_Name nvarchar(50),@Column_Intro nvarchar(1000))ASDeclare @Err As intSet @Err=0Begin Tran--通过现有记录获取栏目IDDeclare @Column_ID As intDeclare @Column_Depth As intSelect @Column_ID = Max(Column_ID) From Tb_ColumnIF @Column_ID Is Not NullSet @Column_ID = @Column_ID+1ElseSet @Column_ID = 1--判断是否是顶级栏目,设置其Column_Path和Column_OrderDeclare @Column_Path As nvarchar(1000)Declare @Column_Order As intIF @Parent_ID = 0BeginSet @Column_Path =Ltrim(Str(@Column_ID))Select @Column_Order = Max(Column_Order) From Tb_ColumnIF @Column_Order Is Not NullSet @Column_Order = @Column_Order + 1Else --如果没有查询到记录,说明这是第一条记录Set @Column_Order = 1--深度Set @Column_Depth = 1EndElseBegin--获取父节点的路径和深度Select @Column_Path = Column_Path ,@Column_Depth = Column_Depth From Tb_Colu mn WhereColumn_ID=@Parent_IDIF @Column_Path Is NullBeginSet @Err = 1Goto theEnd--获取同父节点下的最大序号Select @Column_Order = Max(Column_Order) From Tb_PicColumn Where Column_Path like''+@Column_Path+'|%'Or Column_ID = @Parent_IDIF @Column_Order Is Not Null --如果序号存在,那么将该序号后的所有序号都加1Begin--更新当前要插入节点后所有节点的序号Update Tb_Column Set Column_Order = Column_Order +1 Where Column_Order>@Column_Order--同父节点下的最大序号加上1,构成自己的序号Set @Column_Order = @Column_Order + 1EndElseBeginSet @Err=1Goto theEndEnd--父节点的路径加上自己的ID号,构成自己的路径Set @Column_Path = @Column_Path + '|' + Ltrim(Str(@Column_ID))Set @Column_Depth = @Column_Depth+1EndInsert Into Tb_Column(Column_Name,Parent_ID,Column_Path,Column_Depth,Column_Or der,Column_Intro)Values(@Column_Name,@Parent_ID,@Column_Path,@Column_Depth,@Column_Order,@ Column_Intro)IF @@Error<>0BeginSet @Err=1Goto theEndEnd--更新当前记录之后的记录的ORDER--Update Tb_Column Set Column_Order = Column_Order+1 Where Column_Order> @Column_OrdertheEnd:IF @Err=0BeginCommit TranReturn @Column_IDEndElseBeginRollback TranReturn 0EndGO待续.......删除的存储过程CREATE PROCEDURE sp_Column_Delete(@Column_ID int)ASDeclare @Err As intSet @Err = 0Begin Tran--首先查询该节点下是否有子节点Select Column_ID From Tb_Column Where Parent_ID = @Column_ID IF @@RowCount<>0BeginSet @Err = 1Goto theEndEnd--获取该节点的Column_Order,为了删除后整理其他记录的顺序Declare @Column_Order As intSelect @Column_Order = Column_Order From Tb_Column Where Column_ID = @Colu mn_IDIF @Column_Order Is NUllBeginSet @Err =2Goto theEndEnd--更新其他记录的Column_OrderUpdate Tb_Column Set Column_Order = Column_Order -1 Where Column_Order >@Col umn_OrderIF @@Error<>0BeginSet @Err =3Goto theEndEnd--删除操作Delete From Tb_Column Where Column_ID=@Column_IDIF @@Error<>0BeginSet @Err =4Goto theEndEnd--更新其他记录的Column_ID--Update Tb_Column Set Column_ID= Column_ID - 1 Where Column_ID >@Column_ID--IF @@Error<>0--Begin--Set @Err =5--Goto theEnd--EndtheEnd:IF @Err = 0BeginCommit TranReturn 0 --删除成功EndElseBeginIF @Err=1BeginRollback TranReturn 1 --有子节点EndElseBeginRollback TranReturn 2--未知错误EndEndGO4.编辑的存储过程(没有用到,我自己写了一个简单的只是编辑名称,没有涉及到移动)CREATE PROCEDURE sp_Column_Update(@Column_ID int,@Parent_ID int,@Column_Name nvarchar(50),@Column_Intro nvarchar(1000))ASDeclare @Err As intSet @Err=0Begin Tran--获取修改前的:Parent_ID,Column_Depth,Column_OrderDeclare @oParent_ID As intDeclare @oColumn_Depth As intDeclare @oColumn_Path As nvarchar(1000)Select @oParent_ID = Parent_ID, @oColumn_Depth = Column_Depth,@oColumn_Order = Column_Order, @oColumn_Path = Column_Path From Tb_Column Where Column_ ID = @Column_IDIF @oParent_ID Is NullBeginSet @Err = 1Goto theEndEnd--如果父ID没有改变,则直接修改栏目名和栏目简介IF @oParent_ID = @Parent_IDBeginUpdate Tb_Column Set Column_Name = @Column_Name,Column_Intro = @Colu mn_Intro Where Column_ID = @Column_IDIF @@Error <> 0Set @Err = 2Goto theEndEndDeclare @nColumn_Path As nvarchar(1000)Declare @nColumn_Depth As int--获取当前节点作为父节点所包含的节点数[包括自身] 注:如果返回“1” 说明是单节点Declare @theCount As intSelect @theCount = Count(Column_ID) From Tb_Column Where Column_ID=@Column_ ID Or Column_Path like ''+@oColumn_Path+'|%'IF @theCount Is NullBeginSet @Err = 3Goto theEndEndIF @Parent_ID=0 --如果是设置为顶级节点,将节点设置为最后一个顶级节点Begin--Print '设置为顶级栏目'Set @nColumn_Path = Ltrim(Str(@Column_ID))Set @nColumn_Depth =1Select @nColumn_Order = Max(Column_Order) From Tb_ColumnIF @nColumn_Order Is NULLBeginSet @Err = 4Goto theEndEndSet @nColumn_Order = @nColumn_Order - @theCount + 1--更新三部分1 节点本身2 所有子节点2 本树更改之前的后面记录的顺序--Print '更新本栏目之前位置后面的所有栏目[不包括本栏目下的子栏目]的:Column_Order'Update Tb_Column Set Column_Order = Column_Order-@theCount Where (Column_Ord er >@oColumn_Order) And (Column_Path Not like ''+@oColumn_Path+'|%')IF @@Error <> 0BeginSet @Err = 7Goto theEndEnd--Print '更新本栏目的:Parent_ID,Column_Path,Column_Depth,Column_Order,Column_Name, Column_Intro'Print 'Order : '+Ltrim(Str(@nColumn_Order))Update Tb_Column Set Parent_ID=@Parent_ID,Column_Path = @nColumn_Path,Column _Depth = @nColumn_Depth,Column_Order = @nColumn_Order, Column_Name = @Col umn_Name,Column_Intro = @Column_Intro Where Column_ID = @Column_IDIF @@Error <> 0BeginSet @Err = 5Goto theEndEnd--Print '更新本栏目下的所有子栏目的:Column_Path,Column_Depth,Column_Order'Update Tb_Column Set Column_Path = Replace(Column_Path,@ oColumn_Path,@nColumn_Path),Column_Depth = Column_Depth + (@nColumn_Depth-@oColumn_Depth),Column_Order = Column_Order+( @nColumn_Order-@oColumn_Orde r) Where Column_Path like ''+@oColumn_Path+'|%'IF @@Error <> 0BeginSet @Err = 6Goto theEndEndEndElseBegin--获取未来父节点的相关信息,并设置本节点的相关值Select @nColumn_Depth = Column_Depth,@nColumn_Path = Column_Path From Tb_Co lumn Where Column_ID = @Parent_IDIF @nColumn_Depth Is NULL Or @nColumn_Path Is NullBeginSet @Err = 8Goto theEndEndSet @nColumn_Depth = @nColumn_Depth +1Select @nColumn_Order =Max(Column_Order) From Tb_Column Where Column_ID = @ Parent_ID Or Column_Path like ''+@nColumn_Path+'|%'IF @nColumn_Order Is NULLBeginSet @Err = 9Goto theEndEndSet @nColumn_Path = @nColumn_Path +'|'+ Ltrim(Str(@Column_ID))IF @nColumn_Order = @oColumn_Order+1 --如果新的父节点是原来位置上端最近一个兄弟,则所有节点的顺序都不改变BeginUpdate Tb_Column Set Parent_ID=@Parent_ID,Column_Path = @nColumn_Path,Column _Depth = @nColumn_Depth, Column_Name = @Column_Name,Column_Intro = @Colu mn_Intro Where Column_ID = @Column_IDIF @@Error <> 0BeginSet @Err = 10Goto theEndEndEndSet@nColumn_Order = @nColumn_Order + 1--更新三部分1 本树更改之前的后面(或前面)记录的顺序1 节点本身 3 所有子节点--分为向上移或象下移--Print '更新本栏目之前位置后面的所有栏目[或者本栏目之后位置][不包括本栏目下的子栏目]的:C olumn_Order'IF @nColumn_Order < @oColumn_OrderBeginUpdate Tb_Column Set Column_Order = Column_Order+@theCount Where Column_Ord er<@oColumn_Order And Column_Order >=@nColumn_Order And (Column_Path Not like ''+@oColumn_Path+'|%') And Column_ID<>@Column_IDIF @@Error <> 0BeginSet @Err = 12Goto theEndEndEndElseBeginUpdate Tb_Column Set Column_Order = Column_Order-@theCount Where Column_Orde r >@oColumn_Order And Column_Order<@nColumn_Order And (Column_Path Not lik e ''+@oColumn_Path+'|%') And Column_ID<>@Column_IDIF @@Error <> 0BeginSet @Err = 13Goto theEndEnd--Print '更新本栏目的:Parent_ID,Column_Path,Column_Depth,Column_Order,Column_Name, Column_Intro'Print 'Order : '+Ltrim(Str(@nColumn_Order))IF @nColumn_Order > @oColumn_OrderSet @nColumn_Order = @nColumn_Order - @theCountUpdate Tb_Column Set Parent_ID=@Parent_ID,Column_Path = @nColumn_Path,Column _Depth = @nColumn_Depth,Column_Order = @nColumn_Order, Column_Name = @Col umn_Name,Column_Intro = @Column_Intro Where Column_ID = @Column_IDIF @@Error <> 0BeginSet @Err = 10Goto theEndEnd--Print '更新本栏目下的所有子栏目的:Column_Paht,Column_Depth,Column_Order'Update Tb_Column Set Column_Path = Replace(Column_Path,@ oColumn_Path,@nColumn_Path),Column_Depth = Column_Depth + (@nColumn_Depth-@oColumn_Depth),Column_Order = Column_Order+(@nColumn_Order-@oColumn_Order) Where Column_Path like ''+@oColumn_Path+'|%'IF @@Error <> 0BeginSet @Err = 11EndEndtheEnd:IF @Err<>0 --如果有错误则返回错误号BeginRollback TranReturn @ErrEndElse--如果没有错误就返回0BeginCommit TranReturn 0EndGO待续..................最后一步显示分类(只是一条select语句)CREATE PROCEDURE sp_Column_ListASSELECT Column_ID, Column_Name, Parent_ID, Column_Path, Column_Depth, Column_Order, Column_IntroFROM Tb_ColumnORDER BY Column_OrderGO接下来就是在界面呈现了,显示的时候用的是DataGridHtml代码如下:<ASP:DataGrid id="DataGrid1" runat="server" AutoGenerateColumns="False" Width="52 0px" DataKeyField="Column_Id"><Columns><asp:TemplateColu mn HeaderText="分类信息"><ItemTempl ate><asp: Label id="lbname" runat="server"></asp:Label></ItemTemp late><EditItemTe mplate><asp: TextBox id="tbdgname" runat="server" Text='<%#DataBinder.Eval(Container.DataItem, "Column_Name")%>'></as p:TextBox></EditItemT emplate></asp:TemplateColu mn><asp:TemplateColu mn HeaderText="添加子节点"><ItemTempl ate><a h ref="#" onclick="Open('AddSonCate.aspx?fid=<%#DataBinder.Eval(Container.DataItem," Column_Id")%>','son',280,80)">添加子分类</a></ItemTemp late></asp:TemplateColu mn><asp:TemplateColu mn HeaderText="编辑"><ItemTempl ate><asp: LinkButton runat="server" Text="编辑" CommandName="Edit" CausesValidation="false"> </asp:LinkButton></ItemTemp late><EditItemTe mplate><asp:LinkButton runat="server" Text="更新" CommandName="Update" CausesValidation="Fals e"></asp:LinkButton> <asp: LinkButton runat="server" Text="取消" CommandName="Cancel" CausesValidation="fals e"></asp:LinkButton></EditItemT emplate></asp:TemplateColu mn><asp:TemplateColu mn HeaderText="删除"><ItemTempl ate><asp: LinkButton runat="server" ID="lbdelete" Text="删除" CommandName="Delete" CausesV alidation="false" CommandArgument='<%#DataBinder.Eval(Container.DataItem,"Column _Id")%>'></as p:LinkButton></ItemTemp late></asp:TemplateColu mn></Columns></asp:DataGrid>最关键的地方在DataGrid_ItemDataBind事件中private void DataGrid1_ItemDataBound(object sender, System.Web.UI.WebControls.Data GridItemEventArgs e){if(e.Item.ItemType == ListItemType.Item || e.Item.ItemType == ListItemType.AlternatingItem){//得到Column_Name字段的值string columnName = (string)DataBinder.Eval(e.Item.DataI tem,"Column_Name");string columnTemp = "";//得到深度值Column_Depthint columnDepth = Int32.Parse(DataBinder.Eval(e.Item.Dat aItem,"Column_Depth").ToString());if(columnDepth>1){for(int i = 1;i<columnDepth;i++){columnTemp +=" &nbs p;";}columnTemp+="┝";}Label lbname = (Label)e.Item.FindControl("lbname");lbname.Text = columnTemp+columnName ;LinkButton lbdelete = (LinkButton)e.Item.FindControl("lbde lete");lbdelete.Attributes.Add("OnClick","JavaScript:return confirm ('确实要删掉此分类吗?');");}}Over!。