April 18, 2021, 00:56:56

Neuigkeiten:

Ist euer Problem gelöst, dann bitte den Knopf "Thema gelöst" drücken!


Natürliche Sortierung

Begonnen von crystal, August 12, 2016, 18:44:05

⏪ vorheriges - nächstes ⏩

crystal

Hi daolix,

von wegen "von nix nen plan", wie du in deinem Profil schreibst...

Das ist genau, was ich mir vorgestellt habe. Allerdings werde ich deine Funktion "GetSortKeyString" nicht in einer Abfrage aufrufen, sondern um das zusätzliche "sortfeld" in meiner Tabelle zu erzeugen.

Ich werde berichten, ob und wie es geklappt hat.

Danke für deine Mühe, diesen Wrapper zu schreiben und dabei sogar noch GetLocaleInfo etc. zu berücksichtigen!

Scheint mir insgesamt eine excellente Lösung zu sein!

Gruß,

crystal
Wer Fehler in meinen Antworten findet, darf sie behalten, muss sie aber kommentieren. ;-)
Dies ist keineswegs arrogant gemeint, sondern soll nur unterstreichen, dass meine Antworten - natürlich - nicht immer fehlerfrei sind und sein können.
Devise: bitte immer erst selbst probieren!

Aus gesundheitlichen Gründen nur noch selten dabei...

daolix

Wie gesagt mit den Flags musst du noch ein wenig spielen, denn immo  wird nicht unterschieden zw z.b.  "D 9" und "D9", ausser du definierst eine sekundäre Sortierung  ala "..ORDER BY sortfeld, Tabellenfeld

PhilS

Zitat von: daolix am August 17, 2016, 14:47:27die Funktion "GetSortKeyString" dann mal in deiner Abfrage aufrufen

PS: das flag müsste wohl lauten : &H400 Or &H8 Or &H800

Mit den Konstanten ist es weniger kryptisch:
Private Const LCMAP_SORTKEY = &H400                   '  WC sort key (normalize)
Private Const LCMAP_BYTEREV = &H800                   '  byte reversal
Private Const SORT_DIGITSASNUMBERS     As Long = &H8         '  use digits as numbers sort method


Ich habe auch ein wenig mit der LCMapStringEx-Function gespielt. - Grundsätzlich genau der richtige Ansatz. Ich sehe nur ein grundlegendes Problem in Verbindung mit Access als Backend.

LCMapStringEx generiert als Sortkey ein Byte-Array. Access kann nicht über Binärfelder sortieren. Wenn man das Byte-Array jetzt als String behandelt, verwendet Access für die Sortierung die jeweils für die DB eingestellt Sortierreihenfolge. Dabei kann es aber IMO passieren, dass die Sortierung am Ende nicht der binären Sortierung der Bytes entspricht und damit nicht mehr korrekt ist.

Übersehe ich etwas?
Access DevTools - Find and Replace
Komfortables Suchen und Ersetzen in den Entwurfseigenschaften von Access-Objekten. In Abfragen, Formularen, Berichten und VBA-Code - Überall und rasend schnell!

daolix

ZitatÜbersehe ich etwas?

Nö, du hast recht, wie ich gerade übelst feststellen durfte.

ZitatMit den Konstanten ist es weniger kryptisch:
Dat ist wohl war, is halt die faulheit die siegt beim Übernehmen der Werte aus nen anderen dialekt.

PhilS

Ich glaube dafür ist mir eine Lösung eingefallen:
Private Function GetByteString(buffer() As Byte, ByVal buffLen As Long) As String
    Dim retVal As String
    Dim i As Long
   
    For i = 0 To buffLen - 1
        retVal = retVal & Format(buffer(i), "000") & ","
    Next i

    GetByteString = retVal
End Function

Mit der Function fülle ich die Byte-werte immer auf drei Stellen mit Nullen auf und hänge sie als String hintereinander. - Das sollte funktionieren.

Das Endergebnis entspricht allerdings noch nicht so ganz meinen Erwartungen. (Siehe Screenshot).



