PDA

View Full Version : Thẩm định email bất hợp lệ



dq_ninh
02-06-2010, 11:53
Hôm nay, tôi muốn hướng dẫn các bạn cách nạp một hàm CLR, được viết bằng C#, vào SQL 2005/2008 (và những phiên bản sau này) server.

Tôi có cơ hội được thực hiện một dự án (project) nho nhỏ là gửi 55 triệu emails đến “khách hàng” có những email thuộc về hơn 170 ngàn ISP (nhà cung cấp email) khác nhau.

Việc phải làm trước khi gửi 55 triệu emails là thẩm định khuôn thức của 55 triệu emails để loại bỏ những email bất hợp lệ. Có hàng ngàn lỗi khác nhau có thể xảy ra trong 55 triệu emails. Thay vì xnguyen.yahoo.com, vì một lý do nào đó, có thể đã được lưu giữ trong CSDL là x?nguyen.yahoo.com, hoặc xnguyen.yaho.com, hoặc xnguyen.yahoo.co. Do đó, chuyện phân lọc những email có khuôn thức sai là chuyện bắt buộc phải thực hiện đầu tiên.

Người tiền nhiệm của tôi có một stored procedure dùng để thẩm định và phân lọc email. Nhưng stored procedure này không những rắc rối, mà còn tốn rất nhiều thời gian vận hành để có thể phân lọc 55 triệu emails.

Với một chút xíu kinh nghiệm lập trình nhỏ nhoi trong quá trình viết những phần mềm .NET, tôi biết rằng với một chút thời gian nho nhỏ, tôi có thể phát triển một hàm C# dùng lớp RegEx để thẩm định email. Mới google trên mạng chưa đầy 5 phút, tôi đã tìm ra được hàm C# sau đây, chỉ có 2 giòng, nhưng ít nhất, khỏi mất công viết:



using System.Data.SqlTypes;
using System.Text.RegularExpressions;
using Microsoft.SqlServer.Server;

public static partial class UserDefinedFunctions
{
public static readonly RegexOptions Options =
RegexOptions.IgnorePatternWhitespace |
RegexOptions.Compiled | RegexOptions.Singleline;

[SqlFunction]
public static SqlBoolean RegexMatch( SqlChars input, SqlString pattern )
{
Regex regex = new Regex( pattern.Value, Options );
return regex.IsMatch( new string( input.Value ) );
}
}


Sau khi có hàm trên, chuyện kế tiếp là phải dung VS-2008 để tạo một project thuộc loại class library. Tôi đặt tên cho project là SqlFunctions. Khi tạo project, tôi chọn .NET 2.0, vì biết rằng SQL-2005 đã được phát hành cùng thời gian với .NET 2.0

Sau khi project SqlFunctions đã được thiết lập. Tôi đổi tên của tập tin class1.cs thành RegexMatch.cs cho phù hợp với tên của hàm trên, và sao chép nguyên văn hàm trên vào trong tập tin này.

Kế tiếp là dung VS-2008 để “build” project, tạo ra tập tin SqlFunctions.dll.

Bước kế tiếp là đưa tập tin SqlFunctions.dll vào SQL-2005/SQL-2008 server. Cách đưa vào như sau. Các bạn cần phải đăng nhập vào SQL server với System Admin:

Bước thứ 1 – cài đặt SQL Server để cho phép xử dụng những hàm CLR:

sp_configure 'clr enabled', 1
RECONFIGURE WITH OVERRIDE

Bước thứ 2 – đưa thông tin về SqlFunctions.dll vào master database:

CREATE ASSEMBLY SqlFunctions FROM 'F:\GCG\wndTaskManager\SqlFunctions\bin\Release\Sq lfunctions.dll' WITH PERMISSION_SET = SAFE;

Bước thứ 3 – ánh xạ hàm CLR vào một hàm SQL cục bộ:

CREATE Function RegexMatch(@Input NVARCHAR(512),@Pattern NVARCHAR(2048))
RETURNS BIT EXTERNAL NAME SqlFunctions.UserDefinedFunctions.RegexMatch

Thế là xong. Dưới đây là cách dung hàm RegexMatch:

Emails hợp khuôn thức hồi báo giá trị = 1

SELECT dbo.RegexMatch('BillClinton@gardencitygroup.com', '^[_a-z0-9-]+(\.[_a-z0-9-]+)*@[a-z0-9-]+(\.[a-z0-9-]+)*(\.[a-z]{2,4})$')
SELECT dbo.RegexMatch('Bill.Clinton@gardencitygroup.net', '^[_a-z0-9-]+(\.[_a-z0-9-]+)*@[a-z0-9-]+(\.[a-z0-9-]+)*(\.[a-z]{2,4})$')
SELECT dbo.RegexMatch('Bill.Clinton@gardencitygroup.com.f r', '^[_a-z0-9-]+(\.[_a-z0-9-]+)*@[a-z0-9-]+(\.[a-z0-9-]+)*(\.[a-z]{2,4})$')

