如何踢掉 sql 语句中的尾巴,我用 C# 苦思了五种办法
一:背景
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);}}從代碼中可以看出,只要在倒序的過程中,有一個字符和 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);}}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);}}思想很簡單,就是判斷最后出現的 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);}}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);}}5. 使用 TrimEnd
相信很多朋友用這個方法的場景大多在于剔除尾部的空格,哈哈,其實它還有一個隱藏功能,不僅可以剔除空格,還可以剔除任意多個指定的字符,這就????????了,不信的話可以看看 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);}可以看出已成功剔除,此時我很好奇底層到底是怎么實現的,源碼如下:
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種,腦仁已經疼了????????????, 更多好玩的寫法,歡迎大家留言討論哈!
總結
以上是生活随笔為你收集整理的如何踢掉 sql 语句中的尾巴,我用 C# 苦思了五种办法的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 从零开始实现 ASP.NET Core
- 下一篇: .NET 应用如何优雅的做功能开关(Fe