今天在论坛上看到一个举例,关于sql server 的示例。1/25/50/100美分,多少种可能拼凑成2美元。

看了其中第一条语法,放在SQL SERVER中测试,发现真的列举出所有组合成2美元的方式。

于是仔细研究语法,发现用了With关键字。

发现很长时间没有使用SQL SERVER数据库,真的有点落后了。于是见到那学习了下 With关键字。

1.引言

现实项目中经常遇到需要处理递归父子关系的问题,如果把层次关系分开,放在多个表里通过主外键关系联接,最明显的问题就是扩展起来不方便,对于这种情况,一般我们会创建一个使用自连接的表来存放数据。例如存放会员地区数据表结构可能是这样:

列名 描述
location_id 地区编号
location_name 地区名称
parentlocation_id 上级地区编号

或者某个部分的职员表结构可能如下所示:

列名 描述
employee_id 职员编号
employee_name 职员名称
manager_id 职员的直接上级管理者,和employee_id进行自联接

通过类似表结构,我们就可以通过一个表理论上管理无限级数的父/子关系,但是当我们需要将这些数据读取出来,不论是填充到一个树中,或是使用级联显 示出来,需要花费一定的精力。传统的做法,是做一个递归调用,首先连接数据库将顶层数据(也就是parent_xxx为null的记录)读取出来,再对每 一条数据进行递归访问填充集合,这种做法需要连接数据库多次,显然不是较好的解决方法,那么我们能不能通过一次数据库访问,将数据全部读取出来,并且为了 按照父子关系形成集合,使返回的数据满足某种格式。

2. 分析

理想情况下,如果父/子关系数据时严格按照关系结构添加到数据库中,亦即首先添加某条父记录,接着添加该父记录的子记录,如果子记录还包含子记录的 话继续添加,最终数据表中父/子关系按规则排列数据,我们就可以使用某种算法填充集合,但是正如我们所说,这是理想情况,实际情况下数据经常会发生改变, 导致数据没有规律可言,如下图所示,这样的话读取数据填充集合就不太容易的。

所以我们要做的就是通过查询使数据库返回的数据满足这种格式,那么我们的思路是首先查找顶层(0层)记录,再查询第1层记录,接下来是第2层、第3层直到第n层。因为层数是不确定的,所以仍然需要使用递归访问。

SQL Server 2005中提供了新的with关键字,用于指定临时命名的结果集,这些结果集称为公用表表达式(CTE)。该表达式源自简单查询,并且在SELECT、 INSERT、UPDATE或DELETE 语句的执行范围内定义。该子句也可用在 CREATE VIEW 语句中,作为该语句的 SELECT 定义语句的一部分。公用表表达式可以包括对自身的引用。这种表达式称为递归公用表表达式。

其语法为:

 [ WITH <common_table_expression> [ ,...n ] ]
<common_table_expression>::=
expression_name[ ( column_name [ ,...n ] ) ]
AS
( CTE_query_definition )

使用with关键子的一个简单示例,以下代码将tb_loc表中数据源样输出:

WITH locs(id,name,parent)
AS
(SELECT * FROM tb_loc
)
SELECT * FROM locs

为了创建良好层次记录结构集,使用with关键字首先读取顶层记录,并且针对每一条顶层记录读取其子记录,直到读取到最底层级记录,最后将所有的记录组合起来,这里用到了UNION ALL关键字,用于将多个查询结果组合到一个结果集中。

接下来就可以使用该关键字创建存储过程返回结果集,并附加每条记录所位于的“层”数,如下图所示:

最后需要在前台界面将其显示出来,由于记录已经按层次返回,需要做的就是按层次首其输出,首先将第0层数据输出,接下来将遍历第0层数据,将第一层 数据添加到合适的父对象中,重复此过程直到填充结果。那么这里的难题就在于如何查找父对象,我们当然可以遍历集合,但是这么做的话如果数据量很大将导致效 率低下。既然可以得到当前对象所位于的层的信息,就也是这树倒置的树是一层一层向下填充的,我们可以定义一个临时集合变量,存储当前层上一层的所有父对 象,在插入当前层对象时遍历集合变量以插入到合适的位置,同时我们还必须保证在逐层读取数据时临时集合变量中持有的始终时当前层上一层所有的对象,程序流 程图如下所示:

根据以上分析,我们就可以编写实现代码了(为了方便,将本文中用到的数据表和创建记录等SQL语句一并给出)。

3. 实现

3.1 打开SQL Server 2005 Management Studio,选择某个数据库输入以下语句创建表结构:

CREATE TABLE [tb_loc]([id] [int],[name] [varchar](16),[parent] [int]
)GO

3.2 创建测试数据:

INSERT tb_loc(id,name,parent) VALUES( 1,'河北省',NULL)
INSERT tb_loc(id,name,parent) VALUES( 2,'石家庄',1)
INSERT tb_loc(id,name,parent) VALUES( 3,'保定',1)
INSERT tb_loc(id,name,parent) VALUES( 4,'山西省',NULL)
INSERT tb_loc(id,name,parent) VALUES( 5,'太原',4)
INSERT tb_loc(id,name,parent) VALUES( 6,'新华区',2)
INSERT tb_loc(id,name,parent) VALUES( 7,'北焦村',6)
INSERT tb_loc(id,name,parent) VALUES( 8,'大郭村',6)
INSERT tb_loc(id,name,parent) VALUES( 9,'河南省',NULL)
INSERT tb_loc(id,name,parent) VALUES( 10,'大郭村南',8)
INSERT tb_loc(id,name,parent) VALUES( 11,'大郭村北',8)
INSERT tb_loc(id,name,parent) VALUES( 12,'北焦村东',7)
INSERT tb_loc(id,name,parent) VALUES( 13,'北焦村西',7)
INSERT tb_loc(id,name,parent) VALUES( 14,'桥东区',3)
INSERT tb_loc(id,name,parent) VALUES( 15,'桥西区',3) GO

3.3 创建pr_GetLocations存储过程:

CREATE PROCEDURE pr_GetLocations
AS
BEGINWITH locs(id,name,parent,loclevel)AS(SELECT id,name,parent,0 AS loclevel FROM tb_locWHERE parent IS NULLUNION ALLSELECT l.id,l.name,l.parent,loclevel+1 FROM tb_loc l INNER JOIN locs p ON l.parent=p.id)SELECT * FROM locs
END

3.4 在Visual Studio 2008里创建解决方案并新建一个网站。

3.5 在网站中添加APP_Code目录,并创建Location实体类,该类标识了所在地编号和名称,并且保存了父级所在地编号和它所包含的所有子所在地的集合:

public class Location
{public int Id{get;set;}public string Name{get;set;}public LocationCollection SubLocations{get;set;}public int ParentId{get;set;}public Location(){Id = 0;Name = string.Empty;SubLocations = new LocationCollection();ParentId=0;}
}

3.5 以上代码使用了LocationCollection集合类,使用泛型集合创建该类(同样位于APP_Code目录下):

using System.Collections.Generic;public class LocationCollection:List<Location>
{}

3.6 在APP_Code目录下创建DAO类用于访问数据库,添加必要的命名空间引用:

using System;
using System.Data;
using System.Data.SqlClient;public class DAO
{
}

3.7编写GetLocations方法,返回所在地集合对象(请根据实际情况修改数据库连接字符串):

public LocationCollection GetLocations()
{LocationCollection locs = new LocationCollection();using (SqlConnection conn = newSqlConnection("server=.;uid=sa;pwd=00000000;database=temp;")){conn.Open();SqlCommand cmd = new SqlCommand();cmd.CommandText = "pr_GetLocations";cmd.CommandType = CommandType.StoredProcedure;cmd.Connection = conn;SqlDataReader reader = cmd.ExecuteReader();int level = 0;int oldlevel = 1;LocationCollection container=new LocationCollection();LocationCollection current = new LocationCollection();while (reader.Read()){Location loc = GetLocationFromReader(reader, out level);if (level == 0){locs.Add(loc);container.Add(loc);                    }else{if (oldlevel != level){container.Clear();foreach (Location l in current)container.Add(l);current.Clear();oldlevel = level;}current.Add(loc);CreateLocation(container, loc);}                }}return locs;
}

