SqlParser.Net
SqlParser.Net is a free, comprehensive and high-performance SQL parsing engine library that can help you parse and process SQL simply, quickly and efficiently.SqlParser.Net是一个免费,功能全面且高性能的sql解析引擎类库,它可以帮助你简单快速高效的解析和处理sql。
Install / Use
/learn @TripleView/SqlParser.NetREADME
破坏性更新日志
什么是sql解析
正如我们拿到一个json字符串,我们在程序里是无法处理的,只有将他反序列化为类,我们的程序才能正确处理这个类,这个反序列化的过程,也可以称之为json解析,sql解析也是一样的,帮助我们将sql字符串解析为结构化的类
背景
hi 大家好,我是三合,在过往的岁月中,我曾经想过要写以下这些工具
- 写一个通过拦截业务系统所有sql,然后根据这些sql自动分析表与表,字段与字段之间是如何关联的工具,即sql血缘分析工具
- 想着动态改写sql,比如给where动态添加一个条件。
- 写一个sql格式化工具
- 写一个像mycat那样的分库分表中间件
- 写一个sql防火墙,防止出现where 1=1后没有其他条件导致查询全表
- 写一个数据库之间的sql翻译工具,比如把sqlserver的sql自动翻译为oracle的sql
但是无一例外,都失败了,因为要实现以上这些需求,都需要一个核心的类库,即sql解析引擎,遗憾的是,我没有找到合适的,这是我当初寻找的轨迹
- 我发现了tsql-parser,但他只支持sql server,所以只能pass。
- 然后我又发现了SqlParser-cs, 他的语法树解析出来像这样,
JsonConvert.SerializeObject(statements.First(), Formatting.Indented)
// Elided for readability
{
"Query": {
"Body": {
"Select": {
"Projection": [
{
"Expression": {
"Ident": {
"Value": "a",
"QuoteStyle": null
}
}
}
...
额,怎么说呢,这语法树也太丑了点吧,同时非常难以理解,跟我想象中的完全不一样啊,于是也只能pass。
-
接下来我又发现了另外一些基于antlr来解析sql的类库,比如SQLParser,因为代码是antlr自动生成的,比较难以进行手动优化,所以还是pass。
-
最后我还发现了另外一个gsp的sqlparser,但它是收费的,而且巨贵无比,也pass。
找了一圈下来,我发现符合我要求的类库并不存在,所以我上面的那些想法,也一度搁浅了,但每一次的搁浅,都会使我内心的不甘加重一分,终于有一天,我下定决心,自己动手,丰衣足食,所以最近花了大概3个月时间,从头开始写了一个sql解析引擎,包括词法解析器到语法分析器,不依赖任何第三方组件,纯c#代码,在通过了360个各种各样场景的单元测试以及各种真实的业务环境验证后,今天它SqlParser.Net1.0.0正式发布了,本项目基于MIT协议开源,有以下优点,
- 支持5大数据库,oracle,sqlserver,mysql,pgsql以及sqlite。
- 极致的速度,解析普通sql,时间基本在0.3毫秒以下,当然了,sql越长,解析需要的时间就越长。
- 文档完善,众所周知,我三合的开源项目,一向是文档齐全且简单易懂,做到看完就能上手,同时,我也会根据用户的反馈不断的补充以及完善文档。
- 代码简洁易懂
SqlParser.Net存在的意义
SqlParser.Net是一个免费,功能全面且高性能的sql解析引擎类库,它可以帮助你简单快速高效的解析和处理sql。如果你使用本项目实现了哪些有意思的功能,欢迎提交issue告诉我,我会把这些案例放在readme里,以供其他用户借此产生灵感。
Getting Started
接下来,我将介绍SqlParser.Net的用法
通过Nuget安装
你可以运行以下命令在你的项目中安装 SqlParser.Net 。
PM> Install-Package SqlParser.Net
支持框架
netstandard2.0
从最简单的demo开始
让我们一起看一个最简单的select语句是如何解析的
var sql = "select * from test";
var sqlAst = DbUtils.Parse(sql, DbType.Oracle);
var result = sqlAst.ToFormat();
var newSql= sqlAst.ToSql();
解析结果如下:
var expect = new SqlSelectExpression()
{
Query = new SqlSelectQueryExpression()
{
Columns = new List<SqlSelectItemExpression>()
{
new SqlSelectItemExpression()
{
Body = new SqlAllColumnExpression()
}
},
From = new SqlTableExpression()
{
Name = new SqlIdentifierExpression()
{
Value = "test"
}
}
}
};
以上面为例子,抽象语法树的所有叶子节点均为sqlExpression的子类,且各种sqlExpression节点可以互相嵌套,组成一颗复杂无比的树,其他sql解析引擎还分为statement和expression,我认为过于复杂,所以全部统一为sqlExpression,顶级的sqlExpression总共分为4种,
- 查询语句(SqlSelectExpression)
- 插入语句(SqlInsertExpression)
- 删除语句(SqlDeleteExpression)
- 更新语句(SqlUpdateExpression)
这4种顶级语句中,我认为最复杂的是查询语句,因为查询组合非常多,要兼容各种各样的情况,其他3种反而非常简单。现阶段,sqlExpression的子类一共有38种,我将在下面的演示中,结合例子给大家讲解一下。
1. Select查询语句
如上例子,SqlSelectExpression代表一个查询语句,SqlSelectQueryExpression则是真正具体的查询语句,他包括了
- 要查询的所有列(Columns字段)
- 数据源(From字段)
- 条件过滤语句(Where字段)
- 分组语句(GroupBy字段)
- 排序语句(OrderBy字段)
- 分页语句(Limit字段)
- Into语句(sql server专用,如SELECT id,name into test14 from TEST t)
- ConnectBy语句(oracle专用,如SELECT LEVEL l FROM DUAL CONNECT BY NOCYCLE LEVEL<=100)
- WithSubQuerys语句,公用表表达式,即CTE
其中Columns是一个列表,他的每一个子项都是一个SqlSelectItemExpression,他的body代表一个逻辑子句,逻辑子句的值,可以为以下这些
- 字段,如name,
- 二元表达式,如t.age+3
- 函数调用,如LOWER(t.NAME)
- 一个完整的查询语句,如SELECT name FROM TEST2 t2
包括order by,partition by,group by,between,in,case when后面跟着的都是逻辑子句,这个稍后会演示,在这个例子中,因为是要查询所有列,所以仅有一个SqlSelectItemExpression,他的body是SqlAllColumnExpression(代表所有列),From代表要查询的数据源,在这里仅单表查询,所以From的值为SqlTableExpression(代表单表),表名是一个SqlIdentifierExpression,即标识符表达式,表示这是一个标识符,在SQL中,标识符(Identifier)是用于命名数据库对象的名称。这些对象可以包括表、列、索引、视图、模式、数据库等。标识符使得我们能够引用和操作这些对象,在这里,标识符的值为test,表示表名为test。
1.1 查询返回列的各种情形
1.1.1 查询指定字段
var sql = "select id AS bid,t.NAME testName from test t";
var sqlAst = DbUtils.Parse(sql, DbType.Oracle);
解析结果如下:
var expect = new SqlSelectExpression()
{
Query = new SqlSelectQueryExpression()
{
Columns = new List<SqlSelectItemExpression>()
{
new SqlSelectItemExpression()
{
Body = new SqlIdentifierExpression()
{
Value = "id",
},
Alias = new SqlIdentifierExpression()
{
Value = "bid",
},
},
new SqlSelectItemExpression()
{
Body = new SqlPropertyExpression()
{
Name = new SqlIdentifierExpression()
{
Value = "NAME",
},
Table = new SqlIdentifierExpression()
{
Value = "t",
},
},
Alias = new SqlIdentifierExpression()
{
Value = "testName",
},
},
},
From = new SqlTableExpression()
{
Name = new SqlIdentifierExpression()
{
Value = "test",
},
Alias = new SqlIdentifierExpression()
{
Value = "t",
},
},
},
};
在上面这个例子中,我们指定了要查询2个字段,分别是id和t.NAME,此时Columns列表里有2个值, 第一个SqlSelectItemExpression包含了
- 主体,即body字段,在本例子中他的值是一个SqlIdentifierExpression表达式,值为id,表示列名为id,
- 别名,即Alias字段,在本例子中他也是一个SqlIdentifierExpression,值为bid,代表列别名为bid,
第二个SqlSelectItemExpression的body里是一个SqlPropertyExpression,代表这是一个属性表达式,SqlPropertyExpression它包含了
- 表名,即Table字段,值为t,即表名为t
- 属性名,即Name字段,值为Name,即属性名为name
合起来则代表t表的name字段,而第二个SqlSelectItemExpression也有列别名,即testName,这个查询也是单表查询,但SqlTableExpression他多了一个Alias别名字段,即表示,表别名为t。
1.1.2 查询列为二元表达式的情况
var sql = "select 1+2 from test";
var sqlAst = DbUtils.Parse(sql, DbType.Oracle);
解析结果如下:
var expect = new SqlSelectExpression()
{
Query = new SqlSelectQueryExpression()
{
Columns = new List<SqlSelectItemExpression>()
{
new SqlSelectItemExpression()
{
Body = new SqlBinaryExpression()
{
Left = new SqlNumberExpression()
{
Value = 1M,
},
Operator = SqlBinaryOperator.Add,
Right = new SqlNumberExpression()
{
Value = 2M,
},
},
},
},
From = new SqlTableExpression()
{
Name = new SqlIdentifierExpression()
{
Value = "test",
},
},
},
};
在这个例子中,要查询的字段的值为一个二元表达式SqlBinaryExpression,他包含了
- 左边部分,即Left字段,值为一个SqlNumberExpression,即数字表达式,它的值为1
- 右边部分,即Right字段,值为一个SqlNumberExpression,即数字表达式,它的值为2
- 中间符号,即Operator字段,值为add,即加法
这个例子证明了,SqlSelectItemExpression代表一个逻辑子句,而不仅仅是某个字段。
1.1.3 查询列为字符串/数字/布尔值的情况
var sql = "select ''' ''',3,true FROM test";
var sqlAst = DbUtils.Parse(sql, DbType.MySql);
解析结果如下:
var expect = new SqlSelectExpression()
{
Query = new SqlSelectQueryExpression()
{
Columns = new List<SqlSelectItemExpression>()
{
new SqlSelectItemExpression()
{
Body = new SqlStringExpression()
{
Value = "' '"
},
},
new SqlSelectItemExpression()
{
Body = new SqlNumberExpression()
{
Value = 3M,
},
},
new SqlSelectItemExpression()
{
Body = new SqlBoolExpression()
{
Value = true
},
},
},
From = new SqlTableExpression()
{
Name = new SqlIdentifierExpression()
{
Value = "test",
},
},
},
};
在这个例子中,要查询的3个字段为字符串,数字和布尔值,字符串表达式即SqlStringExpression,body里即字符串的值' ',数字表达式即SqlNumberExpression,值为3,布尔表达式即SqlBoolExpression,值为true;
1.1.4 查询列为函数调用的情况
1.1.4.1 简单的函数调用
var sql = "select LOWER(name) FROM test";
var sqlAst = DbUtils.Parse(sql, DbType.Oracle);
解析结果如下:
var expect = new SqlSelectExpression()
{
Query = new SqlSelectQueryExpression()
{
Columns = new List<SqlSelectItemExpression>()
{
new SqlSelectItemExpression()
{
Body = new SqlFunctionCallExpression()
{
Name = new SqlIdentifierExpression()
{
Value = "LOWER",
},
Arguments = new List<SqlExpression>()
{
new SqlIdentifierExpression()
{
Value = "name",
},
},
},
},
},
From = new SqlTableExpression()
{
Name = new SqlIdentifierExpression()
{
Value = "test",
},
},
},
};
在这个例子中,要查询的表达式是一个函数调用,函数调用表达式即SqlFunctionCallExpression,它包含了
- 函数名,即Name字段,值为LOWER,
- 函数参数列表,即Arguments字段,列表里只有一个值,即函数只有一个参数,且参数的值为name
1.1.4.2 带有over子句的函数调用
var sql = "SELECT t.*, ROW_NUMBER() OVER ( PARTITION BY t.ID ORDER BY t.NAME,t.ID) as rnum FROM TEST t";
var sqlAst = DbUtils.Pa
Related Skills
feishu-drive
339.3k|
things-mac
339.3kManage Things 3 via the `things` CLI on macOS (add/update projects+todos via URL scheme; read/search/list from the local Things database)
clawhub
339.3kUse the ClawHub CLI to search, install, update, and publish agent skills from clawhub.com
yu-ai-agent
2.0k编程导航 2025 年 AI 开发实战新项目,基于 Spring Boot 3 + Java 21 + Spring AI 构建 AI 恋爱大师应用和 ReAct 模式自主规划智能体YuManus,覆盖 AI 大模型接入、Spring AI 核心特性、Prompt 工程和优化、RAG 检索增强、向量数据库、Tool Calling 工具调用、MCP 模型上下文协议、AI Agent 开发(Manas Java 实现)、Cursor AI 工具等核心知识。用一套教程将程序员必知必会的 AI 技术一网打尽,帮你成为 AI 时代企业的香饽饽,给你的简历和求职大幅增加竞争力。