Access DevTools - Find and Replace
Komfortables Suchen und Ersetzen in den Entwurfseigenschaften von Access-Objekten. In Abfragen, Formularen, Berichten und VBA-Code - Überall und rasend schnell!

Lachtaube

August 17, 2016, 18:20:12 #20 Letzte Bearbeitung: August 17, 2016, 18:33:55 von Lachtaube
Was wiederum geht, ist, ein Binärfeld anzulegen, dessen Kapazität allerding maximal 510 Bytes umfasst. Das Ergebnis aus LCMapStringEx lässt sich dann innerhalb der Größenbeschrnkung ablegen, und das Binärfeld kann zum Sortieren verwendet werden.Option Explicit

'int LCMapStringEx(
'  _In_opt_  LPCWSTR          lpLocaleName,
'  _In_      DWORD            dwMapFlags,
'  _In_      LPCWSTR          lpSrcStr,
'  _In_      int              cchSrc,
'  _Out_opt_ LPWSTR           lpDestStr,
'  _In_      int              cchDest,
'  _In_opt_  LPNLSVERSIONINFO lpVersionInformation,
'  _In_opt_  LPVOID           lpReserved,
'  _In_opt_  LPARAM           sortHandle
');

Private Const LOCALE_NAME_USER_DEFAULT$ = vbNullString
Private Const LCMAP_SORTKEY& = &H400            ' // WC sort key (normalize)
Private Const SORT_DIGITSASNUMBERS& = &H8       ' // use digits as numbers sort method

#If VBA7 Then
Private Declare PtrSafe Function LCMapStringEx Lib "kernel32" ( _
   ByVal lpLocaleName As LongPtr _
, ByVal dwMapFlags As Long _
, ByVal lpSrcStr As LongPtr _
, ByVal cchSrc As Long _
, ByVal lpDestStr As LongPtr _
, ByVal cchDest As Long _
, Optional ByVal lpVersionInformation As LongPtr _
, Optional ByVal lpReserved As LongPtr _
, Optional ByVal lParam As Long _
) As Long
#Else
Private Declare Function LCMapStringEx Lib "kernel32" ( _
   ByVal lpLocaleName As Long _
, ByVal dwMapFlags As Long _
, ByVal lpSrcStr As Long _
, ByVal cchSrc As Long _
, ByVal lpDestStr As Long _
, ByVal cchDest As Long _
, Optional ByVal lpVersionInformation As Long _
, Optional ByVal lpReserved As Long _
, Optional ByVal lParam As Long _
) As Long
#End If

Public Function GetMappedStringArray(ByVal Source$) As Byte()
   Dim r&, dest() As Byte

   r = LCMapStringEx(StrPtr(LOCALE_NAME_USER_DEFAULT), _
                     LCMAP_SORTKEY Or SORT_DIGITSASNUMBERS, _
                     StrPtr(Source), Len(Source), 0, 0)
   If r Then
      ReDim dest(r - 1)
      r = LCMapStringEx(StrPtr(LOCALE_NAME_USER_DEFAULT), _
                        LCMAP_SORTKEY Or SORT_DIGITSASNUMBERS, _
                        StrPtr(Source), Len(Source), _
                        VarPtr(dest(0)), _
                        UBound(dest) + 1)
      GetMappedStringArray = dest
   End If
End Function
Grüße von der (⌒▽⌒)

PhilS

Zitat von: Lachtaube am August 17, 2016, 18:20:12Was wiederum geht, ist, ein Binärfeld anzulegen, dessen Kapazität allerding maximal 510 Bytes umfasst.

Natürlich, per DDL, du hast recht!

Leider kann man die Funktion mit Rückgabewert Byte() nicht direkt in eine Abfrage einbauen. Also muss man erst über ein Recordset in die Tabelle den binären SortKey schreiben und kann dann danach sortieren. - Was mir nicht ganz so gut gefällt, weil es Update-Anomalien mit sich bringen könnte.

Auch wenn das Byte-Array sicherlich der eigentlich performantere und bessere Datentyp wäre, würde ich trotzdem meinen Lösungsvorschlag mit der Umwandlung des Byte-Arrays in einen String vorziehen. Das kann man direkt in einer Abfrage verwenden, ohne den SortKey speichern zu müssen.


