SqlServer SqlParser 介绍及使用示例

Intro

最近发现在 Nuget 上有一个 SqlServer 的 SqlParser,利用 SqlParser 我们做到可以解析 SQL 的每一部分 ,nuget 包是公开的,可以拿来即用,只是缺少使用示例,很多功能需要自己去摸索

Nuget 包是 https://www.nuget.org/packages/Microsoft.SqlServer.Management.SqlParser/

下面我们来看使用示例吧

Sample

来看一个简单的使用示例:

var sqlText = "SELECT TOP 100 * FROM dto.tabUsers WHERE Id > 10 ORDER BY Id DESC";
var result = Parser.Parse(sqlText);
Console.WriteLine(result.BatchCount);
Console.WriteLine(result.Script.Sql);Console.WriteLine("-------------------------------");
IterateSqlNode(result.Script);

上面的 IterateSqlNode 方法是一个遍历解析结果的一个方法,定义如下:

static void IterateSqlNode(SqlCodeObject sqlCodeObject, int indent=0)
{if (sqlCodeObject.Children == null) return;foreach (var child in sqlCodeObject.Children){Console.WriteLine($"{new string(' ', indent)}Sql:{child.Sql}, Type:{child.GetType().Name}");IterateSqlNode(child, indent+2);}
}

上面示例的输出结果如下:

从上面的输出结果,我们大概可以看得出来一个 SELECT 查询的 SQL 组成部分大概有以下部分:

  • SqlBatch

    • SqlSelectSpecification

    • SqlSelectClause

    • SqlFromClause

    • SqlWhereClause

    • SqlQuerySpecification

    • SqlOrderByClause

    • SqlSelectStatement

每一个 SQL 语句可能会有多个语句,所以最外层是一个 SqlBatch,如果只有一个语句就对应着一个 SqlBatch,如果是一个 SELECT 查询就是一个 SqlSelectStatement,由 SqlQuery(SqlSelectClause/SqlFromClause/SqlWhereClause)和 SqlOrderBy 组成,还有一些 GroupByClause/HavingClause 等从句,可以自己去尝试一下

Practice

接着我们再来看一个实例,我们的带分页的列表查询接口有几个方法内部都是两个方法,一个查询列表,一个查询总数,这样的查询大家是如何处理的呢?

我觉得有些繁琐,合成一个查询就好了,我尝试着利用 SqlParser 来分析 SQL 语句,根据列表查询的 SQL 自动生成一个查询总数的 SQL,来看下面这个例子:

// 查询列表 SQL
sqlText = @"
SELECT u.Id AS UserId, u.[Name] AS UserName, u.City AS [From] FROM dbo.tabUsers AS u WITH(NOLOCK)
INNER JOIN dbo.tabUserRoles AS r WITH(NOLOCK) ON r.UserId= u.Id
WHERE u.Id>10
ORDER BY u.Id DESC
OFFSET 0 ROWS FETCH NEXT 10 ROWS ONLY
";
IterateSqlNode(Parser.Parse(sqlText).Script);// 根据查询列表的 SQL 生成 GET COUNT 的 SQL
static string GetCountSql(string sql)
{var result = Parser.Parse(sql);if (result.Script is null){throw new ArgumentException("Invalid query", nameof(sql));}var sqlQuery = result.Script.Batches[0].Children.OfType<SqlSelectStatement>().FirstOrDefault()?.Children.OfType<SqlSelectSpecification>().FirstOrDefault()?.Children.OfType<SqlQuerySpecification>().FirstOrDefault();if (sqlQuery is null){throw new ArgumentException("Invalid query", nameof(sql));}return $@"SELECT COUNT(1) {sqlQuery.FromClause.Sql} {sqlQuery.WhereClause.Sql}";
}

上面这个 SQL 是一个比较典型的我们常用的列表查询 SQL,有的会更简单一些只需要一个表,有些查询条件会比较复杂一些,上面代码输出结果如下(内容有点长):

