Điều này có thể thực hiện được khá dễ dàngdưới dạng Tập hợp do Người dùng Xác định SQLCLR (UDA). Một tập hợp hoạt động trên một tập hợp các hàng, vì vậy bạn sẽ có thể hoạt động trên tất cả các hàng hoặc chỉ là một tập hợp con, dựa trên một WHERE
điều kiện và tùy chọn GROUP BY
(nếu muốn hoạt động trên các bộ hàng riêng biệt).
NHƯNG, bạn có nên làm điều này hay không phụ thuộc vào những gì bạn đang dự định làm với kết quả. Nếu đây là một dự án một lần để thực hiện một số nghiên cứu sẽ không được lặp lại, thì có lẽ tốt nhất là chỉ cần tạo một Ứng dụng Console nhỏ để đọc theo hàng và xử lý theo đó.
Tuy nhiên, nếu bạn có một số nhu cầu sử dụng giá trị được trả về trong một quy trình tập trung vào cơ sở dữ liệu, thì SQLCLR sẽ ổn (giả sử bạn làm theo hai khuyến nghị được đề cập trong phần "Có thể sử dụng các thủ thuật sau để giảm mức sử dụng bộ nhớ của việc triển khai" trong phần Mã giả . Bạn sẽ chỉ cần tìm cách xử lý "sáng tạo" với tình huống có nhiều kết quả cho chuỗi con chung dài nhất (nghĩa là nếu 2 hoặc nhiều chuỗi con chung buộc cho "vị trí đầu tiên"). ví dụ từ trang Wikipedia (hiển thị 2 kết quả khớp cho 2 chuỗi):
ABAB
BABA
Trả về cả hai:
Có lẽ trả về một tài liệu XML trùng khớp vì đó là phân tích cú pháp và có thể chứa bất kỳ chuỗi nào (khi được thoát đúng cách).
CẬP NHẬT (cập nhật và cập nhật lại)
Mã nguồn .NET C # cho điều này có thể được tìm thấy trên Pastebin.com tại:
SQLCLR UDA cho chuỗi con chung dài nhất - Mã nguồn
Và đối với bất kỳ ai muốn chơi với UDA này mà không biên dịch nó, có thể tìm thấy tập lệnh T-SQL cài đặt (không có DLL bên ngoài) trên Pastebin.com tại:
SQLCLR UDA cho chuỗi con chung dài nhất - Trình cài đặt
UDA nên khá hiệu quả về bộ nhớ vì nó chỉ lưu trữ các chuỗi con khớp với chuỗi hiện tại và tất cả các chuỗi gặp phải trước đó. Khi một hàng mới gọi UDA, mọi chuỗi con không tìm thấy trong chuỗi mới sẽ bị xóa khỏi bộ sưu tập.
Nó cũng hiệu quả với CPU ở chỗ, nếu tại bất kỳ thời điểm nào, số lượng các chuỗi con "phổ biến" bằng 0, nó sẽ đặt một cờ cho biết rằng không có chuỗi con nào có thể xảy ra và tất cả các lệnh thực thi trong tương lai sẽ thoát ra khi được gọi. Điều này xảy ra ngay lập tức nếu gặp một chuỗi trống. Trong bất kỳ trường hợp nào, một tài liệu XML trống (chỉ phần tử gốc) được trả về. Ý nghĩa của tài liệu trống tương đương với một chuỗi rỗng, vì điều duy nhất các chuỗi đầu vào có điểm chung là chúng không phải là NULL
chuỗi.
NULL
, theo cách giải thích của tôi, bị bỏ qua và không cho biết không có kết quả khớp nào giống như một chuỗi rỗng.
A NULL
được trả về trong hai trường hợp sau:
- Tất cả các đầu vào là
NULL
- Chỉ có một hàng duy nhất
NULL
trong tập hợp và do đó không có chuỗi nào khác để so sánh, do đó không có gì được coi là "phổ biến".
Tôi đã thêm một tham số đầu vào thứ hai để kiểm soát xem trả về chỉ là các chuỗi con chung dài nhất hoặc tất cả các chuỗi con chung. Trong trường hợp trả lại tất cả, một thuộc tính được thêm vào từng "mục" để cho biết liệu đó có phải là một trong các chuỗi con "dài nhất" hay không:
SELECT dbo.LongestCommonSubstring(tab.col, 0) AS [Test1a]
FROM (VALUES (N'ABAB'), (N'BABA')) tab(col);
Trả về:
<Items Merged="False">
<Item>ABA</Item>
<Item>BAB</Item>
</Items>
Và
SELECT dbo.LongestCommonSubstring(tab.col, 1) AS [Test1b]
FROM (VALUES (N'ABAB'), (N'BABA')) tab(col);
Trả về:
<Items Merged="False">
<Item IsLongest="True">ABA</Item>
<Item IsLongest="True">BAB</Item>
<Item IsLongest="False">AB</Item>
<Item IsLongest="False">BA</Item>
<Item IsLongest="False">A</Item>
<Item IsLongest="False">B</Item>
</Items>
Ngoài ra, các so sánh hiện là trường hợp InSensitive để khớp với Collation điển hình.
Dưới đây là 16 trường hợp kiểm tra nữa chỉ kiểm tra chức năng, không phải hiệu suất. Tôi sẽ đăng các bài kiểm tra bổ sung sau đó hoạt động trên nhiều hàng chuỗi dài hơn nhiều. Bây giờ tôi cố tình bỏ đi Kết hợp các Nhân vật và Nhân vật Bổ trợ, vì chúng hơi phức tạp hơn một chút.
SELECT dbo.LongestCommonSubstring(tab.col, 1) AS [Test2]
FROM (VALUES (N'ABAB'), (N'BABA'), (N'2BAB5')) tab(col);
-- <Items><Item>BAB</Item></Items>
SELECT dbo.LongestCommonSubstring(tab.col, 0) AS [Test3]
FROM (VALUES (N'ABAB'), (N'BABA'), (NULL), (N'2BAB5')) tab(col);
-- <Items><Item>BAB</Item></Items>
SELECT dbo.LongestCommonSubstring(tab.col, 0) AS [Test4]
FROM (VALUES (NULL), (NULL), (NULL)) tab(col);
-- NULL
SELECT dbo.LongestCommonSubstring(tab.col, 0) AS [Test5]
FROM (VALUES (N'ABAB'), (N'BABA'), (N''), (N'2BAB5')) tab(col);
-- <Items />
SELECT dbo.LongestCommonSubstring(tab.col, 0) AS [Test6]
FROM (VALUES (N'ABAB'), (N'BABA'), (N'L'), (N'2BAB5')) tab(col);
-- <Items />
SELECT dbo.LongestCommonSubstring(tab.col, 0) AS [Test7]
FROM (VALUES (N'ABAB')) tab(col);
-- NULL
SELECT dbo.LongestCommonSubstring(tab.col, 0) AS [Test8a-DuplicatesAcross2Rows]
FROM (VALUES (N'ABAB'), (N'ABAB')) tab(col);
-- <Items><Item>ABAB</Item></Items>
SELECT dbo.LongestCommonSubstring(tab.col, 0) AS [Test8b-DuplicatesAcross3Rows]
FROM (VALUES (N'ABAB'), (N'ABAB'), (N'ABAB')) tab(col);
-- <Items><Item>ABAB</Item></Items>
SELECT dbo.LongestCommonSubstring(tab.col, 0) AS [Test8c-DuplicatesAcross4Rows]
FROM (VALUES (N'ABAB'), (N'ABAB'), (N'ABAB'), (N'ABAB')) tab(col);
-- <Items Merged="False"><Item>ABAB</Item></Items>
SELECT dbo.LongestCommonSubstring(tab.col, 0) AS [Test9-DuplicatesWithinOneString]
FROM (VALUES (N'ABAB'), (N'zABABh2348923ABABf')) tab(col);
-- <Items Merged="False"><Item>ABAB</Item></Items>
SELECT dbo.LongestCommonSubstring(tab.col, 0) AS [Test10-XmlEncodableCharacters]
FROM (VALUES (N'ABA&B'), (N'zABA&Bh2348923ABA&Bf')) tab(col);
-- <Items Merged="False"><Item>ABA&B</Item></Items>
SELECT dbo.LongestCommonSubstring(tab.col, 0) AS [Test11a-FinalMatchesShorterThanInitialSet]
FROM (VALUES (N'ABCDq1234g'), (N'1234qABCDg'), (N'uiyuiuy1234qBCDg'), (N'512tttrtrtBCDdfdfgdg')) tab(col);
-- <Items Merged="False"><Item>BCD</Item></Items>
SELECT dbo.LongestCommonSubstring(tab.col, 0) AS [Test11b-FinalMatchesShorterThanInitialSet]
FROM (VALUES (N'BCDq1234g'), (N'1234qABCDg'), (N'uiyuiuy1234qBCDg'), (N'512tttrtrtBCDdfdfgdg')) tab(col);
-- <Items Merged="False"><Item>BCD</Item></Items>
SELECT dbo.LongestCommonSubstring(tab.col, 0) AS [Test11c-FinalMatchesShorterThanInitialSet]
FROM (VALUES (N'ABCDq1234g'), (N'1234qABCDg'), (N'uiyuiuy1234qBCDg'), (N'5123tttrtrtBCDdfdfgdg')) tab(col);
-- <Items Merged="False"><Item>BCD</Item><Item>123</Item></Items>
SELECT dbo.LongestCommonSubstring(tab.col, 0) AS [Test11d-FinalMatchesShorterThanInitialSet]
FROM (VALUES (N'BCDq1234g'), (N'1234qABCDg'), (N'uiyuiuy1234qBCDg'), (N'5123tttrtrtBCDdfdfgdg')) tab(col);
-- <Items Merged="False"><Item>BCD</Item><Item>123</Item></Items>
SELECT dbo.LongestCommonSubstring(tab.col, 0) AS [Test11e-FinalMatchesShorterThanInitialSet]
FROM (VALUES (N'BCDq1234g'), (N'1234qABCDg'), (N'uiyuiuy1234qBCDg'), (N'123tttrtrtBCDdfdfgdg')) tab(col);
-- <Items Merged="False"><Item>BCD</Item><Item>123</Item></Items>
SELECT dbo.LongestCommonSubstring(tab.col, 1) AS [Test12-CaseInSensivity]
FROM (VALUES (N'AbAB'), (N'BAbA')) tab(col);
/*
<Items Merged="False">
<Item IsLongest="True">AbA</Item>
<Item IsLongest="True">bAB</Item>
<Item IsLongest="False">Ab</Item>
<Item IsLongest="False">bA</Item>
<Item IsLongest="False">A</Item>
<Item IsLongest="False">b</Item>
</Items>
*/
Cập nhật cuối cùng (hy vọng)
Tôi đã thực hiện một vài thay đổi nhỏ đối với mã kiểm tra được cung cấp trong câu trả lời của @ MisterMagoo để nó: a) trả lại các mối quan hệ cho chuỗi con chung "dài nhất" và b) bao gồm ký tự cuối cùng trong mỗi chuỗi như một phần của tìm kiếm. Tôi cũng đã thay đổi, một chút, làm thế nào các hàng thử nghiệm ban đầu được tập hợp sao cho chỉ có hơn 1 triệu hàng và chạy lại các thử nghiệm. Kết quả là phiên bản T-SQL mất 1 phút và 13 giây trong khi UDA SQLCLR chỉ mất 12 giây (và thậm chí có tùy chọn trả về tất cả các chuỗi con chung, không chỉ dài nhất).
Sau đó, tôi đã sửa đổi dữ liệu thử nghiệm để bao gồm một chuỗi con chung khác có cùng độ dài với người chiến thắng hiện tại (7 ký tự), một chuỗi con ngắn hơn nhưng vẫn phổ biến gồm 4 ký tự và 3 ký tự ngẫu nhiên. Điều này đã tăng kích thước chuỗi thử nghiệm tối đa từ 32 ký tự lên 46 ký tự. Chạy thử nghiệm lại (cùng mã), phiên bản T-SQL phải bị giết sau 23 phút và chỉ thử nghiệm 3 độ dài đầu tiên: 27, 26 và 25. UDA SQLCLR quay trở lại sau khoảng 1 phút 10 giây.
Đã đến lúc ăn mừng? SQLCLR đã lưu trong ngày chưa? Giữ lấy..
Trong một linh cảm, tôi quyết định xem liệu tối ưu hóa mà tôi đã thêm vào phiên bản SQLCLR có giúp ích cho T-SQL hay không, cụ thể là:
Lấy hai chuỗi ngắn nhất (ngắn nhất sẽ có số lượng chuỗi con ít nhất có thể và dù sao cũng sẽ là chuỗi dài nhất có thể).
Trích xuất tất cả các chuỗi con phổ biến có thể có từ hai chuỗi ngắn (bất kỳ chuỗi con nào là "chung" trên tất cả các hàng nhất thiết phải có trong tập hợp xuất phát từ chỉ hai chuỗi này và không có chuỗi con mới nào từ các hàng khác có thể được giới thiệu vì chúng sẽ không được giới thiệu "chung").
Bắt đầu với chuỗi con chung dài nhất, kiểm tra xem nó có được tìm thấy trong tất cả các hàng không. Tôi đã sử dụng IF (NOT EXISTS (WHERE CHARINDEX(substring, test_row) > 0))
vì EXISTS
mệnh đề sẽ thoát trên hàng đầu tiên trả về 0 (có nghĩa là chuỗi con không có) và do đó không cần phải kiểm tra tất cả các hàng. Phần này được thực hiện với một CURSOR
(shh, đừng nói cho ai biết) vì nó cho phép bắt đầu ở đầu danh sách và chỉ cần chọn từng hàng mới mà không cần quét lại danh sách mỗi lần để tìm mục tiếp theo.
Khi tìm thấy chuỗi con đầu tiên, hãy lưu độ dài của nó trong một biến và lưu chính chuỗi con đó vào một biến bảng.
Tiếp tục kiểm tra, nhưng chỉ đối với các chuỗi có cùng độ dài với chuỗi con chung đầu tiên được tìm thấy. ngay khi độ dài của chuỗi con tiếp theo cần tìm nhỏ hơn độ dài của chuỗi con đầu tiên để khớp, thoát khỏi vòng lặp và dọn sạch con trỏ.
Tất cả những thứ con trỏ này phải làm cho nó chậm một cách đau đớn, phải không? Chà, hãy nhớ rằng phiên bản T-SQL trước đó sẽ mất vài giờ để hoàn thành và UDA SQLCLR mất 1 phút 10 giây, bạn có thể đoán phiên bản cập nhật này sẽ mất gì? Một vài phút? 10 phút? Hơn? Rốt cuộc, chỉ cần nhắc đến "con trỏ" là một cú đánh tự động trong 5 phút, phải không? Thời gian thực tế cho phiên bản sửa đổi:
22 giây !!!
Tất nhiên, điều đó phần lớn là do các chuỗi con nằm ở phía dài hơn (so với kích thước của chuỗi) và do đó vòng lặp có thể thoát ra sớm hơn nếu chuỗi con chung dài nhất chỉ dài 3 hoặc 4 ký tự (nghĩa là nó có ít để kiểm tra, nhưng đó vẫn là một trường hợp hợp lệ và không gian lận). Ngoài ra, một lợi ích khi làm việc trong T-SQL là bạn có thể kiểm tra toàn bộ tập hợp theo một giá trị duy nhất, trong khi SQLCLR chỉ có hàng hiện tại và không thể thấy toàn bộ tập hợp, do đó SQLCLR không thể đoản mạch khi tìm chuỗi con chung dài nhất (bởi vì nó sẽ không biết cái gì là "phổ biến" ở nơi đầu tiên cho đến khi nó được thực thi trên tất cả các hàng).
Một thay đổi cuối cùng là cho phép phiên bản T-SQL mới trả về tất cả các chuỗi con "chung", không chỉ các chuỗi con dài nhất, trong khi chỉ ra phiên bản nào dài nhất trong cột kiểu dữ liệu thứ hai BIT
. Khi trả về tất cả các chuỗi con, phản ánh chức năng của UDA SQLCLR (ngay cả khi UDA chỉ trả về các chuỗi con chung dài nhất, nó vẫn có danh sách đầy đủ tất cả các chuỗi con được lưu trữ vì nó, một lần nữa, không có khả năng ngắn mạch), Phiên bản T-SQL trả về sau 2 phút 41 giây.
Vì vậy, có những điều kiện là T-SQL có thể nhanh hơn, thậm chí ở mức 1,2 triệu hàng. Nhưng hiệu suất ổn định hơn nhiều đối với phiên bản SQLCLR và chắc chắn nhanh hơn khi bạn muốn tất cả các chuỗi con phổ biến.
Kịch bản thử nghiệm, chứa cả 3 thử nghiệm cùng với kết quả của chúng, có thể được tìm thấy trên Pastebin tại:
SQLCLR UDA cho chuỗi con chung dài nhất - Kiểm tra
PS Mặc dù thử nghiệm đã được thực hiện trên 1,2 triệu hàng, chuỗi dài nhất được thử nghiệm là 46 ký tự. Tôi không chắc hiệu suất của một trong hai cách tiếp cận sẽ bị ảnh hưởng như thế nào khi hoạt động trên các chuỗi dài hơn nhiều. Việc kiểm tra đó sẽ phải đợi cho đến khi có thời gian để thực hiện ;-).