10

如何踢掉 sql 语句中的尾巴,我用 C# 苦思了五种办法

 3 years ago
source link: https://mp.weixin.qq.com/s?__biz=MzU4Mjc4NzgyOQ%3D%3D&%3Bmid=2247487707&%3Bidx=1&%3Bsn=dd475f550295be8031e1ef79ddf66b33
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

IjEvUn6.gif!mobile

一:背景

1. 讲故事

这几天都在修复bug真的太忙了,期间也遇到了一个挺有趣bug,和大家分享一下,这是一块sql挺复杂的报表相关业务,不知道哪一位大佬在错综复杂的 嵌套 + 平行 if判断中sql拼接在某些UI组合下出问题了,最终的 sql 架构类似这样的。


var sql = "select 1 union all select 2 union all select 3 union all";

这种sql到数据库去肯定是报错的,有些朋友可能想说这还不简单,在相关的 if 判断中不要追加这个 union all 就好了,这确实是一个根治的办法,但现实情况这一块的业务太复杂了,也不太敢改里面的代码,改的没问题还好,改出问题你得兜着走,所以最保险的办法就是怎么去掉  union all 这个大尾巴,所以我干脆思考了一会,想出了如下五种办法。

二:剔除 union all 的五大方式

1. 最原始的 for 循环

最简单的办法就是通过 for 循环搞定,我可以倒序判断最后几个字符是不是关键词 union all 就可以了,如下代码所示:


static void Main(string[] args)
{
var sql = "select 1 union all select 2 union all select 3 union all";

var keyword = "union all";

var isall = true;
int i = 0;

for (i = 1; i <= keyword.Length; i++)
{
if (keyword[keyword.Length - i] != sql[sql.Length - i])
{
isall = false;
break;
}
}

if (isall)
{
var query = sql.Substring(0, sql.Length - i + 1);

Console.WriteLine(query);
}
}

IJZVzyj.png!mobile

从代码中可以看出,只要在倒序的过程中,有一个字符和 keyword 中的不符,那就直接跳出,否则就是全匹配,拿到最后的 i 进行 Substring 截取即可。

2. 使用 Substring 搞定

第一种方式确实可以实现,但实现的并不轻松,毕竟大家都是用 C# 写代码而不是 C,为了这点小功能写了这么多代码,显得太 low 了,所以尽量能用类库的方法就用类库的方法吧,改进措施很简单,可以从 sql 尾部切出 keyword.length 个字符,也就是: start:sql.length - keyword.length ,然后判断一下是否和 keyword 相等即可,代码修改如下:


static void Main(string[] args)
{
var sql = "select 1 union all select 2 union all select 3 union all";

var keyword = "union all";

var isSucc = sql.Substring(sql.Length - keyword.Length) == keyword;

if (isSucc)
{
var query = sql.Substring(0, sql.Length - keyword.Length);

Console.WriteLine(query);
}
}

AB7VRj3.png!mobile

3. 使用 LastIndexOf

第二种方式写出来的代码确实比较简洁,但大家有没有发现一个问题,我为了获取最后的 string 做了两次 substring 操作,也就是说在托管堆中生成了两个 string 对象,那能不能免掉一个 substring 呢?给 gc 减轻一些负担,这就可以用到 LastIndexOf 方法了,代码如下:


static void Main(string[] args)
{
var sql = "select 1 union all select 2 union all select 3 union all";

var keyword = "union all";

var index = sql.LastIndexOf(keyword);

if (sql.Length - index == keyword.Length)
{
var query = sql.Substring(0, index);

Console.WriteLine(query);
}
}

yuqaQnI.png!mobile

思想很简单,就是判断最后出现的 union all 的位置到尾部的距离 是否恰好和 keyword.length 一致,如果是的话那 keyword 就是 sql 的大尾巴,这里的 if 写的有点难懂,其实还可以使用  EndsWith 再优化一下代码:


static void Main(string[] args)
{
var sql = "select 1 union all select 2 union all select 3 union all";

var keyword = "union all";

if (sql.EndsWith(keyword))
{
var query = sql.Substring(0, sql.Length - keyword.Length);
Console.WriteLine(query);
}
}

