Wir müssen einige Berichte über Werte erstellen, bei denen es sich normalerweise um gemischte Zeichenfolgen aus Zahlen und Buchstaben handelt, die "natürlich" sortiert werden müssen. Dinge wie zB "P7B18" oder "P12B3". @Die Zeichenfolgen bestehen hauptsächlich aus Buchstabenfolgen und abwechselnden Zahlen. Die Anzahl dieser Segmente und die Länge der einzelnen Segmente können jedoch variieren.
Wir möchten, dass die numerischen Teile davon in numerischer Reihenfolge sortiert werden. Wenn ich diese Zeichenfolgenwerte nur direkt mit ORDER BY
behandle, wird "P12B3" natürlich vor "P7B18" stehen, da "P1" früher als "P7" ist, aber ich möchte das Gegenteil, da "P7" natürlich vorausgeht "P12".
Ich möchte auch in der Lage sein, Bereichsvergleiche durchzuführen, z @bin < 'P13S6'
. B. oder solche. Ich muss nicht mit Gleitkomma- oder negativen Zahlen umgehen. Dies sind ausschließlich nicht negative Ganzzahlen, mit denen wir es zu tun haben. Die Zeichenfolgenlängen und die Anzahl der Segmente können möglicherweise beliebig sein, ohne feste Obergrenzen.
In unserem Fall ist das String-Gehäuse nicht wichtig. Wenn es jedoch eine Möglichkeit gibt, dies kollationsbewusst zu tun, finden andere dies möglicherweise nützlich. Das Hässlichste daran ist, dass ich in der WHERE
Klausel sowohl die Reihenfolge als auch die Bereichsfilterung durchführen kann .
Wenn ich dies in C # tun würde, wäre es eine ziemlich einfache Aufgabe: Führen Sie eine Analyse durch, um das Alpha von der Zahl zu trennen, implementieren Sie IComparable, und Sie sind im Grunde genommen fertig. SQL Server scheint natürlich keine ähnliche Funktionalität zu bieten, zumindest soweit mir bekannt ist.
Kennt jemand gute Tricks, um diese Arbeit zu machen? Gibt es eine wenig publizierte Möglichkeit, benutzerdefinierte CLR-Typen zu erstellen, die IComparable implementieren und sich wie erwartet verhalten? Ich bin auch nicht gegen dumme XML-Tricks (siehe auch: Listenverkettung), und ich habe auch CLR-Regex-Matching- / Extraktions- / Ersatz-Wrapper-Funktionen auf dem Server verfügbar.
BEARBEITEN: Als etwas detaillierteres Beispiel möchte ich, dass sich die Daten so verhalten.
SELECT bin FROM bins ORDER BY bin
bin
--------------------
M7R16L
P8RF6JJ
P16B5
PR7S19
PR7S19L
S2F3
S12F0
Teilen Sie die Zeichenfolgen in Token aller Buchstaben oder Zahlen auf und sortieren Sie sie entweder alphabetisch oder numerisch, wobei die Token ganz links der wichtigste Sortierbegriff sind. Wie ich bereits erwähnt habe, ein Kinderspiel in .NET, wenn Sie IComparable implementieren, aber ich weiß nicht, wie (oder ob) Sie so etwas in SQL Server tun können. Es ist sicherlich nicht etwas, auf das ich in 10 oder so Jahren der Arbeit jemals gestoßen bin.
P7B12
könnte esP 07 B 12
dann werden (via ASCII)80 07 65 12
, so80076512
Antworten:
Möchten Sie ein vernünftiges und effizientes Mittel zum Sortieren von Zahlen in Zeichenfolgen als tatsächliche Zahlen? Stimmen Sie für meinen Microsoft Connect-Vorschlag ab: Unterstützen Sie "natürliche Sortierung" / DIGITSASNUMBERS als Sortieroption
Es gibt keine einfachen, eingebauten Mittel, um dies zu tun, aber hier ist eine Möglichkeit:
Normalisieren Sie die Zeichenfolgen, indem Sie sie in Segmente fester Länge umformatieren:
VARCHAR(50) COLLATE Latin1_General_100_BIN2
. Die maximale Länge von 50 muss möglicherweise basierend auf der maximalen Anzahl von Segmenten und ihren möglichen maximalen Längen angepasst werden.AFTER [or FOR] INSERT, UPDATE
Trigger ermöglichen, sodass Sie garantiert den Wert für alle Datensätze, auch für diese, richtig einstellen können Eingehen über Ad-hoc-Abfragen usw. Natürlich kann diese skalare UDF auch über SQLCLR verarbeitet werden, aber es müsste getestet werden, um festzustellen, welche tatsächlich effizienter ist. ** **.UPPER()
Funktion auf das Endergebnis aller Segmente an (sodass sie nur einmal und nicht pro Segment ausgeführt werden muss). Dies ermöglicht eine ordnungsgemäße Sortierung angesichts der binären Sortierung der Sortierspalte.AFTER INSERT, UPDATE
Trigger für die Tabelle, der die UDF aufruft, um die Sortierspalte festzulegen. Um die Leistung zu verbessern, verwenden Sie dieUPDATE()
Funktion, um festzustellen, ob diese Codespalte gerade in derSET
Klausel derUPDATE
Anweisung enthalten ist (einfachRETURN
wenn falsch), und verknüpfen Sie dann dieINSERTED
und dieDELETED
Pseudotabellen in der Codespalte, um nur Zeilen zu verarbeiten, deren Codewert geändert wurde . Stellen Sie sicher,COLLATE Latin1_General_100_BIN2
dass Sie diese JOIN-Bedingung angeben , um sicherzustellen, dass genau festgestellt wird, ob eine Änderung vorliegt.Beispiel:
Bei diesem Ansatz können Sie sortieren über:
Und Sie können die Bereichsfilterung durchführen über:
oder:
Sowohl der
ORDER BY
als auch derWHERE
Filter sollten die binäre Kollatierung verwenden, dieSortColumn
aufgrund der Kollatierungspräzedenz definiert ist .Gleichheitsvergleiche würden weiterhin für die ursprüngliche Wertespalte durchgeführt.
Andere Gedanken:
Verwenden Sie eine SQLCLR-UDT. Dies könnte funktionieren, obwohl es unklar ist, ob es im Vergleich zu dem oben beschriebenen Ansatz einen Nettogewinn darstellt.
Ja, bei einem SQLCLR-UDT können die Vergleichsoperatoren mit benutzerdefinierten Algorithmen überschrieben werden. Dies behandelt Situationen, in denen der Wert entweder mit einem anderen Wert verglichen wird, der bereits denselben benutzerdefinierten Typ hat, oder mit einem Wert, der implizit konvertiert werden muss. Dies sollte den Bereichsfilter in einem
WHERE
Zustand behandeln.In Bezug auf das Sortieren des UDT als regulären Spaltentyp (keine berechnete Spalte) ist dies nur möglich, wenn das UDT "Byte geordnet" ist. "Byte geordnet" bedeutet, dass die binäre Darstellung des UDT (die im UDT definiert werden kann) natürlich in der entsprechenden Reihenfolge sortiert wird. Unter der Annahme, dass die binäre Darstellung ähnlich wie der oben für die Spalte VARCHAR (50) beschriebene Ansatz behandelt wird, bei dem Segmente mit fester Länge aufgefüllt sind, würde dies qualifizieren. Wenn es nicht einfach wäre, sicherzustellen, dass die binäre Darstellung natürlich in der richtigen Reihenfolge angeordnet ist, können Sie eine Methode oder Eigenschaft des UDT verfügbar machen, die einen Wert ausgibt, der ordnungsgemäß geordnet ist, und dann eine
PERSISTED
berechnete Spalte darauf erstellen Methode oder Eigenschaft. Die Methode muss deterministisch sein und als gekennzeichnet seinIsDeterministic = true
.Vorteile dieses Ansatzes sind:
Parse
Methode des UDT nimmt denP7B18
Wert auf und konvertiert ihn, dann sollten Sie in der Lage sein, die Werte einfach auf natürliche Weise als einzufügenP7B18
. Und mit der im UDT festgelegten impliziten Konvertierungsmethode würde die WHERE-Bedingung auch die Verwendung von einfach P7B18` ermöglichen.Konsequenzen dieses Ansatzes sind:
PERSISTED
berechnete Spalte für eine Eigenschaft oder Methode des UDT verwenden, wird die von der Eigenschaft oder Methode zurückgegebene Darstellung angezeigt. Wenn Sie den ursprünglichenP7B18
Wert möchten , müssen Sie eine Methode oder Eigenschaft des UDT aufrufen, die codiert ist, um diese Darstellung zurückzugeben. Da Sie dieToString
Methode ohnehin überschreiben müssen, ist dies ein guter Kandidat dafür.Es ist unklar (zumindest für mich im Moment, da ich diesen Teil nicht getestet habe), wie einfach / schwierig es wäre, Änderungen an der binären Darstellung vorzunehmen. Das Ändern der gespeicherten, sortierbaren Darstellung erfordert möglicherweise das Löschen und erneute Hinzufügen des Felds. Außerdem würde das Löschen der Assembly, die den UDT enthält, fehlschlagen, wenn sie auf eine der beiden Arten verwendet wird. Sie sollten also sicherstellen, dass sich in der Assembly außer diesem UDT nichts anderes befindet. Sie können
ALTER ASSEMBLY
die Definition ersetzen, es gibt jedoch einige Einschränkungen.Auf der anderen Seite handelt es sich bei dem
VARCHAR()
Feld um Daten, die vom Algorithmus getrennt sind, sodass nur die Spalte aktualisiert werden muss. Und wenn es mehrere zehn Millionen Zeilen (oder mehr) gibt, kann dies in einem Stapelansatz erfolgen.Implementieren Sie die ICU- Bibliothek, die diese alphanumerische Sortierung tatsächlich ermöglicht. Die Bibliothek ist zwar hochfunktional, aber nur in zwei Sprachen verfügbar: C / C ++ und Java. Dies bedeutet, dass Sie möglicherweise einige Änderungen vornehmen müssen, damit es in Visual C ++ funktioniert, oder dass die Möglichkeit besteht, dass der Java-Code mithilfe von IKVM in MSIL konvertiert werden kann . Auf dieser Site sind ein oder zwei .NET-seitige Projekte verlinkt, die eine COM-Schnittstelle bieten, auf die in verwaltetem Code zugegriffen werden kann. Ich glaube jedoch, dass sie seit einiger Zeit nicht mehr aktualisiert wurden und ich sie nicht ausprobiert habe. Am besten ist es, dies in der App-Ebene mit dem Ziel zu behandeln, Sortierschlüssel zu generieren. Die Sortierschlüssel würden dann in einer neuen Sortierspalte gespeichert.
Dies ist möglicherweise nicht der praktischste Ansatz. Es ist jedoch immer noch sehr cool, dass eine solche Fähigkeit existiert. In der folgenden Antwort habe ich ein detaillierteres Beispiel dafür gegeben:
Gibt es eine Sortierung, um die folgenden Zeichenfolgen in der folgenden Reihenfolge zu sortieren: 1,2,3,6,10,10A, 10B, 11?
Das in dieser Frage behandelte Muster ist jedoch etwas einfacher. Ein Beispiel, das zeigt, dass die Art des Musters, mit dem sich diese Frage befasst, auch funktioniert, finden Sie auf der folgenden Seite:
ICU Collation Demo
Setzen Sie unter "Einstellungen" die Option "numerisch" auf "ein" und alle anderen sollten auf "Standard" gesetzt sein. Deaktivieren Sie rechts neben der Schaltfläche "Sortieren" die Option für "Diff-Stärken" und aktivieren Sie die Option für "Sortierschlüssel". Ersetzen Sie dann die Liste der Elemente im Textbereich "Eingabe" durch die folgende Liste:
Klicken Sie auf die Schaltfläche "Sortieren". Der Textbereich "Ausgabe" sollte Folgendes anzeigen:
Bitte beachten Sie, dass die Sortierschlüssel in mehreren Feldern strukturiert sind, die durch Kommas getrennt sind. Jedes Feld muss unabhängig sortiert werden, damit ein weiteres kleines Problem gelöst werden kann, wenn dies in SQL Server implementiert werden muss.
** Wenn Bedenken hinsichtlich der Leistung hinsichtlich der Verwendung von benutzerdefinierten Funktionen bestehen, beachten Sie bitte, dass die vorgeschlagenen Ansätze diese nur minimal nutzen. Tatsächlich bestand der Hauptgrund für das Speichern des normalisierten Werts darin, zu vermeiden, dass für jede Zeile jeder Abfrage eine UDF aufgerufen wird. Im primären Ansatz wird die UDF verwendet, um den Wert von festzulegen
SortColumn
, und dies erfolgt nur überINSERT
undUPDATE
über den Trigger. Das Auswählen von Werten ist weitaus häufiger als das Einfügen und Aktualisieren, und einige Werte werden nie aktualisiert. Für jedeSELECT
Abfrage, die denSortColumn
Filter für einen Bereich in derWHERE
Klausel verwendet, wird die UDF nur einmal pro Wert für range_start und range_end benötigt, um die normalisierten Werte zu erhalten. Die UDF wird nicht pro Zeile aufgerufen.In Bezug auf die UDT ist die Verwendung tatsächlich dieselbe wie bei der skalaren UDF. Das heißt, das Einfügen und Aktualisieren würde die Normalisierungsmethode einmal pro Zeile aufrufen, um den Wert festzulegen. Dann würde die Normalisierungsmethode einmal pro Abfrage pro Bereichsstart und Bereichswert in einem Bereichsfilter aufgerufen, jedoch nicht pro Zeile.
Ein Punkt für die vollständige Behandlung der Normalisierung in einer SQLCLR-UDF ist, dass sie, da sie keinen Datenzugriff ausführt und deterministisch ist, wenn sie als markiert
IsDeterministic = true
ist, an parallelen Plänen teilnehmen kann (was denINSERT
undUPDATE
Operationen helfen könnte ), während a T-SQL UDF verhindert, dass ein paralleler Plan verwendet wird.quelle