在该方法按照以下步骤执行:

1. 使用命令对象对象执行pr_GetLocations存储过程返回结果集

2. 如果数据阅读器读取了数据(reader.Read方法返回true)执行:

2.1.从数据阅读器当前记录中读取Location对象,并返回层数信息(out level)

2.2.如果是第一层(level等于0)填充locs集合,并加入到container对象

2.3.如果不是第一层根据层标志(oldlevel)判断当前层是否是新的一层

2.4 如果当前层是新的一层清空container集合并将current集合中实体复制到container集合中,清空current集合并置层标志(oldlevel)

2.5 将当前对象添加到current集合中

2.6 调用CreateLocation方法从container上层集合中匹配当前实体父级对象并加入父对象的子集合中

3. 重复第2步直到读取完全部数据

可以看到container集合始终保存了当前层的上层所有的实体对象,并且为了在更换层数后能够正确的更新container集合,使用current集合保存当前层的实体对象。

3.8 编写GetLocationFromReader方法,用于从数据阅读器中返回Location实体对象,并将层数信息使用out参数返回:

private Location GetLocationFromReader(SqlDataReader reader, out int level)
{Location loc = new Location();loc.Id = Convert.ToInt32(reader["id"]);loc.Name = Convert.ToString(reader["name"]);object o = reader["parent"];if (o != DBNull.Value)loc.ParentId = Convert.ToInt32(o);level = Convert.ToInt32(reader["loclevel"]);return loc;
}

3.9 编写CreateLocation方法,该方法遍历实体集合找到与当前实体对象的父级编号匹配的实体,并将当前实体加入到父级实体的子集合中:

private void CreateLocation(LocationCollection container, Location loc)
{foreach (Location location in container){if (location.Id == loc.ParentId){location.SubLocations.Add(loc);break;}}
}

3.10 向Default.aspx页面上添加TreeView控件:

<asp:TreeView ID="trvLocation" runat="server" Font-Size="12px"ShowLines="True">
</asp:TreeView>

3.11 在Default.aspx页面后置代码中编写BindData数据绑定方法:

private void BindData()
{DAO dao = new DAO();LocationCollection locs = dao.GetLocations();TreeNodeCollection nodes = CreateTreeNodes(locs);foreach (TreeNode node in nodes){trvLocation.Nodes.Add(node);}
}

3.12 BindData方法调用了CreateTreeNode方法返回节点集合,该方法中递归调用自身以得到全部所在地节点:

private TreeNodeCollection CreateTreeNodes(LocationCollection locs)
{TreeNodeCollection nodeColl = new TreeNodeCollection();foreach (Location loc in locs){TreeNode node = new TreeNode(loc.Name, loc.Id.ToString());if (loc.SubLocations.Count > 0){TreeNodeCollection subColl = CreateTreeNodes(loc.SubLocations);foreach (TreeNode subNode in subColl)node.ChildNodes.Add(subNode);}nodeColl.Add(node);}return nodeColl;
}

3.13 最后在页面加载事件里执行数据绑定:

protected void Page_Load(object sender, EventArgs e)
{if (!IsPostBack){this.BindData();}
}

3.14 在浏览器中预览结果:

4. 总结

原来在处理类似父子关系时总是找不到好的解决办法,现在通过SQL Server 2005里的新特性可以较为合理的解决该类问题,在这里主要用到了with关键字实现递归访问,并且在输出数据时同样使用了递归的方法。如果各位有更好的实现方式,请不不吝赐教。

本文示例代码下载:示例代码


感谢各位支持,在SQL Server Management Studio中抓取了查询单表和以上算法的执行计划,供大家参考吧

1.select * from tb_loc

2. exec pr_GetLocations

转载于:https://www.cnblogs.com/ddlzq/p/4171074.html

