MySQLの自然順


81

MySQLデータベースでパフォーマンスの高い自然な並べ替えを行うためのエレガントな方法はありますか?

たとえば、このデータセットがある場合:

  • ファイナルファンタジー
  • ファイナルファンタジー4
  • ファイナルファンタジー10
  • ファイナルファンタジー12
  • ファイナルファンタジー12:プロマティアの連鎖
  • ファイナルファンタジーアドベンチャー
  • ファイナルファンタジーオリジン
  • ファイナルファンタジータクティクス

ゲームの名前をコンポーネントに分割する以外のエレガントなソリューション

  • タイトル:「ファイナルファンタジー」
  • 番号:「12」
  • 字幕:「プロマティアの鎖」

それらが正しい順序で出てくることを確認するには?(2の前ではなく4の後の10)。

ゲームタイトルを解析するメカニズムを壊す別のゲームが時々あるので、そうすることはa **の苦痛です(例えば、「ウォーハンマー40,000」、「ジェームズボンド007」)


28
チェーンオブプロマシアは11に関連しています。–


回答:


20

これが、多くのものがリリース日でソートされている理由だと思います。

解決策は、「SortKey」のテーブルに別の列を作成することです。これは、並べ替えやカウンターを簡単にするために作成したパターンに準拠した、サニタイズされたタイトルのバージョンである可能性があります。



2
これは間違いなく正しいアプローチですが、それだけではほとんど答えにはなりません。
ドゥーイン

90

簡単な解決策は次のとおりです。

SELECT alphanumeric, 
       integer
FROM sorting_test
ORDER BY LENGTH(alphanumeric), alphanumeric

49
すべてが「ファイナルファンタジー」であればいいのですが、FFスイートよりも「グーフィー」が優先されます。
fortboise 2011

4
このソリューションは常に機能するとは限りません。時々壊れます。むしろこれを使用する必要があります:stackoverflow.com/a/12257917/384864
Borut Tomazin 2013年

6
クラッジにクラッジを重ねる:SELECT alphanumeric, integer FROM sorting_test ORDER BY SOUNDEX(alphanumeric), LENGTH(alphanumeric), alphanumeric。これがまったく機能する場合、それはSOUNDEXが便利に数値を破棄するためです。したがって、たとえばapple1が前に来るようにしz1ます。
offby1 2014年

最適なソリューション、おかげで、私はスイッチをしなければならなかったもののalphanmuriclength(alphanumeric)「ファイナルファンタジー」の前に「グーフィー」を避けるために
Asped

1
@ offby1の提案SOUNDEX()は、英語の単語でのみ正しく機能するように設計されているため、テキストが100%英語で書かれている場合にのみ機能します。
RaymondNijland19年

56

ちょうどこれを見つけました:

SELECT names FROM your_table ORDER BY games + 0 ASC

数字が前にあるときに自然ソートを実行します。中央でも機能する可能性があります。


2
私はそれを試していませんが、私はそれを真剣に疑っています。前面gamesの数値で機能する理由は、が数値コンテキストのように使用され、比較前に数値に変換されるためです。途中の場合は常に0に変換され、並べ替えは疑似ランダムになります。
manixrock 2011年

1
これは自然順ではありません。むしろ、この実用的なソリューションを見てください:stackoverflow.com/a/12257917/384864
Borut Tomazin 2013年

@fedirこれは私にとってもうまくいきました。なぜこれが機能するのか、完全にはわかりません。説明マークレットのチャンスはありますか?
BizNuge 2014

これを簡単に調査したところ、わかりました。文字列に数学演算子を使用するだけでMySQLがこの種のキャストを実行することすら気づいていませんでした。クールなのは、文字列の先頭に「キャスト」する整数がない場合にzer0を返すだけです。これをありがとう!---> SELECT ADDRESS、(ADDRESS * 1)as _cast FROM sumption WHERE POSTCODE LIKE'NE1% 'ORDER BY ADDRESS * 1 ASC、ADDRESS LIMIT 100000;
BizNuge 2014

1
「ファイナルファンタジー100」や「ファイナルファンタジー2」のように数字が真ん中にある場合は、実際には機能しません。「ファイナルファンタジー100」が先に登場します。ただし、整数が最初の「100ファイナルファンタジー」の場合は機能します
dwenaus 2016年

52

@plalxによって投稿されたものと同じ関数ですが、MySQLに書き直されています。

