Gibt es eine SQL Server-Implementierung des Longest Common Substring-Problems ? Eine Lösung, die alle Zeilen einer Spalte in SQL Server überprüft? Ich habe Lösungen gesehen, die zwei Zeichenfolgen als Eingabe verwenden, aber keine SQL Server-Lösung, die alle Zeilen einer Spalte in einer Tabelle betrachtet.
Ich habe ein paar Dinge ausprobiert, aber um ehrlich zu sein, denke ich, dass mir im Moment eine Lösung über den Kopf geht. Vorschläge sind daher willkommen.
Hier gibt es kein Problem der "realen Welt". Ich betrachte nur Programmierprobleme und wie sie mit SQL Server gelöst werden könnten.
sql-server
query-performance
substring
ManOnAMission
quelle
quelle
Antworten:
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 einerWHERE
Bedingung und optionalGROUP 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):
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:NULL
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:
Kehrt zurück:
Und
Kehrt zurück:
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.
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 dieEXISTS
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 einemCURSOR
(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 ;-).
quelle
Solomon hat wahrscheinlich Recht, aber bis eine CLR-Lösung angezeigt wird, ist hier eine T-SQL-Version zum Spielen.
quelle
AKTUALISIERT am 20171127 um 23:58 Uhr CST, UM UNIGRAMME ZU BEHANDELN
Ich weiß, dass ich hier etwas spät dran bin (um mehr als ein Jahr), aber meine neueste Funktion für die längste gemeinsame Teilzeichenfolge von t-sql ist mehrere tausend Mal schneller als alles, was ich irgendwo gesehen habe, einschließlich der oben genannten CLR (ich habe sie gerade getestet) ).
Es ist eine Semi-Brute-Force-Technik, auf die ich gekommen bin:
Löst die kürzere der beiden Zeichenfolgen auf und durchsucht die längere Teilzeichenfolge nach einer Übereinstimmung.
Akzeptiert einen dritten Parameter namens "Fenster" (es ist eher ein NTile-Parameter, aber ich verwende den Begriff "Fenster", weil nur wenige Leute Ntile verstehen.) Dies ist die geheime Sauce, die diesen bösen Hund so schnell macht
Die Routine verwendet dann eine reine Brute Force, um festzustellen, ob die Teilzeichenfolgen mit einer Größe von 20 oder weniger in der kurzen Zeichenfolge auch in der längeren Zeichenfolge vorhanden sind. Verwenden einer Tally-Tabelle - Der Brute-Force-Ansatz für Teilzeichenfolgen mit einer Größe von 20 oder weniger erfolgt sofort (0 ms).
Danach durchsucht es die längere Zeichenfolge nach Teilzeichenfolgen, die in der kürzeren Zeichenfolge vorhanden sind, deren Größe durch @window gleichmäßig teilbar ist. Wenn beispielsweise @window = 100 ist, wird nach übereinstimmenden Teilzeichenfolgen mit einer Länge von 100, 200 ... bis zur Länge der längeren Zeichenfolge gesucht (z. B. wenn die längere Zeichenfolge 515 Zeichen lang ist, wird nach Übereinstimmungen gesucht Teilzeichenfolgen mit einer Länge von 100, 200, 300, 400 und 500 Zeichen.
Sobald wir herausgefunden haben, in welchem "Fenster" die längste Teilzeichenfolge lebt, verwende ich eine Zähltabelle und einen Trick "Lücken und Inseln auf Zeichenfolgen", den ich von Chris Morris ( hier besprochen ) gelernt habe , um jede Zeichenfolge auf übereinstimmende Teilzeichenfolgen zu vergleichen.
TOP 1 mit Bindungen wird verwendet, um den längsten Teilstring zu identifizieren.
Die Funktionen):
Hier ist ein Beispiel dafür, wie die Funktion die längste gemeinsame Teilzeichenfolge zwischen zwei Zeichenfolgen mit einer Länge von etwa 7700 Zeichen berechnet. Es gibt die richtige Antwort in 16 Millisekunden zurück.
Dies ist Teil eines Artikels, an dem ich arbeite. Da kommt noch mehr!
quelle
NVARCHAR
solange dies der Fall istVARCHAR
, und d) dies funktioniert nicht über eine Reihe von Zeilen , was hier die Voraussetzung ist. Können Sie es bitte satzbasiert machen und Punkt b reparieren ?