Sql:
SELECT u.Id AS UserId, u.[Name] AS UserName, u.City AS [From] FROM dbo.tabUsers AS u WITH(NOLOCK)
INNER JOIN dbo.tabUserRoles AS r WITH(NOLOCK) ON r.UserId= u.Id
WHERE u.Id>10
ORDER BY u.Id DESC
OFFSET 0 ROWS FETCH NEXT 10 ROWS ONLY
, Type:SqlBatchSql:SELECT u.Id AS UserId, u.[Name] AS UserName, u.City AS [From] FROM dbo.tabUsers AS u WITH(NOLOCK)
INNER JOIN dbo.tabUserRoles AS r WITH(NOLOCK) ON r.UserId= u.Id
WHERE u.Id>10
ORDER BY u.Id DESC
OFFSET 0 ROWS FETCH NEXT 10 ROWS ONLY, Type:SqlSelectStatementSql:SELECT u.Id AS UserId, u.[Name] AS UserName, u.City AS [From] FROM dbo.tabUsers AS u WITH(NOLOCK)
INNER JOIN dbo.tabUserRoles AS r WITH(NOLOCK) ON r.UserId= u.Id
WHERE u.Id>10
ORDER BY u.Id DESC
OFFSET 0 ROWS FETCH NEXT 10 ROWS ONLY, Type:SqlSelectSpecificationSql:SELECT u.Id AS UserId, u.[Name] AS UserName, u.City AS [From] FROM dbo.tabUsers AS u WITH(NOLOCK)
INNER JOIN dbo.tabUserRoles AS r WITH(NOLOCK) ON r.UserId= u.Id
WHERE u.Id>10, Type:SqlQuerySpecificationSql:SELECT u.Id AS UserId, u.[Name] AS UserName, u.City AS [From], Type:SqlSelectClauseSql:u.Id AS UserId, Type:SqlSelectScalarExpressionSql:u.Id, Type:SqlColumnOrPropertyRefExpressionSql:u.Id, Type:TwoPartObjectIdentifierSql:u, Type:SqlIdentifierSql:Id, Type:SqlIdentifierSql:UserId, Type:SqlIdentifierSql:u.[Name] AS UserName, Type:SqlSelectScalarExpressionSql:u.[Name], Type:SqlColumnOrPropertyRefExpressionSql:u.[Name], Type:TwoPartObjectIdentifierSql:u, Type:SqlIdentifierSql:[Name], Type:SqlIdentifierSql:UserName, Type:SqlIdentifierSql:u.City AS [From], Type:SqlSelectScalarExpressionSql:u.City, Type:SqlColumnOrPropertyRefExpressionSql:u.City, Type:TwoPartObjectIdentifierSql:u, Type:SqlIdentifierSql:City, Type:SqlIdentifierSql:[From], Type:SqlIdentifierSql:FROM dbo.tabUsers AS u WITH(NOLOCK)
INNER JOIN dbo.tabUserRoles AS r WITH(NOLOCK) ON r.UserId= u.Id, Type:SqlFromClauseSql:dbo.tabUsers AS u WITH(NOLOCK)
INNER JOIN dbo.tabUserRoles AS r WITH(NOLOCK) ON r.UserId= u.Id, Type:SqlQualifiedJoinTableExpressionSql:dbo.tabUsers AS u WITH(NOLOCK), Type:SqlTableRefExpressionSql:dbo.tabUsers, Type:TwoPartObjectIdentifierSql:dbo, Type:SqlIdentifierSql:tabUsers, Type:SqlIdentifierSql:u, Type:SqlIdentifierSql:NOLOCK, Type:SqlTableHintSql:dbo.tabUserRoles AS r WITH(NOLOCK), Type:SqlTableRefExpressionSql:dbo.tabUserRoles, Type:TwoPartObjectIdentifierSql:dbo, Type:SqlIdentifierSql:tabUserRoles, Type:SqlIdentifierSql:r, Type:SqlIdentifierSql:NOLOCK, Type:SqlTableHintSql:ON r.UserId= u.Id, Type:SqlConditionClauseSql:r.UserId= u.Id, Type:SqlComparisonBooleanExpressionSql:r.UserId, Type:SqlColumnOrPropertyRefExpressionSql:r.UserId, Type:TwoPartObjectIdentifierSql:r, Type:SqlIdentifierSql:UserId, Type:SqlIdentifierSql:u.Id, Type:SqlColumnOrPropertyRefExpressionSql:u.Id, Type:TwoPartObjectIdentifierSql:u, Type:SqlIdentifierSql:Id, Type:SqlIdentifierSql:WHERE u.Id>10, Type:SqlWhereClauseSql:u.Id>10, Type:SqlComparisonBooleanExpressionSql:u.Id, Type:SqlColumnOrPropertyRefExpressionSql:u.Id, Type:TwoPartObjectIdentifierSql:u, Type:SqlIdentifierSql:Id, Type:SqlIdentifierSql:10, Type:IntegerLiteralExpressionSql:ORDER BY u.Id DESC
OFFSET 0 ROWS FETCH NEXT 10 ROWS ONLY, Type:SqlOrderByClauseSql:u.Id DESC, Type:SqlOrderByItemSql:u.Id, Type:SqlColumnOrPropertyRefExpressionSql:u.Id, Type:TwoPartObjectIdentifierSql:u, Type:SqlIdentifierSql:Id, Type:SqlIdentifierSql:OFFSET 0 ROWS FETCH NEXT 10 ROWS ONLY, Type:SqlOffsetFetchClauseSql:0, Type:IntegerLiteralExpressionSql:10, Type:IntegerLiteralExpression