DROP FUNCTION IF EXISTS `udf_FirstNumberPos`;
DELIMITER ;;
CREATE FUNCTION `udf_FirstNumberPos` (`instring` varchar(4000)) 
RETURNS int
LANGUAGE SQL
DETERMINISTIC
NO SQL
SQL SECURITY INVOKER
BEGIN
    DECLARE position int;
    DECLARE tmp_position int;
    SET position = 5000;
    SET tmp_position = LOCATE('0', instring); IF (tmp_position > 0 AND tmp_position < position) THEN SET position = tmp_position; END IF; 
    SET tmp_position = LOCATE('1', instring); IF (tmp_position > 0 AND tmp_position < position) THEN SET position = tmp_position; END IF;
    SET tmp_position = LOCATE('2', instring); IF (tmp_position > 0 AND tmp_position < position) THEN SET position = tmp_position; END IF;
    SET tmp_position = LOCATE('3', instring); IF (tmp_position > 0 AND tmp_position < position) THEN SET position = tmp_position; END IF;
    SET tmp_position = LOCATE('4', instring); IF (tmp_position > 0 AND tmp_position < position) THEN SET position = tmp_position; END IF;
    SET tmp_position = LOCATE('5', instring); IF (tmp_position > 0 AND tmp_position < position) THEN SET position = tmp_position; END IF;
    SET tmp_position = LOCATE('6', instring); IF (tmp_position > 0 AND tmp_position < position) THEN SET position = tmp_position; END IF;
    SET tmp_position = LOCATE('7', instring); IF (tmp_position > 0 AND tmp_position < position) THEN SET position = tmp_position; END IF;
    SET tmp_position = LOCATE('8', instring); IF (tmp_position > 0 AND tmp_position < position) THEN SET position = tmp_position; END IF;
    SET tmp_position = LOCATE('9', instring); IF (tmp_position > 0 AND tmp_position < position) THEN SET position = tmp_position; END IF;

    IF (position = 5000) THEN RETURN 0; END IF;
    RETURN position;
END
;;

DROP FUNCTION IF EXISTS `udf_NaturalSortFormat`;
DELIMITER ;;
CREATE FUNCTION `udf_NaturalSortFormat` (`instring` varchar(4000), `numberLength` int, `sameOrderChars` char(50)) 
RETURNS varchar(4000)
LANGUAGE SQL
DETERMINISTIC
NO SQL
SQL SECURITY INVOKER
BEGIN
    DECLARE sortString varchar(4000);
    DECLARE numStartIndex int;
    DECLARE numEndIndex int;
    DECLARE padLength int;
    DECLARE totalPadLength int;
    DECLARE i int;
    DECLARE sameOrderCharsLen int;

    SET totalPadLength = 0;
    SET instring = TRIM(instring);
    SET sortString = instring;
    SET numStartIndex = udf_FirstNumberPos(instring);
    SET numEndIndex = 0;
    SET i = 1;
    SET sameOrderCharsLen = CHAR_LENGTH(sameOrderChars);

    WHILE (i <= sameOrderCharsLen) DO
        SET sortString = REPLACE(sortString, SUBSTRING(sameOrderChars, i, 1), ' ');
        SET i = i + 1;
    END WHILE;

    WHILE (numStartIndex <> 0) DO
        SET numStartIndex = numStartIndex + numEndIndex;
        SET numEndIndex = numStartIndex;

        WHILE (udf_FirstNumberPos(SUBSTRING(instring, numEndIndex, 1)) = 1) DO
            SET numEndIndex = numEndIndex + 1;
        END WHILE;

        SET numEndIndex = numEndIndex - 1;

        SET padLength = numberLength - (numEndIndex + 1 - numStartIndex);

        IF padLength < 0 THEN
            SET padLength = 0;
        END IF;

        SET sortString = INSERT(sortString, numStartIndex + totalPadLength, 0, REPEAT('0', padLength));

        SET totalPadLength = totalPadLength + padLength;
        SET numStartIndex = udf_FirstNumberPos(RIGHT(instring, CHAR_LENGTH(instring) - numEndIndex));
    END WHILE;

    RETURN sortString;
END
;;

使用法:

SELECT name FROM products ORDER BY udf_NaturalSortFormat(name, 10, ".")

5
これが実際に機能する唯一のソリューションです。drupalsコードもテストしましたが、失敗することがあります。ありがとう!
Borut Tomazin 2013年

誰もが本当に大きなテーブルでこれを1000万以上使用していますか?
Mark Steudel 2017年

3
@MarkSteudelこれに似た関数(正確な関数ではありませんが)を使用して、最大500万行の複数のテーブルを自然に並べ替えます。ただし、クエリで直接呼び出すのではなく、nat_name列の値を設定するために使用します。行が更新されるたびに、トリガーを使用して関数を実行します。このアプローチでは、列を追加することなく、実際のパフォーマンスコストをかけずに自然な並べ替えを行うことができます。
ジェイコブ

この作品は、文字の前に数字を並べ替え、およびこれに何か似を使用して、hook_views_query_alterを使用してDrupalので実現することができるif ($query->orderby[0]["field"] === "node_field_data.title") { $orderBySql = " udf_NaturalSortFormat(node_field_data.title, 10, '.') "; $query->orderby = []; $query->addOrderBy(NULL, $orderBySql, $query->orderby[0]["direction"], 'title_natural'); array_unshift($query->orderby, end($query->orderby)); }
realgt

16

私は少し前にMSSQL2000用にこの関数を書きました:

/**
 * Returns a string formatted for natural sorting. This function is very useful when having to sort alpha-numeric strings.
 *
 * @author Alexandre Potvin Latreille (plalx)
 * @param {nvarchar(4000)} string The formatted string.
 * @param {int} numberLength The length each number should have (including padding). This should be the length of the longest number. Defaults to 10.
 * @param {char(50)} sameOrderChars A list of characters that should have the same order. Ex: '.-/'. Defaults to empty string.
 *
 * @return {nvarchar(4000)} A string for natural sorting.
 * Example of use: 
 * 
 *      SELECT Name FROM TableA ORDER BY Name
 *  TableA (unordered)              TableA (ordered)
 *  ------------                    ------------
 *  ID  Name                    ID  Name
 *  1.  A1.                 1.  A1-1.       
 *  2.  A1-1.                   2.  A1.
 *  3.  R1      -->         3.  R1
 *  4.  R11                 4.  R11
 *  5.  R2                  5.  R2
 *
 *  
 *  As we can see, humans would expect A1., A1-1., R1, R2, R11 but that's not how SQL is sorting it.
 *  We can use this function to fix this.
 *
 *      SELECT Name FROM TableA ORDER BY dbo.udf_NaturalSortFormat(Name, default, '.-')
 *  TableA (unordered)              TableA (ordered)
 *  ------------                    ------------
 *  ID  Name                    ID  Name
 *  1.  A1.                 1.  A1.     
 *  2.  A1-1.                   2.  A1-1.
 *  3.  R1      -->         3.  R1
 *  4.  R11                 4.  R2
 *  5.  R2                  5.  R11
 */
CREATE FUNCTION dbo.udf_NaturalSortFormat(
    @string nvarchar(4000),
    @numberLength int = 10,
    @sameOrderChars char(50) = ''
)
RETURNS varchar(4000)
AS
BEGIN
    DECLARE @sortString varchar(4000),
        @numStartIndex int,
        @numEndIndex int,
        @padLength int,
        @totalPadLength int,
        @i int,
        @sameOrderCharsLen int;

    SELECT 
        @totalPadLength = 0,
        @string = RTRIM(LTRIM(@string)),
        @sortString = @string,
        @numStartIndex = PATINDEX('%[0-9]%', @string),
        @numEndIndex = 0,
        @i = 1,
        @sameOrderCharsLen = LEN(@sameOrderChars);

    -- Replace all char that has to have the same order by a space.
    WHILE (@i <= @sameOrderCharsLen)
    BEGIN
        SET @sortString = REPLACE(@sortString, SUBSTRING(@sameOrderChars, @i, 1), ' ');
        SET @i = @i + 1;
    END

    -- Pad numbers with zeros.
    WHILE (@numStartIndex <> 0)
    BEGIN
        SET @numStartIndex = @numStartIndex + @numEndIndex;
        SET @numEndIndex = @numStartIndex;

        WHILE(PATINDEX('[0-9]', SUBSTRING(@string, @numEndIndex, 1)) = 1)
        BEGIN
            SET @numEndIndex = @numEndIndex + 1;
        END

        SET @numEndIndex = @numEndIndex - 1;

        SET @padLength = @numberLength - (@numEndIndex + 1 - @numStartIndex);

        IF @padLength < 0
        BEGIN
            SET @padLength = 0;
        END

        SET @sortString = STUFF(
            @sortString,
            @numStartIndex + @totalPadLength,
            0,
            REPLICATE('0', @padLength)
        );

        SET @totalPadLength = @totalPadLength + @padLength;
        SET @numStartIndex = PATINDEX('%[0-9]%', RIGHT(@string, LEN(@string) - @numEndIndex));
    END

    RETURN @sortString;
END

GO

@MarkSteudel試してみて、自分でテストする必要があります。さらに悪いことに、フォーマットされた値を常にキャッシュすることができます。フィールドにインデックスを付けることもできるので、これはおそらく大きなテーブルに対して行うことです。
plalx 2017

15

MySQLはこの種の「自然ソート」を許可していないため、目的のデータを取得するための最良の方法は、上記のようにデータセットを分割するか(IDフィールドを分離するなど)失敗することです。つまり、タイトル以外の要素、データベース内のインデックス付き要素(日付、データベースに挿入されたIDなど)に基づいて並べ替えを実行します。

dbに並べ替えを行わせると、ほとんどの場合、選択したプログラミング言語に大きなデータセットを読み込んで並べ替えるよりも速くなります。したがって、ここでdbスキーマを制御できる場合は、追加を検討してください。上記のようにフィールドを簡単に並べ替えることで、長期的には面倒な作業やメンテナンスを大幅に節約できます。

「自然順」を追加するリクエストは、MySQLのバグディスカッションフォーラムでときどき出てきます。多くのソリューションは、データの特定の部分を取り除きORDER BY、クエリの一部にキャストすることを中心に展開しています。

SELECT * FROM table ORDER BY CAST(mid(name, 6, LENGTH(c) -5) AS unsigned) 

この種のソリューションは、上記のファイナルファンタジーの例で機能するように作成できますが、特に柔軟性がなく、「ウォーハンマー40,000」や「ジェームズボンド007」などのデータセットに完全に拡張される可能性は低いです。 。


9

ですから、あなたが満足のいく答えを見つけたとは思いますが、私はしばらくの間この問題に苦しんでいました、そして私たちは以前にそれがSQLで合理的にうまくできないと判断し、JSONでjavascriptを使用する必要がありましたアレイ。

これが私がSQLを使ってそれを解決した方法です。うまくいけば、これは他の人に役立つでしょう:

私は次のようなデータを持っていました:

シーン1
シーン1A
シーン1B
シーン2A
シーン3
..。
シーン101
シーンXXA1
シーンXXA2

私は実際には物事を「キャスト」しませんでしたが、それもうまくいったかもしれません。

最初にデータで変更されていない部分(この場合は「シーン」)を交換し、次にLPADを実行して物事を並べました。これにより、アルファ文字列と番号付き文字列を適切に並べ替えることができます。

私のORDER BY条項は次のようになります。

ORDER BY LPAD(REPLACE(`table`.`column`,'Scene ',''),10,'0')

明らかに、これはそれほど均一ではなかった元の問題には役立ちませんが、これはおそらく他の多くの関連する問題でも機能すると思いますので、そこに置いてください。


LPAD()ヒントは非常に有用でした。並べ替える単語や数字があり、LPAD自然に数字を並べ替えることができました。そして、CONCAT私は非数字を無視します。このような私のクエリルックス(別名ソートする列です):IF(CONCAT("",alias*1)=alias, LPAD(alias,5,"0"), alias) ASC;👍
甲斐ノアック

6
  1. テーブルにソートキー(ランク)を追加します。 ORDER BY rank

  2. 「リリース日」列を利用します。 ORDER BY release_date

  3. SQLからデータを抽出するときは、オブジェクトに並べ替えを実行させます。たとえば、Setに抽出する場合は、TreeSetにし、データモデルにComparableを実装させ、ここで自然な並べ替えアルゴリズムを実行します(使用している場合は挿入並べ替えで十分です)。コレクションのない言語)モデルを作成してコレクションに挿入するときにSQLから行を1つずつ読み取るため)


5

リチャード・トスからの最高の反応について https://stackoverflow.com/a/12257917/4052357

2バイト(またはそれ以上)の文字と数字を含むUTF8エンコード文字列に注意してください。

12 南新宿

MySQLのを使用LENGTH()してudf_NaturalSortFormat機能することは、文字列のバイト長を返し、正しくない、代わりに使用されますCHAR_LENGTH()これをすると正しい文字長が返されます。

私の場合、使用するLENGTH()とクエリが完了せず、MySQLのCPU使用率が100%になります。

DROP FUNCTION IF EXISTS `udf_NaturalSortFormat`;
DELIMITER ;;
CREATE FUNCTION `udf_NaturalSortFormat` (`instring` varchar(4000), `numberLength` int, `sameOrderChars` char(50)) 
RETURNS varchar(4000)
LANGUAGE SQL
DETERMINISTIC
NO SQL
SQL SECURITY INVOKER
BEGIN
    DECLARE sortString varchar(4000);
    DECLARE numStartIndex int;
    DECLARE numEndIndex int;
    DECLARE padLength int;
    DECLARE totalPadLength int;
    DECLARE i int;
    DECLARE sameOrderCharsLen int;

    SET totalPadLength = 0;
    SET instring = TRIM(instring);
    SET sortString = instring;
    SET numStartIndex = udf_FirstNumberPos(instring);
    SET numEndIndex = 0;
    SET i = 1;
    SET sameOrderCharsLen = CHAR_LENGTH(sameOrderChars);

    WHILE (i <= sameOrderCharsLen) DO
        SET sortString = REPLACE(sortString, SUBSTRING(sameOrderChars, i, 1), ' ');
        SET i = i + 1;
    END WHILE;

    WHILE (numStartIndex <> 0) DO
        SET numStartIndex = numStartIndex + numEndIndex;
        SET numEndIndex = numStartIndex;

        WHILE (udf_FirstNumberPos(SUBSTRING(instring, numEndIndex, 1)) = 1) DO
            SET numEndIndex = numEndIndex + 1;
        END WHILE;

        SET numEndIndex = numEndIndex - 1;

        SET padLength = numberLength - (numEndIndex + 1 - numStartIndex);

        IF padLength < 0 THEN
            SET padLength = 0;
        END IF;

        SET sortString = INSERT(sortString, numStartIndex + totalPadLength, 0, REPEAT('0', padLength));

        SET totalPadLength = totalPadLength + padLength;
        SET numStartIndex = udf_FirstNumberPos(RIGHT(instring, CHAR_LENGTH(instring) - numEndIndex));
    END WHILE;

    RETURN sortString;
END
;;

psこれをコメントとしてオリジナルに追加したと思いますが、(まだ)十分な評判がありません


4

数字のすべての文字列が固定長にゼロで埋め込まれている「ソートキー」のフィールドを追加し、代わりにそのフィールドでソートします。

長い桁の文字列がある場合、別の方法は、各桁の文字列の前に桁数(固定幅、ゼロが埋め込まれている)を追加することです。たとえば、連続する数字が99桁を超えない場合、「Super Blast 10 Ultra」の場合、並べ替えキーは「Super Blast0210Ultra」になります。


4

:順に
0
1
2
10
23
101
205
1000年 AAC Bの casdsadsaの CSS





このクエリを使用します:

選択する 
    column_name 
から 
    table_name 
ORDER BY
    column_name REGEXP '^ \ d * [^ \ da-z&\。\' \-\ "\!\ @ \#\ $ \%\ ^ \ * \(\)\; \:\\、\?\ / \〜\ `\ | \ _ \-] 'DESC、 
    column_name + 0、 
    column_name;

あなたがそのような中で値を追加する場合ダウン残念ながら、この休憩a1a2a11、等...
random_user_name

4

車輪の再発明をしたくない場合や、機能しないコードがたくさんあるという頭痛の種がある場合は、Drupal Natural Sortを使用してください... zip形式のSQL(MySQLまたはPostgre)を実行するだけです。クエリを作成するときは、次を使用して注文するだけです。

... ORDER BY natsort_canon(column_name, 'natural')

これのおかげで、私はあらゆる種類の解決策を試してきました(私がそこで何をしたかわかりましたか?)が、それらのどれも私が持っていたすべてのデータに対して実際に機能しませんでした。drupal機能は魅力のように機能しました。投稿していただきありがとうございます。
ベンヒッチコック

最後に、この作品が、種類数(AZ、その後0-9)
realgt

4

別のオプションは、mysqlからデータをプルした後にメモリ内でソートを行うことです。パフォーマンスの観点からは最善の選択肢ではありませんが、巨大なリストを並べ替えない場合は問題ありません。

ジェフの投稿を見ると、使用している可能性のある言語に対応するアルゴリズムがたくさん見つかります。 人間のための分類:自然順


2

「ソート列」を動的に作成することもできます。

SELECT name, (name = '-') boolDash, (name = '0') boolZero, (name+0 > 0) boolNum 
FROM table 
ORDER BY boolDash DESC, boolZero DESC, boolNum DESC, (name+0), name

そうすれば、並べ替えるグループを作成できます。

私のクエリでは、すべての前に「-」、次に数字、次にテキストが必要でした。これは次のような結果になる可能性があります:

-
0    
1
2
3
4
5
10
13
19
99
102
Chair
Dog
Table
Windows

そうすれば、データを追加するときにソート列を正しい順序で維持する必要がありません。必要に応じて、並べ替え順序を変更することもできます。


これがどれほどのパフォーマンスになるかはわかりません。いつもご不便をおかけすることなくご利用いただいております。私のデータベースは大きくありません。
アントワーヌ2013年

1

PHPを使用している場合は、phpで自然ソートを実行できます。

$keys = array();
$values = array();
foreach ($results as $index => $row) {
   $key = $row['name'].'__'.$index; // Add the index to create an unique key.
   $keys[] = $key;
   $values[$key] = $row; 
}
natsort($keys);
$sortedValues = array(); 
foreach($keys as $index) {
  $sortedValues[] = $values[$index]; 
}