yuqaQnI.png!mobile

4. 使用 Split 切割

前面几种方式都是在 string 上做文章,要么 substring,要么 LastIndexOf,要么 EndsWith,其实也可以跳出这个定势思维,转换成数组进行处理,用 union all 作为分隔符切割字符串,如果数组的最后一个元素为 string.Empty,那就表明 sql 尾巴就是 keyword, 对吧,代码修改如下:


static void Main(string[] args)
{
var sql = "select 1 union all select 2 union all select 3 union all";

var keyword = "union all";

var arr = sql.Split("union all");

if (string.IsNullOrEmpty(arr[arr.Length - 1]))
{
var query = string.Join(keyword, arr.Take(arr.Length - 1));

Console.WriteLine(query);
}
}


fM77v2q.png!mobile

5. 使用 TrimEnd

相信很多朋友用这个方法的场景大多在于剔除尾部的空格,哈哈,其实它还有一个隐藏功能,不仅可以剔除空格,还可以剔除任意多个指定的字符,这就:ox::nose:了,不信的话可以看看 TrimEnd 方法签名即可:


public sealed class String
{
public string TrimEnd(params char[] trimChars)
{
if (trimChars == null || trimChars.Length == 0)
{
return TrimHelper(1);
}
return TrimHelper(trimChars, 1);
}
}

可以看到 trimChars 是一个字符数组,你可以灌入你想剔除的任意多·个字符,有了这个思想,我可以将 keyword 转成 char[] 再灌入到 TrimEnd 即可,代码如下:


static void Main(string[] args)
{
var sql = "select 1 union all select 2 union all select 3 union all";

var keyword = "union all";

var query = sql.TrimEnd(keyword.ToArray());

Console.WriteLine(query);
}

iqqi6nN.png!mobile

可以看出已成功剔除,此时我很好奇底层到底是怎么实现的,源码如下:


private string TrimHelper(char[] trimChars, int trimType)
{
int num = Length - 1;
int i = 0;
if (trimType != 1)
{
for (i = 0; i < Length; i++)
{
int num2 = 0;
char c = this[i];
for (num2 = 0; num2 < trimChars.Length && trimChars[num2] != c; num2++)
{
}
if (num2 == trimChars.Length)
{
break;
}
}
}
if (trimType != 0)
{
for (num = Length - 1; num >= i; num--)
{
int num3 = 0;
char c2 = this[num];
for (num3 = 0; num3 < trimChars.Length && trimChars[num3] != c2; num3++)
{
}
if (num3 == trimChars.Length)
{
break;
}
}
}
return CreateTrimmedString(i, num);
}

private string CreateTrimmedString(int start, int end)
{
int num = end - start + 1;
if (num == Length)
{
return this;
}
if (num == 0)
{
return Empty;
}
return InternalSubString(start, num);
}

这么多代码,看样子 TrimEnd 底层也不是那么容易的,虽然用起来很爽。

四:总结

五种方式各有利弊,不管是简单粗暴的,基于性能的,灵活巧妙的,都能达到最终的目的,暂时就想到这5种,脑仁已经疼了:joy::joy::joy:, 更多好玩的写法,欢迎大家留言讨论哈!

B7nYBzr.jpg!mobile

往期 精彩 回顾

【推荐】.NET Core开发实战视频课程   ★★★

.NET Core实战项目之CMS 第一章 入门篇-开篇及总体规划

【.NET Core微服务实战-统一身份认证】开篇及目录索引

Redis基本使用及百亿数据量中的使用技巧分享(附视频地址及观看指南)

.NET Core中的一个接口多种实现的依赖注入与动态选择看这篇就够了

10个小技巧助您写出高性能的ASP.NET Core代码

用abp vNext快速开发Quartz.NET定时任务管理界面

在ASP.NET Core中创建基于Quartz.NET托管服务轻松实现作业调度

现身说法:实际业务出发分析百亿数据量下的多表查询优化

关于C#异步编程你应该了解的几点建议

C#异步编程看这篇就够了


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK