私がここで(そして重複した質問で)見る他の多くの答えは基本的に非常にしか機能しません特殊にフォーマットされたデータ、たとえば完全に数字である文字列、または固定長のアルファベット接頭辞がある文字列ます。これは一般的なケースでは機能しません。
MySQLで100%一般的なnat-sortを実装する方法が実際にはないのは事実です。 本当に必要なのは、文字列の辞書式順序と数値の並べ替えを切り替える変更された比較関数です。数。このようなコードは、2つの文字列内の数値部分を認識して比較するために必要な任意のアルゴリズムを実装できます。ただし、残念ながら、MySQLの比較関数はそのコードの内部にあり、ユーザーが変更することはできません。
これは、あなたが作成しようとするある種のハックを残します 残ります。文字列の並べ替えキーして、数値部分を再フォーマットし、標準の辞書式順序で実際に希望どおりに並べ替えるようにします。
最大桁数までの単純な整数の場合、明らかな解決策は、すべて固定幅になるように、単純にゼロを左に埋めることです。これは、Drupalプラグインが採用したアプローチであり、@ plalx / @ RichardTothのソリューションです。(@Christianには別のはるかに複雑なソリューションがありますが、私が見ることができる利点はありません)。
@tyeが指摘しているように、単に左にパディングするのではなく、各数値に固定桁の長さを付加することで、これを改善できます。本質的に厄介なハックの制限を考慮しても、改善できることははるかにたくさんあります。それでも、構築済みのソリューションは存在しないようです。
たとえば、次の場合はどうでしょうか。
- プラス記号とマイナス記号?+10 vs 10 vs -10
- 小数?8.2、8.5、1.006、.75
- 先行ゼロ?020、030、00000922
- 千のセパレータ?「1,001ダルメーション」vs「1001ダルメーション」
- バージョン番号?MariaDBv10.3.18とMariaDBv10.3.3
- 非常に長い数字ですか?103,768,276,592,092,364,859,236,487,687,870,234,598.55
@tyeのメソッドを拡張して、任意の文字列をnat-sortキーに変換し、上記のすべてのケースを処理し、適度に効率的で、全体のソートを保持する、かなりコンパクトなNatSortKey()ストアド関数を作成しました-順序(2つの異なる文字列に同等に比較されるソートキーはありません)。2番目のパラメーターを使用して、各文字列で処理される数値の数(たとえば、最初の10個の数値)を制限できます。これを使用して、出力が指定された長さ内に収まるようにすることができます。
注:この2番目のパラメーターの特定の値で生成されたソートキー文字列は、パラメーターの同じ値で生成された他の文字列に対してのみソートする必要があります。そうしないと、正しくソートされない可能性があります。
注文時に直接使用できます。
SELECT myString FROM myTable ORDER BY NatSortKey(myString,0);
ただし、大きなテーブルを効率的に並べ替えるには、並べ替えキーを別の列に事前に保存しておくことをお勧めします(おそらくインデックスが付いています)。
INSERT INTO myTable (myString,myStringNSK) VALUES (@theStringValue,NatSortKey(@theStringValue,10)), ...
...
SELECT myString FROM myTable ORDER BY myStringNSK;
[理想的には、次のようなものを使用して、計算された格納列としてキー列を作成することにより、これを自動的に実行します。
CREATE TABLE myTable (
...
myString varchar(100),
myStringNSK varchar(150) AS (NatSortKey(myString,10)) STORED,
...
KEY (myStringNSK),
...);
しかし今のところ、MySQLもMariaDBも計算列に格納された関数を許可していないため、残念ながらまだこれを行うことはできません。]
私の関数は数値の並べ替えにのみ影響します。あなたは、このようなすべての句読点を削除、または各末尾から空白をトリミング、または単一スペースでマルチ空白のシーケンスを交換するなど、他のソート正規化のことを、やりたい場合は、どちらかの機能を拡張することができ、またはそれは前または後に行うことができNatSortKey()
、ISデータに適用されます。(私は使用することをお勧めしますREGEXP_REPLACE()
この目的でするます)。
それはまた、私が「。」と仮定するという点で、いくぶん英語中心です。小数点の場合は「、」、千単位の区切り文字の場合は「、」ですが、逆の場合、またはパラメーターとして切り替え可能にする場合は、簡単に変更できます。
他の方法でさらに改善できる可能性があります。たとえば、現在、負の数を絶対値でソートしているため、-1は-2の前にあり、その逆ではありません。また、テキストのASC辞書式ソートを保持しながら、数値のDESCソート順を指定する方法もありません。これらの問題は両方とも、もう少し作業を行うことで修正できます。時間があれば、コードを更新します。
使用しているchasetと照合順序への重要な依存関係など、注意すべき詳細は他にもたくさんありますが、それらすべてをSQLコード内のコメントブロックに入れました。この機能をご自身で使用する前に、よくお読みください。
だから、ここにコードがあります。バグを見つけた場合、または私が言及していない改善点がある場合は、コメントで知らせてください!
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 ;