Noch eine Anmerkung zu meinem Screenshot mit der inkorrekten Sortierung: Dies wurde offenbar durch den Flag LCMAP_BYTEREV verursacht, den ich eingebaut hatte, um seine Auswirkung zu testen. Außerdem hatte ich mit LCMAP_BYTEREV gelegentliche Access-Abstürze, wenn ich die Funktion in der Recordset-Schleife für die ganze Tabelle aufgerufen habe. Ohne den Flag ist das Ergebnis so wie ich es erwartet hätte.
Access DevTools - Find and Replace
Komfortables Suchen und Ersetzen in den Entwurfseigenschaften von Access-Objekten. In Abfragen, Formularen, Berichten und VBA-Code - Überall und rasend schnell!

daolix

ZitatDies wurde offenbar durch den Flag LCMAP_BYTEREV verursacht, den ich eingebaut hatte

hmm, also bei mir nicht. Da hat deine Idee sowohl mit als auch ohne diesem Flag funktioniert.

Lachtaube

Das Flag LCMAP_BYTEREV käme dann sinnvoll zum Einsatz, wenn der SrcStr im Big Endian Format vorläge.

Tja, auf Anomalien muss man natürlich immer achten. Im Zweifelsfall muss man halt noch einmal vorher eine Aktualisierungsabfrage laufen lassen. Der Vorteil des Binärfelds besteht darin, dass es indiziert werden kann.

Ich habe noch zwei weitere Versionen der Funktion erstellt. Eine, die auch in Abfragen auf das Binärfeld angewendet werden kann (z. Bsp. zum Aktualisieren) und eine, die zur Sortierung in Abfragen geeignet ist (Verzicht auf Index-Nutzung). Diese wird deutlich mehr Kapazität als die Version der aneinandergeketteten "000",-Bytefolgen aufweisen und auch schneller sein, weil hier die Zeichen-Verkettung entfällt. Hier noch einmal der komplette Code.Option Explicit

'int LCMapStringEx(
'  _In_opt_  LPCWSTR          lpLocaleName,
'  _In_      DWORD            dwMapFlags,
'  _In_      LPCWSTR          lpSrcStr,
'  _In_      int              cchSrc,
'  _Out_opt_ LPWSTR           lpDestStr,
'  _In_      int              cchDest,
'  _In_opt_  LPNLSVERSIONINFO lpVersionInformation,
'  _In_opt_  LPVOID           lpReserved,
'  _In_opt_  LPARAM           sortHandle
');

Private Const LOCALE_NAME_USER_DEFAULT$ = vbNullString
Private Const LCMAP_SORTKEY& = &H400            ' // WC sort key (normalize)
Private Const SORT_DIGITSASNUMBERS& = &H8       ' // use digits as numbers sort method

#If VBA7 Then
Private Declare PtrSafe Function LCMapStringEx Lib "kernel32" ( _
   ByVal lpLocaleName As LongPtr _
, ByVal dwMapFlags As Long _
, ByVal lpSrcStr As LongPtr _
, ByVal cchSrc As Long _
, ByVal lpDestStr As LongPtr _
, ByVal cchDest As Long _
, Optional ByVal lpVersionInformation As LongPtr _
, Optional ByVal lpReserved As LongPtr _
, Optional ByVal lParam As Long _
) As Long
#Else
Private Declare Function LCMapStringEx Lib "kernel32" ( _
   ByVal lpLocaleName As Long _
, ByVal dwMapFlags As Long _
, ByVal lpSrcStr As Long _
, ByVal cchSrc As Long _
, ByVal lpDestStr As Long _
, ByVal cchDest As Long _
, Optional ByVal lpVersionInformation As Long _
, Optional ByVal lpReserved As Long _
, Optional ByVal lParam As Long _
) As Long
#End If