输出的查询 COUNT 的 SQL 语句如下:

SELECT COUNT(1) FROM dbo.tabUsers AS u WITH(NOLOCK)
INNER JOIN dbo.tabUserRoles AS r WITH(NOLOCK) ON r.UserId= u.Id WHERE u.Id>10

看上去还是比较符合预期的,另外测试了几种稍微比较复杂的情况也都是可以满足我们的需要的

可以自动生成了 COUNT SQL 之后,我们就可以封装一个方法只需要传一个列表查询的接口就可以了

大概实现如下:

public async Task<List<T>> PageListWithTotalAsync<T>(string sql, PageSearchWithTotalDto param)
{var countSql = GetCountSql(sql);var execSql = $@"
SET @TotalCount=({countSql});
{sql}
OFFSET @Offset ROWS FETCH NEXT @PageSize ROWS ONLY";var parameter = new DynamicParameters(param);parameter.Output(param, x => x.TotalCount);using var conn = new SqlConnection("");return (await conn.QueryAsync<T>(execSql, parameter)).ToList();
}

PageSearchWithTotalDto 是一个 Model,定义如下:

public class PageSearchDto
{private int _pageNumber = 1;private int _pageSize = 20;public int PageNumber{get => _pageNumber;set{if (value > 0){_pageNumber = value;}}}public virtual int PageSize{get => _pageSize;set{if (value > 0){_pageSize = value;}}}public int Offset => (PageNumber - 1) * PageSize;
}public class PageSearchWithTotalDto : PageSearchDto
{public int TotalCount { get; set; }
}

调用方式示例如下:

var sql = @"SELECT u.Id AS UserId, u.[Name] AS UserName, u.City AS [From] FROM dbo.tabUsers AS u WITH(NOLOCK)
INNER JOIN dbo.tabUserRoles AS r WITH(NOLOCK) ON r.UserId= u.Id
WHERE u.Id>10
ORDER BY u.Id DESC";var search = new PageSearchWithTotalDto()
{PageNum=1,PageSize=10,
};
var list = await PageListWithTotalAsync(sql, search);
Console.WriteLine(search.TotalCOunt);

相比之前的代码,已经简洁了不少,又有一大波重复代码可以消灭了,舒服~~

More

使用 SqlParser 来自动生成语句这种方案实际上并没有应用到我们的项目中,但是我觉得这个不一样的思路也许对你有所帮助,在你需要解析 SQL 的时候可以考虑一下这个 SqlParser

前面的示例可以从 Github 上获取 https://github.com/WeihanLi/SamplesInPractice/blob/master/SqlParserSample/Program.cs,有人在 Github 上提了一个关于开源这个 SqlParser 的 issue,有需要可以关注一下 https://github.com/microsoft/sqltoolsservice/issues/623

References

  • https://github.com/WeihanLi/SamplesInPractice/blob/master/SqlParserSample/Program.cs

  • https://www.nuget.org/packages/Microsoft.SqlServer.Management.SqlParser/

  • https://github.com/microsoft/sqltoolsservice/issues/623

