Das kann gemacht werden ziemlich leichtals SQLCLR User-Defined Aggregate (UDA). Ein Aggregat wird über eine Reihe von Zeilen ausgeführt, sodass Sie über alle Zeilen oder nur eine Teilmenge hinweg arbeiten können, basierend auf einer WHERE
Bedingung und optional GROUP BY
(wenn Sie über mehrere Sätze von Zeilen arbeiten möchten).
ABER, ob Sie dies tun sollten oder nicht, hängt davon ab, was Sie mit dem Ergebnis vorhaben. Wenn dies ein einmaliges Projekt ist, um Nachforschungen anzustellen, die nicht wiederholt werden, ist es wahrscheinlich am besten, einfach eine kleine Konsolen-App zu erstellen, um die Zeilen einzulesen und entsprechend zu verarbeiten.
Wenn Sie jedoch den zurückgegebenen Wert in einem datenbankzentrierten Prozess verwenden müssen, sollte SQLCLR in Ordnung sein (vorausgesetzt, Sie befolgen die beiden unter "Die folgenden Tricks können verwendet werden, um die Speichernutzung einer Implementierung zu reduzieren"). im Pseudocode- Abschnitt. Sie müssen nur einen "kreativen" Weg finden, um mit der Situation umzugehen, dass mehrere Ergebnisse für den längsten gemeinsamen Teilstring erzielt werden (dh wenn zwei oder mehr gemeinsame Teilstrings für den "ersten Platz" gelten) das Beispiel von der Wikipedia-Seite (die 2 Übereinstimmungen für die 2 Zeichenfolgen zeigt):
ABAB
BABA
Gibt beide zurück:
Möglicherweise wird ein XML-Dokument mit Übereinstimmungen zurückgegeben, da dieses analysierbar ist und eine beliebige Zeichenfolge enthalten kann (wenn es ordnungsgemäß maskiert ist).
UPDATE (aktualisiert und erneut aktualisiert)
Den .NET C # -Quellcode hierfür finden Sie auf Pastebin.com unter:
SQLCLR UDA für den längsten gemeinsamen Teilstring - Quellcode
Und für alle, die mit dieser UDA spielen möchten, ohne sie zu kompilieren, finden Sie auf Pastebin.com ein Installations-T-SQL-Skript (keine externe DLL) unter:
SQLCLR UDA für den längsten gemeinsamen Teilstring - Installer
Der UDA sollte ziemlich speichereffizient sein, da er nur Teilzeichenfolgen speichert, die mit der aktuellen Zeichenfolge und allen zuvor angetroffenen Zeichenfolgen übereinstimmen. Wenn eine neue Zeile die UDA aufruft, werden alle Teilzeichenfolgen, die nicht in der neuen Zeichenfolge gefunden werden, aus der Auflistung entfernt.
Es ist auch insofern CPU-effizient, als wenn zu irgendeinem Zeitpunkt die Anzahl der "gemeinsamen" Teilzeichenfolgen auf Null geht, ein Flag gesetzt wird, das angibt, dass überhaupt keine Teilzeichenfolgen möglich sind, und alle zukünftigen Ausführungen kurz gekündigt werden, um sie beim Aufruf einfach zu beenden. Dies geschieht sofort, wenn eine leere Zeichenfolge gefunden wird. In jedem dieser Fälle wird ein leeres XML-Dokument (dh nur das Stammelement) zurückgegeben. Die Bedeutung des leeren Dokuments entspricht einer leeren Zeichenfolge, da die Eingabezeichenfolgen nur gemeinsam haben, dass sie keine NULL
Zeichenfolgen sind.
NULL
wird in meiner Interpretation ignoriert und zeigt keine möglichen Übereinstimmungen an, wie dies bei einer leeren Zeichenfolge der Fall ist.
A NULL
wird in den folgenden zwei Fällen zurückgegeben:
- Alle Eingänge sind
NULL
- Es war nur eine einzige Nicht-
NULL
Zeile in der Menge und daher gab es keine andere Zeichenfolge, mit der verglichen werden konnte, daher nichts, was als "häufig" angesehen werden könnte.
Ich habe einen zweiten Eingabeparameter hinzugefügt, der steuert, ob die Rückgabe nur die längste gemeinsame Teilzeichenfolge oder alle gemeinsamen Teilzeichenfolgen ist. Bei der Rückgabe von all wird jedem "Element" ein Attribut hinzugefügt, um anzugeben, ob es sich um eine der "längsten" Teilzeichenfolgen handelt oder nicht:
SELECT dbo.LongestCommonSubstring(tab.col, 0) AS [Test1a]
FROM (VALUES (N'ABAB'), (N'BABA')) tab(col);
Kehrt zurück:
<Items Merged="False">
<Item>ABA</Item>
<Item>BAB</Item>
</Items>
Und
SELECT dbo.LongestCommonSubstring(tab.col, 1) AS [Test1b]
FROM (VALUES (N'ABAB'), (N'BABA')) tab(col);
Kehrt zurück:
<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>
Außerdem werden bei den Vergleichen jetzt Groß- und Kleinschreibung berücksichtigt, um der typischen Sortierung zu entsprechen.
Im Folgenden finden Sie 16 weitere Testfälle, die nur die Funktionalität und nicht die Leistung überprüfen. Ich werde später weitere Tests veröffentlichen, die über viele Zeilen mit viel längeren Zeichenfolgen ausgeführt werden. Ich habe vorerst absichtlich auf das Kombinieren von Charakteren und Zusatzcharakteren verzichtet, da diese etwas komplizierter sind.
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>
*/
Letztes Update (hoffentlich)
Ich habe ein paar geringfügige Änderungen am Testcode vorgenommen, der in der Antwort von @ MisterMagoo enthalten ist , damit: a) Bindungen für die "längste" gemeinsame Teilzeichenfolge zurückgegeben werden und b) das letzte Zeichen in jeder Zeichenfolge als Teil der Suche enthalten ist. Ich habe auch geringfügig geändert, wie die anfänglichen Testzeilen gesammelt werden, sodass etwas mehr als 1 Million Zeilen vorhanden sind, und die Tests erneut ausgeführt. Das Ergebnis war, dass die T-SQL-Version 1 Minute und 13 Sekunden dauerte, während die SQLCLR-UDA nur 12 Sekunden dauerte (und sogar die Option hat, alle gängigen Teilzeichenfolgen zurückzugeben, nicht nur die längsten).
Ich habe dann die Testdaten so geändert, dass sie einen weiteren gemeinsamen Teilstring mit der gleichen Länge wie der aktuelle Gewinner (7 Zeichen), einen kürzeren, aber immer noch gemeinsamen Teilstring mit 4 Zeichen und 3 zufälligen Zeichen enthalten. Dies erhöhte die maximale Größe der Testzeichenfolge von 32 Zeichen auf 46 Zeichen. Beim erneuten Ausführen des Tests (gleicher Code) musste die T-SQL-Version nach 23 Minuten beendet werden und hatte nur die ersten drei Längen getestet: 27, 26 und 25. Die SQLCLR-UDA kehrte in etwa 1 Minute und 10 Sekunden zurück.
Ist es Zeit zu feiern? Hat SQLCLR den Tag gerettet? Warten Sie mal..
Aus einer Ahnung heraus entschied ich mich, ob die Optimierung, die ich der SQLCLR-Version hinzugefügt habe, dem T-SQL helfen würde, nämlich:
Schnappen Sie sich die kürzesten zwei Saiten (die kürzeste hätte die geringste Anzahl möglicher Teilzeichenfolgen und wäre sowieso die längste mögliche Übereinstimmung).
Extrahieren Sie alle möglichen gemeinsamen Teilzeichenfolgen aus den beiden kurzen Zeichenfolgen (alle Teilzeichenfolgen, die über alle Zeilen hinweg "gemeinsam" sind, müssen sich in der Menge befinden, die nur aus diesen beiden Zeichenfolgen abgeleitet ist, und es können keine neuen Teilzeichenfolgen aus anderen Zeilen eingeführt werden, wie dies nicht der Fall wäre "verbreitet").
Beginnen Sie mit dem längsten gemeinsamen Teilstring und prüfen Sie, ob er in allen Zeilen gefunden wird. Ich habe verwendet, IF (NOT EXISTS (WHERE CHARINDEX(substring, test_row) > 0))
weil die EXISTS
Klausel in der ersten Zeile beendet wird, die eine 0 zurückgibt (was bedeutet, dass Teilzeichenfolge nicht vorhanden ist) und daher nicht alle Zeilen testen muss. Dieser Teil wird mit einem CURSOR
(shh, sag es niemandem) abgeschlossen, da es ermöglicht, am Anfang der Liste zu beginnen und einfach jede neue Zeile auszuwählen, ohne die Liste jedes Mal neu scannen zu müssen, um den nächsten Eintrag zu finden.
Sobald der erste Teilstring gefunden wurde, speichern Sie seine Länge in einer Variablen und speichern Sie den Teilstring selbst in einer Tabellenvariablen.
Testen Sie weiter, jedoch nur für Zeichenfolgen, die dieselbe Länge wie der erste häufig gefundene Teilstring haben. Sobald die Länge des nächsten zu suchenden Teilstrings geringer ist als die Länge des ersten zu übereinstimmenden Teilstrings, verlassen Sie die Schleife und bereinigen Sie den Cursor.
All dieses Cursor-Zeug muss es schmerzhaft langsam machen, oder? Wenn man bedenkt, dass die Fertigstellung der vorherigen T-SQL-Version mehrere Stunden gedauert hätte und die SQLCLR-UDA 1 Minute und 10 Sekunden gedauert hätte, könnte man vermuten, dass diese aktualisierte Version dauern würde. Was? Ein paar Minuten? 10 Minuten? Mehr? Nur "Cursor" zu erwähnen, ist doch ein automatischer 5-Minuten-Treffer, oder? Die tatsächliche Zeit für die geänderte Version:
22 Sekunden !!!
Dies liegt natürlich hauptsächlich daran, dass sich die Teilzeichenfolgen auf der längeren Seite befinden (im Vergleich zur Größe der Zeichenfolgen), sodass die Schleife früher beendet werden konnte, als wenn die längste gemeinsame Teilzeichenfolge nur 3 oder 4 Zeichen lang war (dh hatte) weniger zu testen, aber das ist immer noch ein gültiger Fall und betrügt nicht). Ein Vorteil der Arbeit in T-SQL besteht auch darin, dass Sie den gesamten Satz anhand eines einzelnen Werts testen können, während SQLCLR nur die aktuelle Zeile enthält und nicht den gesamten Satz sehen kann. Daher kann der SQLCLR beim Auffinden des längsten gemeinsamen Teilstrings keinen Kurzschluss verursachen (weil es nicht weiß, was "allgemein" ist, bis es über alle Zeilen ausgeführt wurde).
Eine letzte Änderung bestand darin, der neuen T-SQL-Version zu ermöglichen, alle "allgemeinen" Teilzeichenfolgen zurückzugeben, nicht nur die längsten, und gleichzeitig anzugeben, welche in einer zweiten Spalte des Datentyps die längsten waren BIT
. Bei der Rückgabe aller Teilzeichenfolgen, die die Funktionalität der SQLCLR-UDA spiegeln (selbst wenn die UDA nur die längsten gemeinsamen Teilzeichenfolgen zurückgibt, wird immer noch die vollständige Liste aller gemeinsamen Teilzeichenfolgen gespeichert, da sie wiederum nicht kurzschließen kann) Die T-SQL-Version kehrt in 2 Minuten und 41 Sekunden zurück.
Es gibt also Bedingungen, unter denen T-SQL selbst bei 1,2 Millionen Zeilen schneller sein kann. Die Leistung ist jedoch für die SQLCLR-Version weitaus stabiler und definitiv schneller, wenn Sie alle gängigen Teilzeichenfolgen verwenden möchten.
Das Testskript, das alle drei Tests zusammen mit ihren Ergebnissen enthält, finden Sie auf Pastebin unter:
SQLCLR UDA für die längste gemeinsame Teilzeichenfolge - Prüfung
PS Obwohl der Test über 1,2 Millionen Zeilen durchgeführt wurde, war die längste getestete Zeichenfolge 46 Zeichen. Ich bin mir nicht sicher, wie sich die Leistung beider Ansätze auf die Leistung beider Ansätze auswirken würde. Dieser Test muss warten, bis es Zeit dafür gibt ;-).