'// Nur zur Verwendung in Recordsets geeignet
Public Function GetMappedStringAsByteArray(ByVal Source$) As Byte()
   Dim r&, dest() As Byte

   r = LCMapStringEx(StrPtr(LOCALE_NAME_USER_DEFAULT), _
                     LCMAP_SORTKEY Or SORT_DIGITSASNUMBERS, _
                     StrPtr(Source), Len(Source), 0, 0)
   If r Then
      ReDim dest(r - 1)
      r = LCMapStringEx(StrPtr(LOCALE_NAME_USER_DEFAULT), _
                        LCMAP_SORTKEY Or SORT_DIGITSASNUMBERS, _
                        StrPtr(Source), Len(Source), _
                        VarPtr(dest(0)), _
                        UBound(dest) + 1)
      '// mehr als 510 Bytes passen nicht in ein Binärfeld
      If r > 510 Then ReDim Preserve dest(509)
      GetMappedStringAsByteArray = dest
   End If
End Function

'// Zur Verwendung in Abfragen und Recordsets - nicht zur Sortierung in Abfragen geeignet
Public Function GetMappedStringArrayAsAnsiString$(ByVal Source$)
   Dim r&, dest() As Byte

   r = LCMapStringEx(StrPtr(LOCALE_NAME_USER_DEFAULT), _
                     LCMAP_SORTKEY Or SORT_DIGITSASNUMBERS, _
                     StrPtr(Source), Len(Source), 0, 0)
   If r Then
      ReDim dest(r - 1)
      r = LCMapStringEx(StrPtr(LOCALE_NAME_USER_DEFAULT), _
                        LCMAP_SORTKEY Or SORT_DIGITSASNUMBERS, _
                        StrPtr(Source), Len(Source), _
                        VarPtr(dest(0)), _
                        UBound(dest) + 1)
      '// mehr als 510 Bytes passen nicht in ein Binärfeld
      If r > 510 Then ReDim Preserve dest(509)
      GetMappedStringArrayAsAnsiString = dest
   End If
End Function

'// Zur Verwendung als Sortierschlüssel in Abfragen und Recordsets geeignet
'// ob Access dabei mehr als 255 Zeichen berücksichtigt, müsste man prüfen
'// - ich vermute eher nicht
Public Function GetMappedStringArrayAsUTF16$(ByVal Source$)
   Dim r&, dest() As Byte

   r = LCMapStringEx(StrPtr(LOCALE_NAME_USER_DEFAULT), _
                     LCMAP_SORTKEY Or SORT_DIGITSASNUMBERS, _
                     StrPtr(Source), Len(Source), 0, 0)
   If r Then
      ReDim dest(r - 1)
      r = LCMapStringEx(StrPtr(LOCALE_NAME_USER_DEFAULT), _
                        LCMAP_SORTKEY Or SORT_DIGITSASNUMBERS, _
                        StrPtr(Source), Len(Source), _
                        VarPtr(dest(0)), _
                        UBound(dest) + 1)
      GetMappedStringArrayAsAnsiString = StrConv(dest, vbUnicode)
   End If
End Function
PS: Wer nicht weiß, wie ein Binärfeld erstellt werden kann, kann temporär die Systemtabellen der Datenbank einblenden und sich z. Bsp. aus der MSysObjects-Tabelle das Owner-Feld in seine Tabelle kopieren und umbenennen.
Grüße von der (⌒▽⌒)

crystal

Hi Leute,

wirklich Klasse, was ihr hier für Antworten geliefert habt!!!

LCMapStringEx liefert ja scheinbar ein Byte-Array, bei dem je 2 Bytes einem Unicode-Zeichen entsprechen, oder? Wenn das so ist, könnte man diese je 2 Bytes nicht auch in ASCII-Äquivalente zurückwandeln, um "sortfeld" lesbar zu bekommen? Denn das Byte-Array ist natürlich nicht besonders "schön". (Übrigens könnte man auf das Komma verzichten, da man den zusammengesetzten String eh nicht lesen kann.)

Da es in meiner Anwendung "nur" um den Fall geht, in einem String "irgendwo" eingebettete Zahlen (und Umlaute) natürlich zu sortieren, tendiere ich inzwischen doch wieder zu einer eigenen Funktion, die an passenden Stellen führende Nullen (bzw. 2-Zeichen-Äquivalente) einfügt, so schön LCMapStringEx auch ist. Nach grober Sichtung meiner knapp 10.000 Datensätze habe ich gesehen, dass numerische Bestandteile maximal 3 Vorkommastellen haben, so dass ich mit einer Auffüllung auf 4 führende Nullen wohl einigermaßen auf der sicheren Seite wäre. Notfalls müsste ich eine solche Wandlungs-Funktion auf mehr Stellen ändern und dann einmalig alle Datensätze bearbeiten.