SqlServer SqlParser 介绍及基本使用相关推荐

  1. SQLServer约束介绍

    约束定义 对于数据库来说,基本表的完整性约束分为列级约束条件和表级约束条件: 列级约束条件 列级约束条件是对某一个特定列的约束,包含在列定义中,可以直接跟在该列的其他定义之后,用空格分隔,不用指定列名 ...

  2. SqlServer索引介绍

    索引的概念 索引的用途:我们对数据查询及处理速度已成为衡量应用系统成败的标准,而采用索引来加快数据处理速度通常是最普遍采用的优化方法. 索引是什么:数据库中的索引类似于一本书的目录,在一本书中使用目录 ...

  3. SqlServer微软数据库简单介绍

    1.SqlServer基本介绍 sql SQL是英文Structured Query Language的缩写,意思为结构化查询语言.SQL语言的主要功能就是同各种数据库建立联系,进行沟通. 按照ANS ...

  4. sqlserver mysql bigint_sqlserver bigint(20)

    每个 BIGINT 类型 的数据占用 8 个字节的存储空间. 二. 浮点数据类型 浮点数据类型用于存储十进制小数.浮点数值的数据在 SQL Server 中采用上舍入 (Round up...... ...

  5. 图书馆管理系统——vs2017\sqlserver\c#

    基于vs2017的图书馆管理系统 图书馆管理系统 基于vs2017的图书馆管理系统 1. 用到的工具:vs2017 .sqlserver 2. 介绍数据库表: 2.1 首先打开数据库:sqlsrerv ...

  6. mysql对时间操作系统_MySQL时间操作的系统函数用法

    oracle length and lengthb LENGTH──返回以字符为单位的字符串长度. LENGTHB──返回以字节为单位的字符串长度,它和类型定义中的长度是一个概念,比如你定义的varc ...

  7. 小知识系列:数据库的主键和外键

    文章目录 简介 创建主键 MySQL Oracle SQL Server 创建外键 mysql Oracle SQL Server 总结 简介 数据库是我们所有应用程序的基础,没有数据库的程序不是一个 ...

  8. 【java毕业设计】基于java+Eclipse +SQL Server的工厂进销存管理系统设计与实现(毕业论文+程序源码)——工厂进销存管理系统

    基于java+Eclipse +SQL Server的工厂进销存管理系统设计与实现(毕业论文+程序源码) 大家好,今天给大家介绍基于java+Eclipse +SQL Server的工厂进销存管理系统 ...

  9. java进销存管理系统设计,基于JavaSwing进销存管理系统的设计与实现毕业论文+任务书+中期表+翻译及原文+答辩+源码+数据库+辅导视频...

    基于JavaSwing进销存管理系统的设计与实现 摘 要 时代在进步,我们的生产生活方式当然也要相对应的做出改变了.在今天这样一个信息化的时代,计算机软件已经广泛的被用于日常的办公,仓库的库存管理,企 ...

最新文章

  1. 68-95-99规则–以普通英语解释正态分布
  2. 47 jQuery文本内容值
  3. php全局变量GLOBAL
  4. R数据导入导出(一): read.table()和read.csv()的区别
  5. MySql事务select for update及数据的一致性处理讲解
  6. jQuery Ajax 实例 全解析
  7. 样条曲面_这样的曲面是如何画成的,用好剪裁工具,便迎刃而解
  8. matlab绘制二元一次函数图像_【八上数学】 一次函数必考知识点(下)
  9. 进程前台运行后台运行的相关命令
  10. LeetCode —— 148. 排序链表(Python)
  11. 水果图像识别:基于 Arduino 和 TensorFlow Lite Micro
  12. php解决mysql主从同步_mysql 主从同步原理
  13. bash的基础特性(一)
  14. 怎么区分zh和ch_zh,ch,sh,和z,c,s怎么分辨?
  15. .netcore 如何获取系统中所有session_C#/.NET/.NET Core定时任务调度组件有哪些?
  16. 04_用户注册与自定义数据转换器Conventer
  17. Filezilla server连接Ubuntu被服务器拒绝及中文乱码问题解决
  18. IDEA如何集成P3C插件检测代码
  19. FLUKE OTDR光纤断点测试仪OFP2-100-Q特色功能及亮点分析
  20. 【OpenCV笔记】光流法之金字塔Lucas-Kanade

热门文章

  1. [转]张孟苏考上的不是大学
  2. 怎样编译libdb_比特币编译(Ubuntu 16.04)
  3. MyBatis 分页插件 PageHelper
  4. 2016福州大学软件工程第四次团队作业-系统设计成绩汇总
  5. JAVA学习博客---2015.5
  6. C++遍历树-非递归递归-使用了标记位
  7. C程序优化之路(二)
  8. android 小黄车首页,android采用MVP漫画APP、适配刘海屏、小黄车主界面、录音波浪动画、综合APP等源码...
  9. 主动给团队或用户安装Teams App
  10. windows7黑屏修复_如何在Windows 10更新后修复黑屏