Wiele innych odpowiedzi, które widzę tutaj (i w zduplikowanych pytaniach) w zasadzie działa tylko dla bardzo specyficznie sformatowanych danych, np. Ciągu, który jest w całości liczbą lub dla którego istnieje prefiks alfabetyczny o stałej długości. W ogólnym przypadku to nie zadziała.
To prawda, że tak naprawdę nie ma sposobu na zaimplementowanie 100% ogólnego sortowania nat w MySQL, ponieważ aby to zrobić, naprawdę potrzebujesz zmodyfikowanej funkcji porównania , która przełącza się między leksykograficznym sortowaniem ciągów i sortowaniem numerycznym, jeśli / kiedy napotka numer. Taki kod mógłby zaimplementować dowolny algorytm rozpoznawania i porównywania części numerycznych w dwóch ciągach. Niestety, funkcja porównawcza w MySQL jest wewnętrzna dla jego kodu i użytkownik nie może jej zmienić.
Pozostawia to pewnego rodzaju hack, w którym próbujesz utworzyć klucz sortowania dla swojego ciągu, w którym części numeryczne są ponownie formatowane, tak że standardowe sortowanie leksykograficzne faktycznie sortuje je tak, jak chcesz .
W przypadku zwykłych liczb całkowitych do pewnej maksymalnej liczby cyfr oczywistym rozwiązaniem jest po prostu wypełnienie ich zerami w lewo, aby wszystkie miały stałą szerokość. Takie jest podejście zastosowane przez wtyczkę Drupal i rozwiązania @plalx / @RichardToth. (@Christian ma inne i znacznie bardziej złożone rozwiązanie, ale nie oferuje żadnych korzyści, które widzę).
Jak wskazuje @tye, możesz to poprawić, dodając stałą liczbę cyfr do każdej liczby, zamiast po prostu dopełniać ją w lewo. Można jednak ulepszyć o wiele, wiele więcej rzeczy, nawet biorąc pod uwagę ograniczenia tego, co w zasadzie jest niezręcznym hackiem. Jednak wydaje się, że nie ma żadnych gotowych rozwiązań!
Na przykład co z:
- Znaki plus i minus? +10 przeciwko 10 przeciwko -10
- Ułamki dziesiętne? 8,2, 8,5, 1,006, 0,75
- Wiodące zera? 020, 030, 00000922
- Tysiące separatorów? „1001 dalmatów” kontra „1001 dalmatów”
- Numery wersji? MariaDB 10.3.18 vs MariaDB 10.3.3
- Bardzo długie liczby? 103.768.276.592.092.364.859.236.487.687.870.234.598,55
Rozszerzając metodę @ tye, stworzyłem dość zwartą funkcję przechowywaną NatSortKey (), która konwertuje dowolny ciąg na klucz nat-sort i która obsługuje wszystkie powyższe przypadki, jest dość wydajna i zachowuje całkowite sortowanie kolejność (żadne dwa różne ciągi nie mają równych kluczy sortowania). Drugi parametr może być użyty do ograniczenia liczby liczb przetwarzanych w każdym łańcuchu (np. Do pierwszych 10 liczb), co może służyć do zapewnienia, że dane wyjściowe mieszczą się w zadanej długości.
UWAGA: Łańcuch klucza sortowania wygenerowany z podaną wartością tego drugiego parametru powinien być sortowany tylko względem innych ciągów wygenerowanych z tą samą wartością parametru, w przeciwnym razie mogą one nie być sortowane poprawnie!
Możesz z niego skorzystać bezpośrednio w zamówieniu np
SELECT myString FROM myTable ORDER BY NatSortKey(myString,0);
Ale w celu efektywnego sortowania dużych tabel lepiej jest wstępnie przechowywać klucz sortowania w innej kolumnie (prawdopodobnie z indeksem):
INSERT INTO myTable (myString,myStringNSK) VALUES (@theStringValue,NatSortKey(@theStringValue,10)), ...
...
SELECT myString FROM myTable ORDER BY myStringNSK;
[Idealnie byłoby, gdyby stało się to automatycznie, tworząc kolumnę kluczową jako obliczoną przechowywaną kolumnę, używając czegoś takiego jak:
CREATE TABLE myTable (
...
myString varchar(100),
myStringNSK varchar(150) AS (NatSortKey(myString,10)) STORED,
...
KEY (myStringNSK),
...);
Ale na razie ani MySQL, ani MariaDB nie pozwalają na przechowywanie funkcji w kolumnach obliczeniowych , więc niestety nie możesz jeszcze tego zrobić .]
Moja funkcja wpływa tylko na sortowanie liczb . Jeśli chcesz robić inne rzeczy sort-normalizacja, takich jak usuwanie wszystkich znaków interpunkcyjnych lub przycinanie spacje przy każdym końcu, albo zastępując sekwencje wielu białych znaków z pojedynczymi odstępami, można też rozszerzyć funkcję, czy można to zrobić przed lub po NatSortKey()
Is zastosowane do Twoich danych. (Polecam użycie REGEXP_REPLACE()
w tym celu).
Zakładam również, że jest nieco anglo-centryczny ”. dla przecinka dziesiętnego i „,” dla separatora tysięcy, ale modyfikacja powinna być łatwa, jeśli chcesz, aby odwrotność była odwrotna lub jeśli chcesz, aby była przełączalna jako parametr.
Może być możliwe dalsze ulepszanie w inny sposób; na przykład obecnie sortuje liczby ujemne według wartości bezwzględnej, więc -1 występuje przed -2, a nie na odwrót. Nie ma również możliwości określenia kolejności sortowania DESC dla liczb przy zachowaniu sortowania leksykograficznego ASC dla tekstu. Oba te problemy można rozwiązać przy odrobinie pracy; Zaktualizuję kod, jeśli / kiedy otrzymam czas.
Jest wiele innych szczegółów, o których należy pamiętać - w tym niektóre krytyczne zależności od używanego zestawu i sortowania - ale umieściłem je wszystkie w bloku komentarzy w kodzie SQL. Przeczytaj uważnie przed użyciem tej funkcji!
Oto kod. Jeśli znajdziesz błąd lub poprawę, o której nie wspomniałem, daj mi znać w komentarzach!
delimiter $$
CREATE DEFINER=CURRENT_USER FUNCTION NatSortKey (s varchar(100), n int) RETURNS varchar(350) DETERMINISTIC
BEGIN
DECLARE x,y varchar(100);
DECLARE r varchar(350) DEFAULT '';
DECLARE suf varchar(101);
DECLARE i,j,k int UNSIGNED;
IF n<=0 THEN SET n := -1; END IF;
LOOP
SET i := REGEXP_INSTR(s,'\\d');
IF i=0 OR n=0 THEN RETURN CONCAT(r,s); END IF;
SET n := n-1, suf := ' ';
IF i>1 THEN
IF SUBSTRING(s,i-1,1)='.' AND (i=2 OR SUBSTRING(s,i-2,1) RLIKE '[^.\\p{L}\\p{N}\\p{M}\\x{608}\\x{200C}\\x{200D}\\x{2100}-\\x{214F}\\x{24B6}-\\x{24E9}\\x{1F130}-\\x{1F149}\\x{1F150}-\\x{1F169}\\x{1F170}-\\x{1F189}]') AND (SUBSTRING(s,i) NOT RLIKE '^\\d++\\.\\d') THEN SET i:=i-1; END IF;
IF i>1 AND SUBSTRING(s,i-1,1)='+' THEN SET suf := '+', j := i-1; ELSE SET j := i; END IF;
SET r := CONCAT(r,SUBSTRING(s,1,j-1)); SET s = SUBSTRING(s,i);
END IF;
SET x := REGEXP_SUBSTR(s,IF(SUBSTRING(s,1,1) IN ('0','.') OR (SUBSTRING(r,-1)=',' AND suf=' '),'^\\d*+(?:\\.\\d++)*','^(?:[1-9]\\d{0,2}(?:,\\d{3}(?!\\d))++|\\d++)(?:\\.\\d++)*+'));
SET s := SUBSTRING(s,LENGTH(x)+1);
SET i := INSTR(x,'.');
IF i=0 THEN SET y := ''; ELSE SET y := SUBSTRING(x,i); SET x := SUBSTRING(x,1,i-1); END IF;
SET i := LENGTH(x);
SET x := REPLACE(x,',','');
SET j := LENGTH(x);
SET x := TRIM(LEADING '0' FROM x);
SET k := LENGTH(x);
SET suf := CONCAT(suf,LPAD(CONV(LEAST((j-k)*2,1294) + IF(i=j,0,1),10,36),2,'0'));
SET i := LOCATE('.',y,2);
IF i=0 THEN
SET r := CONCAT(r,LPAD(CONV(LEAST(k,359),10,36),2,'0'),x,y,suf);
ELSE
SET r := CONCAT(r,LPAD(CONV(LEAST(k,359),10,36),2,'0'),x);
WHILE LENGTH(y)>0 AND n!=0 DO
IF i=0 THEN SET x := SUBSTRING(y,2); SET y := ''; ELSE SET x := SUBSTRING(y,2,i-2); SET y := SUBSTRING(y,i); SET i := LOCATE('.',y,2); END IF;
SET j := LENGTH(x);
SET x := TRIM(LEADING '0' FROM x);
SET k := LENGTH(x);
SET r := CONCAT(r,LPAD(CONV(LEAST(k,359),10,36),2,'0'),x);
SET suf := CONCAT(suf,LPAD(CONV(LEAST((j-k)*2,1294),10,36),2,'0'));
SET n := n-1;
END WHILE;
SET r := CONCAT(r,y,suf);
END IF;
END LOOP;
END
$$
delimiter ;