Emails sai khuôn thức hồi báo giá trị = 0

SELECT dbo.RegexMatch('BillClinton@gardencitygroup.com ', '^[_a-z0-9-]+(\.[_a-z0-9-]+)*@[a-z0-9-]+(\.[a-z0-9-]+)*(\.[a-z]{2,4})$')

SELECT dbo.RegexMatch('Bill/Clinton@gardencitygroup.com', '^[_a-z0-9-]+(\.[_a-z0-9-]+)*@[a-z0-9-]+(\.[a-z0-9-]+)*(\.[a-z]{2,4})$')

Áp dụng hàm SQL trên vào bảng như nhau:

Sau cùng, đây là lệnh SELECT để phân lọc những bản ghi có email bất hợp lệ (giản dị, nhưng có tốc độ xử lý tối ưu):

SELECT * FROM BANG_EMAIL WHERE 0 = dbo.RegexMatch(email_address, '^[_a-z0-9-]+(\.[_a-z0-9-]+)*@[a-z0-9-]+(\.[a-z0-9-]+)*(\.[a-z]{2,4})$')

Ngoài ra, các bạn còn có thể dùng hàm RegexMatch() trên với những biểu thức khác để thẩm định những dữ liệu khác như số điện thoại, chứng minh thư nhân dân, số bằng lái xe, vân vân và vân vân.

Nói tóm lại, chỉ cần một chút xíu tư duy, là có thể tìm thấy một phương pháp tốt hơn để giải quyết một vấn đề.

Red Devilic 1
15-06-2010, 16:44
Bài viết của bác rất bổ ích.
Mình còn 1 băn khoăn là định dạng như lớp RegEx của bác rõ ràng là rất mạnh, vậy sao SQL Server lại không hỗ trợ trực tiếp mà phải thông qua CLR như vậy ?
Dùng LIKE trong SQL Server thì gò bó và phải xử lý phức tạp, không đơn giản như phương án ở trên.

bachnga
16-06-2010, 08:09
Cảm ơn bác dq_ninh (http://ddth.com/member.php?u=372693)
Mong bác tiếp tục chia sẻ kinh nghiệm của bác nhiều hơn nữa
(Copy bài của bác bỏ vào bộ sưu tập đã)

dq_ninh
16-06-2010, 11:32
Bài viết của bác rất bổ ích.
Mình còn 1 băn khoăn là định dạng như lớp RegEx của bác rõ ràng là rất mạnh, vậy sao SQL Server lại không hỗ trợ trực tiếp mà phải thông qua CLR như vậy ?
Dùng LIKE trong SQL Server thì gò bó và phải xử lý phức tạp, không đơn giản như phương án ở trên.

Bạn nhận định đúng lắm. Tôi cũng không hiểu tại sao MS không chịu triển khai cái hàm Regex này cho SQL server của họ. Một trong những hàm của SQL là PatIndex() cũng có thể dùng để tìm ra những phương thức giản dị. Tuy nhiên, nếu so với RegexMatch(), thì PatIndex không thấm vào đâu.

Nhưng không chừng, phiên bản mới của MS-SQL 201n sẽ có những hàm cho RegEx cũng nên.

Ngoài ra, sau khi triển khai hàm RegexMatch(), tôi dùng nó để thử nghiệm với một bảng tương đối nhỏ, có hơn 7 triệu bản ghi. Trong 7++ triệu bản ghi này, có hơn 8 ngàn email bất hợp lệ. Khi chạy thử, mất hết hơn 9 tiếng đồng hồ. Đây là một tốc độ hoàn toàn phi lý và không thể chấp nhận được.

Sau khi sưu tầm và tham khảo, tôi bỏ không dùng thuộc tính tùy chọn RegexOptions.Compiled, và dùng thuộc tính RegexOptions.IgnoreCase, như sau:

[Code]
public static partial class CLRCommon
{
public static readonly RegexOptions Options =
RegexOptions.IgnorePatternWhitespace |
RegexOptions.IgnoreCase | RegexOptions.Singleline;

[SqlFunction]
public static SqlBoolean fn_RegexMatch( SqlChars input, SqlString pattern )
{
Regex regex = new Regex( pattern.Value, Options );
return regex.IsMatch( new string( input.Value ) );
}
}
{/Code]


Kết quả ngoài sự tiên đoán (và mong muốn) của tôi. Với thay đổi trên, câu lệnh SQL đã tìm thấy hơn 8 ngàn email bất hợp lệ với một khoảng thời gian ngắn không ngờ: dưới 2 phút.

Từ hơn 9 tiếng đồng hồ, chỉ còn lại chưa tới 2 phút để tìm thấy 8++ ngàn email bất hợp lệ trong hơn 7 triệu bản ghi, tôi nghĩ rằng không còn gì mong muốn hơn.