Aber vielleicht habt ihr ja noch bessere Ideen.

Danke jedenfalls für diese lehrreichen Beispiele zur Nutzung von LCMapStringEx.

crystal

PS: Laut MSDN-Doku ist die Nutzung des Flags LCMAP_SORTKEY nicht mit anderen Main-Flags kombinierbar, vielleicht kam es deshalb zu Abstürzen.










Wer Fehler in meinen Antworten findet, darf sie behalten, muss sie aber kommentieren. ;-)
Dies ist keineswegs arrogant gemeint, sondern soll nur unterstreichen, dass meine Antworten - natürlich - nicht immer fehlerfrei sind und sein können.
Devise: bitte immer erst selbst probieren!

Aus gesundheitlichen Gründen nur noch selten dabei...

Lachtaube

Zitat von: crystal am August 17, 2016, 21:29:27LCMapStringEx liefert ja scheinbar ein Byte-Array, bei dem je 2 Bytes einem Unicode-Zeichen entsprechen, oder? Wenn das so ist, könnte man diese je 2 Bytes nicht auch in ASCII-Äquivalente zurückwandeln, um "sortfeld" lesbar zu bekommen?
Die Funktion GetMappedStringArrayAsUTF16$ macht das ja - nur wird daraus dann noch kein lesbarer Text. Für bestimmte Unicode-Zeichen werden auch schon einmal 10 Bytes verbraten. Ich denke, dass außer einem eindeutigen Sortierschlüssel, den Bytefolgen nicht viek Sinn hinterlegt ist. Ansonsen würde ich auch eine Umkehr-API Funktion erwarten.

Ein nächster Schritt sollte ein Benchmarking umfassen, um die verschiedenen Versionen auf Praxistauglichkeit testen zu können.
Grüße von der (⌒▽⌒)

PhilS

Zitat von: Lachtaube am August 17, 2016, 20:59:18...und eine, die zur Sortierung in Abfragen geeignet ist (Verzicht auf Index-Nutzung). Diese wird deutlich mehr Kapazität als die Version der aneinandergeketteten "000",-Bytefolgen aufweisen und auch schneller sein, weil hier die Zeichen-Verkettung entfällt.

Du möchtest deine Funktion GetMappedStringArrayAsUTF16 für die Sortierung in Abfragen verwenden?

Du hast vermutlich überlesen, was ich dazu in #17 geschrieben hatte:
Zitat von: PhilS am August 17, 2016, 15:42:31Wenn man das Byte-Array jetzt als String behandelt, verwendet Access für die Sortierung die jeweils für die DB eingestellt Sortierreihenfolge. Dabei kann es aber IMO passieren, dass die Sortierung am Ende nicht der binären Sortierung der Bytes entspricht und damit nicht mehr korrekt ist.

Das "IMO" oben streiche ich. - Ich hab ein konkretes Beispiel.
Gibt mal in einer Tabelle mit Text-Spalte die folgenden Zeichen ein (je eines pro Datensatz): ! . ? a ü x z
Dann verwende diese Spalte als Input für GetMappedStringArrayAsUTF16  und sortiere über den Rückgabewert der Funktion.

Mir ist schon klar, dass meine numerischen Byte-Werte-Folgen von der Performance suboptimal sind, aber eine Alternative sehe ich noch nicht.
Access DevTools - Find and Replace
Komfortables Suchen und Ersetzen in den Entwurfseigenschaften von Access-Objekten. In Abfragen, Formularen, Berichten und VBA-Code - Überall und rasend schnell!

Lachtaube