MySQLが将来のバージョンで自然ソートを実装することを願っていますが、機能リクエスト(#1588)は2003年から公開されているので、私は息を止めません。


理論的にはそれは可能ですが、最初にすべてのデータベースレコードをWebサーバーに読み込む必要があります。
BlaM 2011年

または、次のことを検討してください:(usort($mydata, function ($item1, $item2) { return strnatcmp($item1['key'], $item2['key']); });連想配列があり、キーで並べ替えます。)参照:stackoverflow.com/q/12426825/1066234
KaiNoack19年

1

@ plaix / Richard Toth / Luke Hoggettの最良の応答の簡略化された非udfバージョンは、フィールドの最初の整数に対してのみ機能します。

SELECT name,
LEAST(
    IFNULL(NULLIF(LOCATE('0', name), 0), ~0),
    IFNULL(NULLIF(LOCATE('1', name), 0), ~0),
    IFNULL(NULLIF(LOCATE('2', name), 0), ~0),
    IFNULL(NULLIF(LOCATE('3', name), 0), ~0),
    IFNULL(NULLIF(LOCATE('4', name), 0), ~0),
    IFNULL(NULLIF(LOCATE('5', name), 0), ~0),
    IFNULL(NULLIF(LOCATE('6', name), 0), ~0),
    IFNULL(NULLIF(LOCATE('7', name), 0), ~0),
    IFNULL(NULLIF(LOCATE('8', name), 0), ~0),
    IFNULL(NULLIF(LOCATE('9', name), 0), ~0)
) AS first_int
FROM table
ORDER BY IF(first_int = ~0, name, CONCAT(
    SUBSTR(name, 1, first_int - 1),
    LPAD(CAST(SUBSTR(name, first_int) AS UNSIGNED), LENGTH(~0), '0'),
    SUBSTR(name, first_int + LENGTH(CAST(SUBSTR(name, first_int) AS UNSIGNED)))
)) ASC

1

私はいくつかの解決策を試しましたが、実際には非常に簡単です。

SELECT test_column FROM test_table ORDER BY LENGTH(test_column) DESC, test_column DESC

/* 
Result 
--------
value_1
value_2
value_3
value_4
value_5
value_6
value_7
value_8
value_9
value_10
value_11
value_12
value_13
value_14
value_15
...
*/

1
形式で数値を並べ替えるのに非常にうまく機能します23-4244。ありがとう:)
Pyton 2016

1
番号の前の文字列はすべて同じであるため、このテストデータでのみ機能します。z_99そこに値を貼り付けてみると、一番上に表示されますが、z後に表示されvます。
サミュエルネフ2017

@SamuelNeff SQLを参照してください:ORDER BY LENGTH(test_column)DESC、test_columnDESCそうです。最初に列の長さでソートされるためです。これは、テーブルのプレフィックスグループの並べ替えに適しています。そうしないと、「test_columnDESC」だけでは並べ替えることができません
Tarik

1

私がここで(そして重複した質問で)見る他の多くの答えは基本的に非常にしか機能しません特殊にフォーマットされたデータ、たとえば完全に数字である文字列、または固定長のアルファベット接頭辞がある文字列ます。これは一般的なケースでは機能しません。

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);  ### 0 means process all numbers - resulting sort key might be quite long for certain inputs

