一张表保存一个无限级树形目录

一张表保存一个无限级树形目录:

/****** Object:  Table [dbo].[TreeMenue]    Script Date: 08/20/2014 18:03:00 ******/
SET ANSI_NULLS ON
GO

SET QUOTED_IDENTIFIER ON
GO

SET ANSI_PADDING ON
GO

CREATE TABLE [dbo].[TreeMenue](
    [ID] [int] IDENTITY(1,1) NOT NULL,
    [MenueName] [varchar](50) NULL,
    [ParentID] [int] NULL,
    [URL] [varchar](500) NULL,
 CONSTRAINT [PK_TreeMenue] PRIMARY KEY CLUSTERED
(
    [ID] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
) ON [PRIMARY]

GO

SET ANSI_PADDING OFF
GO

ALTER TABLE [dbo].[TreeMenue]  WITH NOCHECK ADD  CONSTRAINT [FK_TreeMenue_TreeMenue] FOREIGN KEY([ParentID])
REFERENCES [dbo].[TreeMenue] ([ID])
GO

ALTER TABLE [dbo].[TreeMenue] NOCHECK CONSTRAINT [FK_TreeMenue_TreeMenue]
GO

读取树形目录的SQL

with c as
(
    select * from TreeMenue where [Id] = 1
    union all
    select a.* from TreeMenue as a
    join c on a.ParentId = c.Id
)
select * from c

在SQL Server 2008中提供了一种新的数据类型hierarchyid和相关的操作方法来存储和查询这种树型层次关系数据

以下来自转载:

本文原创自CSDN TJVictor专栏:http://blog.csdn.net/tjvictor/archive/2009/07/30/4395681.aspx

使用SQL Server 2008中的hierarchyid类型来设计具有树型层次关系的表

本文主要讲述三个内容:
1.如何创建hierarychyid的表,插入数据及基本递归查询。
2.介绍hierarchyid的10种专有函数。
3.介绍hierarchyid特有的深度优先索引(Depth-First Indexing)和广度优先索引(Breadth-First Indexing)

在上一节中

http://blog.csdn.net/tjvictor/archive/2009/07/30/4395677.aspx
我们已经演示了如何在SQL Server中通过主键和外键来存储如下图所示的树型结构数据

虽然通过主键和外键的相互搭配可以满足我们的查询、存储需求,但是这种方式并不易于管理和维护,幸运的是,在SQL Server 2008中提供了一种新的数据类型hierarchyid和相关的操作方法来存储和查询这种树型层次关系数据。

首先创建数据表:
create database TestDb
go
use TestDb
go
Create table EmployeeTreeTable
(
NodeId        hierarchyid PRIMARY KEY,
NodeLevel     AS NodeId.GetLevel(),
EmployeeId    int UNIQUE NOT NULL,
EmployeeName  nvarchar(32) NOT NULL,
)
NodeId是记录树型层次的Id,是hierarchyid类型。NodeLevel是个计算列,用于存储当前树是深度值,根节点为0。关于NodeId.GetLevel()方法将在下面章节中详细介绍。

按照上图所示的层次关系为表插入数据:
--插入数据
declare @DepthNode hierarchyid;--深度Id
declare @BreadthNode hierarchyid;--广度Id
--插入根节点
insert into EmployeeTreeTable values(hierarchyid::GetRoot(),1,‘项目经理‘)
--计算深度并插入子节点2
select @DepthNode = NodeId from EmployeeTreeTable where [EmployeeId] = 1;
insert into EmployeeTreeTable values(@DepthNode.GetDescendant(null,null),2,‘技术经理‘);
--计算节点2广度,在节点2右边插入节点3
select @BreadthNode = NodeId from EmployeeTreeTable where [EmployeeId] = 2;
insert into EmployeeTreeTable values(@DepthNode.GetDescendant(@BreadthNode,null),3,‘产品经理‘);
--计算节点3广度,在节点3右边插入节点4
select @BreadthNode = NodeId from EmployeeTreeTable where [EmployeeId] = 3;
insert into EmployeeTreeTable values(@DepthNode.GetDescendant(@BreadthNode,null),4,‘测试经理‘);
--计算节点2深度并插入子节点5
select @DepthNode = NodeId from EmployeeTreeTable where [EmployeeId] = 2;
insert into EmployeeTreeTable values(@DepthNode.GetDescendant(null,null),5,‘技术组长1‘);
--计算节点5广度,在节点5右边插入节点6
select @BreadthNode = NodeId from EmployeeTreeTable where [EmployeeId] = 5;
insert into EmployeeTreeTable values(@DepthNode.GetDescendant(@BreadthNode,null),6,‘技术组长2‘);
--计算节点4深度并插入子节点7
select @DepthNode = NodeId from EmployeeTreeTable where [EmployeeId] = 4;
insert into EmployeeTreeTable values(@DepthNode.GetDescendant(null,null),7,‘测试员工1‘);
--计算节点5深度并插入子节点8
select @DepthNode = NodeId from EmployeeTreeTable where [EmployeeId] = 5;
insert into EmployeeTreeTable values(@DepthNode.GetDescendant(null,null),8,‘技术员工1‘);
--计算节点8广度,在节点8右边插入节点9
select @BreadthNode = NodeId from EmployeeTreeTable where [EmployeeId] = 8;
insert into EmployeeTreeTable values(@DepthNode.GetDescendant(@BreadthNode,null),9,‘技术员工2‘);
--计算节点9广度,在节点9右边插入节点10
select @BreadthNode = NodeId from EmployeeTreeTable where [EmployeeId] = 9;
insert into EmployeeTreeTable values(@DepthNode.GetDescendant(@BreadthNode,null),10,‘技术员工3‘);
go
select * from EmployeeTreeTable
结果集为:
NodeId    NodeLevel    EmployeeId   EmployeeName
0x           0                   1                    项目经理
0x58       1                   2                    技术经理
0x5AC0   2                   5                    技术组长1
0x5AD6   3                   8                    技术员工1
0x5ADA   3                   9                    技术员工2
0x5ADE   3                   10                  技术员工3
0x5B40   2                   6                    技术组长2
0x68       1                   3                    产品经理
0x78       1                   4                    测试经理
0x7AC0   2                   7                    测试员工1

1.查询技术组长1所有子节点的员工信息
select * from EmployeeTreeTable
    where NodeId.IsDescendantOf(0x5AC0)=1--0x5AC0是技术组长1的NodeId

2.查询技术组长1所有父节点的员工信息
with c as
(
    select * from EmployeeTreeTable where EmployeeId = 5
    union all
    select a.* from EmployeeTreeTable as a
    join c on a.NodeId = c.NodeId.GetAncestor(1)
)
select * from c

上面的例子中,使用了很多hierarchyid专有的函数,可能大家还不熟悉,下面我将具体介绍一下hierarchyid的10个函数,分别为:
GetRoot,GetLevel,GetAncestor,GetDescendant,IsDescendantOf,ToString,Parse,GetReparentedValue,Read,Write。
1.GetRoot。返回层次结构树的根节点。注意GetRoot() 是静态方法。
关于SQL中静态方法和实例方法的区别请参见:http://blog.csdn.net/tjvictor/archive/2009/07/29/4390673.aspx
SQL:select * from EmployeeTreeTable where NodeId = hierarchyid::GetRoot()
结果集:
NodeId    NodeLevel    EmployeeId    EmployeeName
0x            0                  1                     项目经理

2.返回一个表示节点在树中的深度的整数。
前面建表时我们已经使用了这个函数,NodeLevel字段就是用这个函数自动创建的。
SQL:select EmployeeName,NodeId.GetLevel() as TreeLevel from EmployeeTreeTable
结果集为:
EmployeeName    TreeLevel
项目经理                0
技术经理                1
技术组长1              2
技术员工1              3
技术员工2              3
技术员工3              3
技术组长2              2
产品经理                1
测试经理                1
测试员工1              2

3.GetAncestor返回表示本节点为的第 n 个父节点的 hierarchyid。
SQL:
declare @NodeId hierarchyid
select @NodeId=NodeId from EmployeeTreeTable where EmployeeId = 5
select EmployeeName,NodeLevel from EmployeeTreeTable where NodeId = @NodeId.GetAncestor(0)
select EmployeeName,NodeLevel from EmployeeTreeTable where NodeId = @NodeId.GetAncestor(1)
select EmployeeName,NodeLevel from EmployeeTreeTable where NodeId = @NodeId.GetAncestor(2)
结果集为:
EmployeeName    NodeLevel
技术组长1              2
技术经理                1
项目经理                0
@NodeId.GetAncestor(0) 取自己节点的Id,@NodeId.GetAncestor(1)取父节点的Id,@NodeId.GetAncestor(2)取爷节点的Id,以此类推。

4.GetDescendant返回父级的一个子节点

    • 如果父级为 NULL,则返回 NULL。
    • 如果父级不为 NULL,而 child1 和 child2 为 NULL,则返回父级的子级。
    • 如果父级和 child1 不为 NULL,而 child2 为 NULL,则返回一个大于 child1 的父级的子级。
    • 如果父级和 child2 不为 NULL,而 child1 为 NULL,则返回一个小于 child2 的父级的子级。
    • 如果父级、child1 和 child2 都不为 NULL,则返回一个大于 child1 且小于 child2 的父级的子级。
    • 如果 child1 不为 NULL 且不是父级的子级,则引发异常。
    • 如果 child2 不为 NULL 且不是父级的子级,则引发异常。
    • 如果 child1 >= child2,则引发异常。
      我们在插入的SQL语句中已经使用过了这个方法,这里就不再给出SQL示例,请大家参考前面的插入SQL语句。

      5.IsDescendantOf如果子节点为本节点的后代,则返回 true
      SQL:select * from EmployeeTreeTable where NodeId.IsDescendantOf(0x58)=1
      结果集为:
      NodeId    NodeLevel    EmployeeId    EmployeeName
      0x58        1                  2                  技术经理
      0x5AC0    2                  5                  技术组长1
      0x5AD6    3                  8                  技术员工1
      0x5ADA    3                  9                  技术员工2
      0x5ADE    3                  10                技术员工3
      0x5B40    2                  6                  技术组长2

      6.ToString返回具有本节点逻辑表示形式的字符串
      SQL:select *,NodeId.ToString() as Path from EmployeeTreeTable
      结果集为:
      NodeId    NodeLevel    EmployeeId    EmployeeName    Path
      0x            0                  1                     项目经理                /
      0x58        1                  2                    技术经理               /1/
      0x5AC0    2                  5                     技术组长1             /1/1/
      0x5AD6    3                  8                     技术员工1             /1/1/1/
      0x5ADA    3                 9                     技术员工2             /1/1/2/
      0x5ADE    3                  10                   技术员工3             /1/1/3/
      0x5B40    2                 6                     技术组长2             /1/2/
      0x68        1                 3                     产品经理               /2/
      0x78        1                 4                     测试经理               /3/
      0x7AC0    2                 7                    测试员工1             /3/1/

      7.Parse将hierarchyid 的规范字符串表示形式转换为hierarchyid值。即与ToString()函数是相反函数。Parse是静态函数。
      SQL:
      declare @Path varchar(32) = ‘/1/2/5/6/‘
      select hierarchyid::Parse(@Path)
      结果集为:0x5B6394

      8.GetReparentedValue把当前节点从旧路径更新到新路径
      下面的SQL是把技术员工3,从技术组长1节点更新到技术组长2下面。
      SQL:
      declare @OldNode hierarchyid=0x5AC0;
      declare @NewNode hierarchyid=0x5B40;
      update EmployeeTreeTable set NodeId = NodeId.GetReparentedValue(@OldNode,@NewNode)
          where EmployeeId = 10
      结果集中技术员工3的路径从/1/1/3/变成了/1/2/3/。
      关于GetReparentedValue的用法比较复杂,我在介绍索引后,会更加详细的说明各种替换情况。

      9.Read和Write
      Read和Write是供CLS调用的,不能在T-SQL中直接使用。所以这里就不具体介绍两个函数的使用方法了。

      hierarchyid有深度优先索引和广度优先索引
      当递归查询父子节点时,会利用到深度优先索引;当平行查询兄弟节点时,会利用到广度优先索引。
      深度优先索引图:

      广度优先索引图:

      1.建立深度优先索引:
      深度优先索引是hierarchyid默认的索引,只要在hierarchyid列上建立主键,那么就会自动建立hierarchyid索引。

      2.建立广度优先索引
      广度优先索引必须是个唯一索引且包括NodeLevel和NodeId两列:
      CREATE UNIQUE INDEX IX_EmployeeBreadth ON Employee(NodeLevel, NodeId)

      需要注意的是采用深度优先、广度优先还是结合使用这两种索引,以及将哪一种设为聚集键(如果有),取决于上述两种查询类型的相对重要性以及 SELECT 与 DML 操作的相对重要性,本文不代表一定要如此建立hierarchyid索引。

      最后我们讨论一下hierarchyid的GetReparentedValue几种使用方法。
      下面我们先看一个有问题的节点更新:把技术组长1从技术经理更新到产品经理。
      SQL:
      declare @OldNode hierarchyid=0x58;
      declare @NewNode hierarchyid=0x68;
      update EmployeeTreeTable set NodeId = NodeId.GetReparentedValue(@OldNode,@NewNode)
          where EmployeeId = 5
      go
      select NodeId.ToString(),* from EmployeeTreeTable
      结果集为:
      路径               NodeId      NodeLevel    EmployeeId    EmployeeName
      /                    0x             0                    1                    项目经理
      /1/                 0x58         1                    2                    技术经理
      /1/1/1/           0x5AD6    3                    8                    技术员工1
      /1/1/2/           0x5ADA    3                    9                    技术员工2
      /1/1/3/           0x5ADE    3                    10                  技术员工3
      /1/2/              0x5B40     2                    6                    技术组长2
      /2/                 0x68         1                    3                    产品经理
      /2/1/              0x6AC0     2                    5                    技术组长1
      /3/                 0x78         1                    4                    测试经理
      /3/1/              0x7AC0     2                    7                    测试员工1
      从结果里面可以看到技术组长已经变成了/2/1,成功更新到产品经理节点下。但是技术组长1下面的子节点技术员工1,2,3却没有相应的更新过来,还是原来的/1/1/1,2,3,但是原先的技术组长1的/1/1节点已经没有了,所以出现了所谓的“断层”现象。
      下面提出几种常用更新需求,并且给出相应的SQL实现语句。

      1.职位变更。例如技术经理与产品经理职位互换。
      针对这种情况,有两种方法。一是把技术经理下面的所有节点Id都更新成产品经理节点下。这种情况变动比较大,不推荐使用。第二种方法是把技术经理的NodeId和产品经理的NodeId互换。下面使用第二种方法:
      declare @TechNode hierarchyid=0x58;
      declare @ProductNode hierarchyid=0x68;
      declare @TempNode hierarchyid=0x59;
      update EmployeeTreeTable set NodeId = @TempNode where NodeId = @TechNode;
      update EmployeeTreeTable set NodeId = @TechNode where NodeId = @ProductNode;
      update EmployeeTreeTable set NodeId = @ProductNode where NodeId = @TempNode;

      2.职位升降级。例如技术组长2降级成为技术员工,被挂在技术组长1节点下:
      declare @TechTeamLeadNode1 hierarchyid=0x5AC0;
      declare @TechEmployeeNode3 hierarchyid=0x5ADE;
      update EmployeeTreeTable set NodeId = @TechTeamLeadNode1.GetDescendant(@TechEmployeeNode3,null)
          where EmployeeId = 6
      部分结果集为:
      Path        NodeId    NodeLevel    EmployeeId    EmployeeName
      /1/1/4/    0x5AE1    3                  6                     技术组长2
      可见,技术组长2从/1/2变成了/1/1/4

      总结:
      SQL Server 2008提供的hierarchyid类型使我们能够灵活、方便的操作树型结构。关于hierarchyid还有很多深入的知识,很多灵活的用法,本文不可能一一涉及,这里仅是介绍一些基本用法,抛砖引玉,如果大家在以后的使用中发现什么问题或是更好的解决方案,请联系我。

一张表保存一个无限级树形目录

时间: 2024-08-04 20:26:07

一张表保存一个无限级树形目录的相关文章

sql server2008怎么给一张表加一个用户

有时候我们要对数据库进行权限管理,防止有人误操作或者窃取数据,那么小编这篇文章就是实现这个的操作过程. 百度经验:jingyan.baidu.com 工具/原料 sql server2008数据库 电脑 百度经验:jingyan.baidu.com 方法/步骤 1 1.打开数据库,进入--安全性--登录名--新建登录名 步骤阅读 2 2.在常规这个选项卡中,创建登陆名,并设置默认的数据库 步骤阅读 3 3.在用户映射这个选项中,单击勾选你需要设置的数据库,然后设置架构,单击确认,这样就完成了创建

Java链接HBASE数据库,创建一个表,删除一张表,修改表,输出插入,修改,数据删除,数据获取,显示表信息,过滤查询,分页查询,地理hash

准备工作 1.创建Java的Maven项目 创建好的目录结构如下:  另外注意junit的版本,最好不要太高,最开始笔者使用的junit4.12的,发现运行的时候会报错.最后把Junit的版本改成4.7的之后,问题解决了. 创建测试工具类HbaseDemo,为了保证能够让Hbase运行,需要最开始写出如下配置: package toto.com.hbase; import java.util.ArrayList; import java.util.Iterator; import org.apa

一张图,一个表说明PVLAN

一张图,一个表说明PVLAN,布布扣,bubuko.com

OrmLite动态创建表,一个实体类创建多张表的的偏招

在做一个Android的项目,因为使用数据库频繁,实体字段也比较多,于是打算采用ORM框架,发现OrmLite还不错,于是下了下来,打算使用. 没想到还没正式开工,就遇到问题了.我现在的一个需求如下, 我有一个实体类如下,代表聊天消息,现在要做的是针对每一个当前用户(userId)对应一个朋友(friendId)都要创建一个表.需求比较蛋疼,我本来想的是直接在加两个字段就搞定的,但是我们老大说要分表.没办法只能分表. public class ChatMessage{ public ChatMe

一个获取指定目录下一定格式的文件名称和文件修改时间并保存为文件的python脚本

摘自:http://blog.csdn.net/forandever/article/details/5711319 一个获取指定目录下一定格式的文件名称和文件修改时间并保存为文件的python脚本 @for&ever 2010-07-03 功能: 获取指定目录下面符合一定规则的文件名称和文件修改时间,并保存到指定的文件中 脚本如下: #!/usr/bin/env python# -*- coding: utf-8 -*- '''Created on 2010-7-2 @author: fore

两张表A和B,各有一个字段,更新时间A.MODIFIED_TM和B.MODIFIED_TM,A表为主表,更新时间不为空,但是B表更新时间可能为空,现在要取A、B两表时间最新的那个,B.MODIFIED

问题描述: 两张表A和B,各有一个字段,更新时间A.MODIFIED_TM和B.MODIFIED_TM,A表为主表,更新时间不为空,但是B表更新时间可能为空,现在要取A.B两表时间最新的那个,B.MODIFIED_TM若为空就取A.MODIFIED_TM,例如: 表A ID    MODIFIED_TM 1     2013/3/10 18:07:12 2     2013/4/10 18:07:12 3     2013/5/10 18:07:12 表B ID    MODIFIED_TM 1

转换一个字段中含有多个另外一张表的id | | 行转列

1.Oracle Function --入参:表 id /* cxcyxm02 :操作表,含有一个存储另一张表(xs0101)多个主键id的字段 ids :操作表的主键 id */ CREATE OR REPLACE FUNCTION cxcyxmcyry(ids VARCHAR2) RETURN VARCHAR2 IS tempcyryxx VARCHAR2(1000); cyryxx VARCHAR2(1000); cxcylength number; i number;BEGIN i :=

MySQL实验1: 新建一个名为 library 的数据库,包含 book、reader 两张表,根据自己的理解安排表的内容并插入数据。

数据表(table)简称表,它是数据库最重要的组成部分之一.数据库只是一个框架,表才是实质内容. 实验: 新建一个名为 library的数据库,包含 book.reader两张表,根据自己的理解安排表的内容并插入数据. 参考答案: 总结:经过本次实验,我们已经有了一个名为 library的数据库,其中有 book和reader两张表,我们已经向其中插入了一些数据. 原文地址:https://www.cnblogs.com/HongjianChen/p/10692764.html

MySQL 两张表关联更新(用一个表的数据更新另一个表的数据)

有两张表,info1, info2 . info1: info2: 现在,要用info2中的数据更新info1中对应的学生信息,sql语句如下: UPDATE info1 t1 JOIN info2 t2 ON t1.name = t2.name SET t1.age = t2.age, t1.class = t2.class; 运行结果如下: 更新过的info1: 至于效率问题,之前我有三张表,都在40万左右.需要将 table2 中的两个字段(step1),table3 中的一个字段(ste