Ja, das stimmt.Als Alternative könnte man eine Hex-Zeichenkette erstellen.'// Zur Verwendung als Sortierschlüssel geeignet
'// Ausgabe als Hex-String
Public Function GetMappedStringArrayAsHex(ByVal Source)
   Dim i&, r&, dest() As Byte

   If Len(Nz(Source)) = 0 Then Exit Function

   '// erforderliche Länge ermitteln
   r = LCMapStringEx(StrPtr(LOCALE_NAME_USER_DEFAULT), _
                     LCMAP_SORTKEY Or SORT_DIGITSASNUMBERS, _
                     StrPtr(Source), Len(Source), 0, 0)
   If r > 0 Then
      '// Ziel-Array dimensionieren
      ReDim dest(r - 1)
      '// Ziel-Array füllen
      r = LCMapStringEx(StrPtr(LOCALE_NAME_USER_DEFAULT), _
                        LCMAP_SORTKEY Or SORT_DIGITSASNUMBERS, _
                        StrPtr(Source), Len(Source), _
                        VarPtr(dest(0)), r)
                       
      '// Ergebnispuffer mit Leerzeichen initialisieren
      GetMappedStringArrayAsHex = Space$(2 * (r - 1))

      '// 0-Byte am Ende ignorieren
      For i = 0 To UBound(dest) - 1
         Mid$(GetMappedStringArrayAsHex, 2 * i + 1, 2) _
               = Right$("00" & Hex$(dest(i)), 2)
      Next
   Else
      GetMappedStringArrayAsHex = CVErr(vbObjectError + &H2001)
   End If
End Function
Bei einem Benchmarking mit einer lokalen Tabelle über 50000 Datensätze und Textlängen zwischen 30 und 50 Zeichen braucht diese Funktion knappe 10 Sekunden zur Sortierung - hingegen erledigt ein indiziertes Binärfeld in ca. 0,1 Sekunden diese Aufgabe, und ein Update aller Sortierwerte benötigt ca. 4,8 Sekunden.
Grüße von der (⌒▽⌒)

crystal

Hallo nochmal, Lachtaube.
Ist ja schon eine Weile her, aber dein Code ist eben "zeitlos" toll. Es gibt nur einen kleinen Fehler, den ich hier korrigiert habe (siehe Kommentar "xxxxx" ganz am Ende).

Ich denke, dieser Thread sollte in ein neues Unterforum aufgenommen werden: "Excellente Lösungen".


Option Explicit

'int LCMapStringEx(
'  _In_opt_  LPCWSTR          lpLocaleName,
'  _In_      DWORD            dwMapFlags,
'  _In_      LPCWSTR          lpSrcStr,
'  _In_      int              cchSrc,
'  _Out_opt_ LPWSTR           lpDestStr,
'  _In_      int              cchDest,
'  _In_opt_  LPNLSVERSIONINFO lpVersionInformation,
'  _In_opt_  LPVOID           lpReserved,
'  _In_opt_  LPARAM           sortHandle
');

Private Const LOCALE_NAME_USER_DEFAULT$ = vbNullString
Private Const LCMAP_SORTKEY& = &H400            ' // WC sort key (normalize)
Private Const SORT_DIGITSASNUMBERS& = &H8       ' // use digits as numbers sort method

#If VBA7 Then
Private Declare PtrSafe Function LCMapStringEx Lib "kernel32" ( _
   ByVal lpLocaleName As LongPtr _
, ByVal dwMapFlags As Long _
, ByVal lpSrcStr As LongPtr _
, ByVal cchSrc As Long _
, ByVal lpDestStr As LongPtr _
, ByVal cchDest As Long _
, Optional ByVal lpVersionInformation As LongPtr _
, Optional ByVal lpReserved As LongPtr _
, Optional ByVal lParam As Long _
) As Long
#Else
Private Declare Function LCMapStringEx Lib "kernel32" ( _
   ByVal lpLocaleName As Long _
, ByVal dwMapFlags As Long _
, ByVal lpSrcStr As Long _
, ByVal cchSrc As Long _
, ByVal lpDestStr As Long _
, ByVal cchDest As Long _
, Optional ByVal lpVersionInformation As Long _
, Optional ByVal lpReserved As Long _
, Optional ByVal lParam As Long _
) As Long
#End If