SQL SERVER With语法[转]相关推荐

  1. Access和sql server的语法区别

    Access和sql server的语法区别 一.有区别的函数及解决方案  以下所示的解决方案中的函数定义在untDataBase单元中TAdoConn类的方法中. 序号 简述 Access语法 Sq ...

  2. SQL Server数据库语法篇(付费内容限时开放)

    SQL Server基础语法 一. 数据库的基本操作(命令操作) 1.1 创建数据库 1. create database 数据库名字 -- create databse mi-- 创建数据库并设置文 ...

  3. sql server基础语法 创建数据库 创建表

    sql server基础语法 创建数据库 创建表 1.创建数据库 2.表的创建 3.在现有表中添加标识列 4.创建外键 5.添加外键 6.约束 7.创建局部临时表 8.创建全局临时表 9.创建具有ch ...

  4. 常用SQL Server 小语法、函数 等的实例汇总

    2019独角兽企业重金招聘Python工程师标准>>> 重点内容常用SQL Serve函数及语法的部分实例汇总 本文介绍SQL Server 中常用的几种函数: 函数一:ISNULL ...

  5. SQL Server索引语法

    转载自: https://www.cnblogs.com/kissdodog/archive/2013/06/12/3133345.html https://www.cnblogs.com/guang ...

  6. SQL SERVER高级语法之T-SQL

    一. 信息打印 1.1 打印的方式 直接打印消息: 代码格式: --print 实战演示: 可以看到是打印在窗口中: 在表格中打印消息,可以设置多列,以及每一列的名字: 代码格式: --select ...

  7. SQL SERVER With语法

    今天在论坛上看到一个举例,关于sql server 的示例.1/25/50/100美分,多少种可能拼凑成2美元. 看了其中第一条语法,放在SQL SERVER中测试,发现真的列举出所有组合成2美元的方 ...

  8. SQLite与Sql Server的语法差异(转载)

    1.返回最后插入的标识值 返回最后插入的标识值sql server用@@IDENTITY sqlite用标量函数LAST_INSERT_ROWID() 返回通过当前的 SQLConnection 插入 ...

  9. mysql与SQL SERVER 基本语法区别

    无论SQL SERVER 还是MYSQL 一个表只能存在一个字段是自增长列 1.修改表 --SQL SERVER alter table table_name drop column column_n ...

最新文章

  1. 常考数据结构与算法:设计getMin功能的栈
  2. Shiro + JWT + Spring Boot Restful 简易教程
  3. 程序员最讨厌的100件事,瞬间笑喷了,哈哈~~
  4. redis大幅性能提升之使用管道(PipeLine)和批量(Batch)操作
  5. scrcpy设置快捷键_推荐电脑高清晰同步Anroid屏幕软件Scrcpy
  6. 开课吧课堂之何时调用构造函数
  7. DDD领域驱动设计详解
  8. java-工具-开源
  9. 中国微流体系统市场趋势报告、技术动态创新及市场预测
  10. 莫 言------------- 我们的荆轲
  11. 知物由学 | Android 模拟点击研究,如何突围“黑灰产”的自动化作弊?
  12. 氧化镁MgO晶体基片|钛酸锶SrTiO3晶体基片|铌酸锂LiNbO3晶体基片;直径10mm
  13. python 执行shell_从python执行Shell脚本与变量
  14. 【Vertica系列】一、安装建库
  15. JavaScript-筑基(二十五)navigator对象(判断页面打开终端)、history对象
  16. js 之 call用法
  17. Android移动应用设计与开发(第2版)——基于Android Studio开发环境 胡敏 黄宏程 李冲编著
  18. 浅谈我的建站经验之导航设置
  19. 计算机专业高级知识,高级选择_电脑基础知识_IT计算机_专业资料
  20. 在Tomcat下使用JavaBean

热门文章

  1. 抖音微信之争的真相:地盘之争,还是用户隐私之争?
  2. Junit如何进行多线程测试
  3. JAVA异步爬虫_Java 爬虫遇上数据异步加载,试试这两种办法!
  4. idea中的markdown文档如何插入图片
  5. mysql中,一条select语句是如何执行的?
  6. Nutch关于robot.txt的处理
  7. idea将maven项目打包成war包的方式,以及使用war包
  8. 深入理解Spark 2.1 Core (六):Standalone模式运行的原理与源码分析
  9. 生成式对抗网络Generative Adversarial Networks(GANs)
  10. 构建并用 TensorFlow Serving 部署 Wide Deep 模型