ただし、大きなテーブルを効率的に並べ替えるには、並べ替えキーを別の列に事前に保存しておくことをお勧めします(おそらくインデックスが付いています)。

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
/****
  Converts numbers in the input string s into a format such that sorting results in a nat-sort.
  Numbers of up to 359 digits (before the decimal point, if one is present) are supported.  Sort results are undefined if the input string contains numbers longer than this.
  For n>0, only the first n numbers in the input string will be converted for nat-sort (so strings that differ only after the first n numbers will not nat-sort amongst themselves).
  Total sort-ordering is preserved, i.e. if s1!=s2, then NatSortKey(s1,n)!=NatSortKey(s2,n), for any given n.
  Numbers may contain ',' as a thousands separator, and '.' as a decimal point.  To reverse these (as appropriate for some European locales), the code would require modification.
  Numbers preceded by '+' sort with numbers not preceded with either a '+' or '-' sign.
  Negative numbers (preceded with '-') sort before positive numbers, but are sorted in order of ascending absolute value (so -7 sorts BEFORE -1001).
  Numbers with leading zeros sort after the same number with no (or fewer) leading zeros.
  Decimal-part-only numbers (like .75) are recognised, provided the decimal point is not immediately preceded by either another '.', or by a letter-type character.
  Numbers with thousand separators sort after the same number without them.
  Thousand separators are only recognised in numbers with no leading zeros that don't immediately follow a ',', and when they format the number correctly.
  (When not recognised as a thousand separator, a ',' will instead be treated as separating two distinct numbers).
  Version-number-like sequences consisting of 3 or more numbers separated by '.' are treated as distinct entities, and each component number will be nat-sorted.
  The entire entity will sort after any number beginning with the first component (so e.g. 10.2.1 sorts after both 10 and 10.995, but before 11)
  Note that The first number component in an entity like this is also permitted to contain thousand separators.

  To achieve this, numbers within the input string are prefixed and suffixed according to the following format:
  - The number is prefixed by a 2-digit base-36 number representing its length, excluding leading zeros.  If there is a decimal point, this length only includes the integer part of the number.
  - A 3-character suffix is appended after the number (after the decimals if present).
    - The first character is a space, or a '+' sign if the number was preceded by '+'.  Any preceding '+' sign is also removed from the front of the number.
    - This is followed by a 2-digit base-36 number that encodes the number of leading zeros and whether the number was expressed in comma-separated form (e.g. 1,000,000.25 vs 1000000.25)
    - The value of this 2-digit number is: (number of leading zeros)*2 + (1 if comma-separated, 0 otherwise)
  - For version number sequences, each component number has the prefix in front of it, and the separating dots are removed.
    Then there is a single suffix that consists of a ' ' or '+' character, followed by a pair base-36 digits for each number component in the sequence.

  e.g. here is how some simple sample strings get converted:
  'Foo055' --> 'Foo0255 02'
  'Absolute zero is around -273 centigrade' --> 'Absolute zero is around -03273 00 centigrade'
  'The $1,000,000 prize' --> 'The $071000000 01 prize'
  '+99.74 degrees' --> '0299.74+00 degrees'
  'I have 0 apples' --> 'I have 00 02 apples'
  '.5 is the same value as 0000.5000' --> '00.5 00 is the same value as 00.5000 08'
  'MariaDB v10.3.0018' --> 'MariaDB v02100130218 000004'

  The restriction to numbers of up to 359 digits comes from the fact that the first character of the base-36 prefix MUST be a decimal digit, and so the highest permitted prefix value is '9Z' or 359 decimal.
  The code could be modified to handle longer numbers by increasing the size of (both) the prefix and suffix.
  A higher base could also be used (by replacing CONV() with a custom function), provided that the collation you are using sorts the "digits" of the base in the correct order, starting with 0123456789.
  However, while the maximum number length may be increased this way, note that the technique this function uses is NOT applicable where strings may contain numbers of unlimited length.

  The function definition does not specify the charset or collation to be used for string-type parameters or variables:  The default database charset & collation at the time the function is defined will be used.
  This is to make the function code more portable.  However, there are some important restrictions:

  - Collation is important here only when comparing (or storing) the output value from this function, but it MUST order the characters " +0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ" in that order for the natural sort to work.
    This is true for most collations, but not all of them, e.g. in Lithuanian 'Y' comes before 'J' (according to Wikipedia).
    To adapt the function to work with such collations, replace CONV() in the function code with a custom function that emits "digits" above 9 that are characters ordered according to the collation in use.

  - For efficiency, the function code uses LENGTH() rather than CHAR_LENGTH() to measure the length of strings that consist only of digits 0-9, '.', and ',' characters.
    This works for any single-byte charset, as well as any charset that maps standard ASCII characters to single bytes (such as utf8 or utf8mb4).
    If using a charset that maps these characters to multiple bytes (such as, e.g. utf16 or utf32), you MUST replace all instances of LENGTH() in the function definition with CHAR_LENGTH()

  Length of the output:

  Each number converted adds 5 characters (2 prefix + 3 suffix) to the length of the string. n is the maximum count of numbers to convert;
  This parameter is provided as a means to limit the maximum output length (to input length + 5*n).
  If you do not require the total-ordering property, you could edit the code to use suffixes of 1 character (space or plus) only; this would reduce the maximum output length for any given n.
  Since a string of length L has at most ((L+1) DIV 2) individual numbers in it (every 2nd character a digit), for n<=0 the maximum output length is (inputlength + 5*((inputlength+1) DIV 2))
  So for the current input length of 100, the maximum output length is 350.
  If changing the input length, the output length must be modified according to the above formula.  The DECLARE statements for x,y,r, and suf must also be modified, as the code comments indicate.
****/
  DECLARE x,y varchar(100);            # need to be same length as input s
  DECLARE r varchar(350) DEFAULT '';   # return value:  needs to be same length as return type
  DECLARE suf varchar(101);   # suffix for a number or version string. Must be (((inputlength+1) DIV 2)*2 + 1) chars to support version strings (e.g. '1.2.33.5'), though it's usually just 3 chars. (Max version string e.g. 1.2. ... .5 has ((length of input + 1) DIV 2) numeric components)
  DECLARE i,j,k int UNSIGNED;
  IF n<=0 THEN SET n := -1; END IF;   # n<=0 means "process all numbers"
  LOOP
    SET i := REGEXP_INSTR(s,'\\d');   # find position of next digit
    IF i=0 OR n=0 THEN RETURN CONCAT(r,s); END IF;   # no more numbers to process -> we're done
    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;   # Allow decimal number (but not version string) to begin with a '.', provided preceding char is neither another '.', nor a member of the unicode character classes: "Alphabetic", "Letter", "Block=Letterlike Symbols" "Number", "Mark", "Join_Control"
      IF i>1 AND SUBSTRING(s,i-1,1)='+' THEN SET suf := '+', j := i-1; ELSE SET j := i; END IF;   # move any preceding '+' into the suffix, so equal numbers with and without preceding "+" signs sort together
      SET r := CONCAT(r,SUBSTRING(s,1,j-1)); SET s = SUBSTRING(s,i);   # add everything before the number to r and strip it from the start of s; preceding '+' is dropped (not included in either r or s)
    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++)*+'));   # capture the number + following decimals (including multiple consecutive '.<digits>' sequences)
    SET s := SUBSTRING(s,LENGTH(x)+1);   # NOTE: LENGTH() can be safely used instead of CHAR_LENGTH() here & below PROVIDED we're using a charset that represents digits, ',' and '.' characters using single bytes (e.g. latin1, utf8)
    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;   # move any following decimals into y
    SET i := LENGTH(x);
    SET x := REPLACE(x,',','');
    SET j := LENGTH(x);
    SET x := TRIM(LEADING '0' FROM x);   # strip leading zeros
    SET k := LENGTH(x);
    SET suf := CONCAT(suf,LPAD(CONV(LEAST((j-k)*2,1294) + IF(i=j,0,1),10,36),2,'0'));   # (j-k)*2 + IF(i=j,0,1) = (count of leading zeros)*2 + (1 if there are thousands-separators, 0 otherwise)  Note the first term is bounded to <= base-36 'ZY' as it must fit within 2 characters
    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);   # k = count of digits in number, bounded to be <= '9Z' base-36
    ELSE   # encode a version number (like 3.12.707, etc)
      SET r := CONCAT(r,LPAD(CONV(LEAST(k,359),10,36),2,'0'),x);   # k = count of digits in number, bounded to be <= '9Z' base-36
      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);   # strip leading zeros
        SET k := LENGTH(x);
        SET r := CONCAT(r,LPAD(CONV(LEAST(k,359),10,36),2,'0'),x);   # k = count of digits in number, bounded to be <= '9Z' base-36
        SET suf := CONCAT(suf,LPAD(CONV(LEAST((j-k)*2,1294),10,36),2,'0'));   # (j-k)*2 = (count of leading zeros)*2, bounded to fit within 2 base-36 digits
        SET n := n-1;
      END WHILE;
      SET r := CONCAT(r,y,suf);
    END IF;
  END LOOP;