'// Nur zur Verwendung in Recordsets geeignet
Public Function GetMappedStringAsByteArray(ByVal Source$) As Byte()
   Dim r&, dest() As Byte

   r = LCMapStringEx(StrPtr(LOCALE_NAME_USER_DEFAULT), _
                     LCMAP_SORTKEY Or SORT_DIGITSASNUMBERS, _
                     StrPtr(Source), Len(Source), 0, 0)
   If r Then
      ReDim dest(r - 1)
      r = LCMapStringEx(StrPtr(LOCALE_NAME_USER_DEFAULT), _
                        LCMAP_SORTKEY Or SORT_DIGITSASNUMBERS, _
                        StrPtr(Source), Len(Source), _
                        VarPtr(dest(0)), _
                        UBound(dest) + 1)
      '// mehr als 510 Bytes passen nicht in ein Binärfeld
      If r > 510 Then ReDim Preserve dest(509)
      GetMappedStringAsByteArray = dest
   End If
End Function

'// Zur Verwendung in Abfragen und Recordsets - nicht zur Sortierung in Abfragen geeignet
Public Function GetMappedStringArrayAsAnsiString$(ByVal Source$)
   Dim r&, dest() As Byte

   r = LCMapStringEx(StrPtr(LOCALE_NAME_USER_DEFAULT), _
                     LCMAP_SORTKEY Or SORT_DIGITSASNUMBERS, _
                     StrPtr(Source), Len(Source), 0, 0)
   If r Then
      ReDim dest(r - 1)
      r = LCMapStringEx(StrPtr(LOCALE_NAME_USER_DEFAULT), _
                        LCMAP_SORTKEY Or SORT_DIGITSASNUMBERS, _
                        StrPtr(Source), Len(Source), _
                        VarPtr(dest(0)), _
                        UBound(dest) + 1)
      '// mehr als 510 Bytes passen nicht in ein Binärfeld
      If r > 510 Then ReDim Preserve dest(509)
      GetMappedStringArrayAsAnsiString = dest
   End If
End Function

'// Zur Verwendung als Sortierschlüssel in Abfragen und Recordsets geeignet
'// ob Access dabei mehr als 255 Zeichen berücksichtigt, müsste man prüfen
'// - ich vermute eher nicht
Public Function GetMappedStringArrayAsUTF16$(ByVal Source$)
   Dim r&, dest() As Byte

   r = LCMapStringEx(StrPtr(LOCALE_NAME_USER_DEFAULT), _
                     LCMAP_SORTKEY Or SORT_DIGITSASNUMBERS, _
                     StrPtr(Source), Len(Source), 0, 0)
   If r Then
      ReDim dest(r - 1)
      r = LCMapStringEx(StrPtr(LOCALE_NAME_USER_DEFAULT), _
                        LCMAP_SORTKEY Or SORT_DIGITSASNUMBERS, _
                        StrPtr(Source), Len(Source), _
                        VarPtr(dest(0)), _
                        UBound(dest) + 1)
'xxxxx
'      GetMappedStringArrayAsAnsiString = StrConv(dest, vbUnicode)
'xxxxx
      GetMappedStringArrayAsUTF16 = StrConv(dest, vbUnicode)
   End If
End Function


Ich habe den Code in einem größeren Access-Projekt eingesetzt und das Ergebnis überzeugt total!!!

Vielen Dank also für die grandiose Lösung! :)

;D
Wer Fehler in meinen Antworten findet, darf sie behalten, muss sie aber kommentieren. ;-)
Dies ist keineswegs arrogant gemeint, sondern soll nur unterstreichen, dass meine Antworten - natürlich - nicht immer fehlerfrei sind und sein können.
Devise: bitte immer erst selbst probieren!

Aus gesundheitlichen Gründen nur noch selten dabei...

PhilS

Ich habe die hier erarbeitete Lösung inzwischen auch im Artikel Natürliche Zahlensortierung in Access mit VBA auf meiner Webseite zusammengefasst.
Access DevTools - Find and Replace
Komfortables Suchen und Ersetzen in den Entwurfseigenschaften von Access-Objekten. In Abfragen, Formularen, Berichten und VBA-Code - Überall und rasend schnell!