T-SQL函数实现
1 IF OBJECT_ID('dbo.fn_SQLSigTSQL') IS NOT NULLDROP FUNCTION dbo.fn_SQLSigTSQL;GO 2 CREATE FUNCTION dbo.fn_SQLSigTSQL 3 (@p1 NTEXT, @parselength INT = 4000) 4 RETURNS NVARCHAR(4000) 5 6 7 -- 8 --该函数以“现状”提供且没有任何担保 9 --同时也没有授予任何权利 10 -- 使用该函数所包含的脚本示例受下列条款约束 11 -- http://www.microsoft.com/info/cpyright.htm 12 -- 13 -- 分解查询字符串 14 AS 15 BEGIN 16 DECLARE @pos AS INT; 17 DECLARE @mode AS CHAR(10); 18 DECLARE @maxlength AS INT; 19 DECLARE @p2 AS NCHAR(4000); 20 DECLARE @currchar AS CHAR(1), @nextchar AS CHAR(1); 21 DECLARE @p2len AS INT; 22 23 24 SET @maxlength = LEN(RTRIM(SUBSTRING(@p1,1,4000))); 25 SET @maxlength = CASE WHEN @maxlength > @parselength 26 THEN @parselength ELSE @maxlength END; 27 28 29 SET @pos = 1; 30 SET @p2 = ''; 31 SET @p2len = 0; 32 SET @currchar = ''; 33 set @nextchar = ''; 34 SET @mode = 'command'; 35 36 37 WHILE (@pos <= @maxlength) 38 BEGIN 39 SET @currchar = SUBSTRING(@p1,@pos,1); 40 SET @nextchar = SUBSTRING(@p1,@pos+1,1); 41 IF @mode = 'command' 42 BEGIN 43 SET @p2 = LEFT(@p2,@p2len) + @currchar; 44 SET @p2len = @p2len + 1 ; 45 IF @currchar IN (',','(',' ','=','<','>','!') 46 AND @nextchar BETWEEN '0' AND '9' 47 BEGIN 48 SET @mode = 'number'; 49 SET @p2 = LEFT(@p2,@p2len) + '#'; 50 SET @p2len = @p2len + 1; 51 END 52 IF @currchar = '''' 53 BEGIN 54 SET @mode = 'literal'; 55 SET @p2 = LEFT(@p2,@p2len) + '#'''; 56 SET @p2len = @p2len + 2; 57 END 58 END 59 ELSE IF @mode = 'number' AND @nextchar IN (',',')',' ','=','<','>','!') 60 SET @mode= 'command'; 61 ELSE IF @mode = 'literal' AND @currchar = '''' 62 SET @mode= 'command'; 63 64 65 SET @pos = @pos + 1; 66 END 67 RETURN @p2; 68 END 69 GO
示范:
1 print dbo.fn_SQLSigTSQL('select * from Customers t 2 where t.City='''+'LonDon'+''' and t.Country='''+'UK'+'''',4000) 3 --输出 4 /* 5 6 select * from Customers t 7 where t.City='#' and t.Country='#' 8 9 */
CLR在处理迭代/过程逻辑和字符串处理时比T-SQL效率高,下面介绍用CLR实现模式化查询
CLR方式
a. 建立C#版的Classs Libary,函数如下:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using Microsoft.SqlServer.Server; using System.Data.SqlTypes; using System.Text.RegularExpressions;
public partial class SQLSignature { // fn_SQLSigCLR [SqlFunction(IsDeterministic = true, DataAccess = DataAccessKind.None)] public static SqlString fn_SQLSigCLR(SqlString querystring) { return (SqlString)Regex.Replace( querystring.Value, @"([\s,(=<>!](?![^\]]+[\]]))(?:(?:(?:(?# expression coming )(?:([N])?(')(?:[^']|'')*('))(?# character )|(?:0x[\da-fA-F]*)(?# binary )|(?:[-+]?(?:(?:[\d]*\.[\d]*|[\d]+)(?# precise number )(?:[eE]?[\d]*)))(?# imprecise number )|(?:[~]?[-+]?(?:[\d]+))(?# integer ))(?:[\s]?[\+\-\*\/\%\&\|\^][\s]?)?)+(?# operators ))", @"$1$2$3#$4"); } // fn_RegexReplace - for generic use of RegEx-based replace [SqlFunction(IsDeterministic = true, DataAccess = DataAccessKind.None)] public static SqlString fn_RegexReplace( SqlString input, SqlString pattern, SqlString replacement) { return (SqlString)Regex.Replace( input.Value, pattern.Value, replacement.Value); } }
b. 加载.dll中间语言代码到DB
USE master; CREATE ASSEMBLY SQLSignature FROM 'C:\SQLSignature\SQLSignature\bin\Debug\SQLSignature.dll';
c. 注册函数fn_SQLSigCLR和fn_RegexReplace
CREATE FUNCTION dbo.fn_SQLSigCLR(@querystring AS NVARCHAR(MAX)) RETURNS NVARCHAR(MAX) WITH RETURNS NULL ON NULL INPUT EXTERNAL NAME SQLSignature.SQLSignature.fn_SQLSigCLR; GO CREATE FUNCTION dbo.fn_RegexReplace( @input AS NVARCHAR(MAX), @pattern AS NVARCHAR(MAX), @replacement AS NVARCHAR(MAX)) RETURNS NVARCHAR(MAX) WITH RETURNS NULL ON NULL INPUT EXTERNAL NAME SQLSignature.SQLSignature.fn_RegexReplace; GO
d. 注册完成之后,用下面代码测试:
SELECT dbo.fn_SQLSigCLR(tsql_code) AS sig_sql, duration FROM dbo.Workload;
结果的SQL全被模式化,井号(#)替代所有的参数。
家家有老人
人人会变老
帮老就是帮未来的您