END
$$
delimiter ;

私はMySQLの初心者で、これを試しました。このエラーが発生しました:「#1305-FUNCTIONmydatabase.REGEXP_INSTRが存在しません」。何か案が?
ジョン

そこにいる他の初心者のために。MySQL8.0をインストールしていません。これは、REGEXP_INSTR(およびその他のREGEXPのもの)に必要です。
ジョン

NatSortKeyの重大なバグを修正しました:誤った正規表現文字がありました。この関数を自分で使用したことがある場合は、コードを更新してください。
ドゥーイン


0

タイトルに番号としてのバージョンしかない場合の簡単な例を次に示します。

ORDER BY CAST(REGEXP_REPLACE(title, "[a-zA-Z]+", "") AS INT)';

それ以外の場合は、パターンを使用する場合は単純なSQLを使用できます(このパターンはバージョンの前に#を使用します)。

create table titles(title);

insert into titles (title) values 
('Final Fantasy'),
('Final Fantasy #03'),
('Final Fantasy #11'),
('Final Fantasy #10'),
('Final Fantasy #2'),
('Bond 007 ##2'),
('Final Fantasy #01'),
('Bond 007'),
('Final Fantasy #11}');

select REGEXP_REPLACE(title, "#([0-9]+)", "\\1") as title from titles
ORDER BY REGEXP_REPLACE(title, "#[0-9]+", ""),
CAST(REGEXP_REPLACE(title, ".*#([0-9]+).*", "\\1") AS INT);     
+-------------------+
| title             |
+-------------------+
| Bond 007          |
| Bond 007 #2       |
| Final Fantasy     |
| Final Fantasy 01  |
| Final Fantasy 2   |
| Final Fantasy 03  |
| Final Fantasy 10  |
| Final Fantasy 11  |
| Final Fantasy 11} |
+-------------------+
8 rows in set, 2 warnings (0.001 sec)

必要に応じて他のパターンを使用できます。たとえば、映画「I'm#1」と「I'm#1 part 2」がある場合は、バージョンをラップします(例:「ファイナルファンタジー{11}」)。


-4

私はこのトピックが古くからあることを知っていますが、私はこれを行う方法を見つけたと思います:

SELECT * FROM `table` ORDER BY 
CONCAT(
  GREATEST(
    LOCATE('1', name),
    LOCATE('2', name),
    LOCATE('3', name),
    LOCATE('4', name),
    LOCATE('5', name),
    LOCATE('6', name),
    LOCATE('7', name),
    LOCATE('8', name),
    LOCATE('9', name)
   ),
   name
) ASC

それをスクラップして、それは次のセットを間違ってソートしました(それは役に立たない笑):

ファイナルファンタジー1ファイナルファンタジー2ファイナルファンタジー5ファイナルファンタジー7ファイナルファンタジー7:アドベントチルドレンファイナルファンタジー12ファイナルファンタジー112 FF1 FF2


3
なぜこの答えを削除しないのですか?このバッジを取得します
m47730 2017年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.