90

MySQL データベースで効率的で自然な並べ替えを行うエレガントな方法はありますか?

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

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

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

  • タイトル:「ファイナルファンタジー」
  • 番号:「12」
  • 副題:「プロマシアの鎖」

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

これを行うのは苦痛です**。ゲーム タイトルを解析するメカニズムを破る別のゲームが時々あるからです (例: "Warhammer 40,000"、"James Bond 007")。

4

22 に答える 22

97

ここに簡単な解決策があります:

SELECT alphanumeric, 
       integer
FROM sorting_test
ORDER BY LENGTH(alphanumeric), alphanumeric
于 2011-06-14T13:15:00.960 に答える
63

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

SELECT names FROM your_table ORDER BY games + 0 ASC

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

于 2009-01-19T16:44:11.527 に答える
57

@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, ".")
于 2012-09-04T06:30:55.137 に答える
22

発売日順に並んでいるものが多いのはそのためだと思います。

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

于 2008-09-30T15:44:44.480 に答える
17

しばらく前に、 MSSQL 2000用に次の関数を作成しました。

/**
 * 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
于 2011-04-07T14:52:28.903 に答える
15

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

ほとんどの場合、データベースに並べ替えを行わせることは、選択したプログラミング言語に大きなデータセットを読み込んで並べ替えるよりも高速になるため、ここでデータベーススキーマ全体を制御できる場合は、追加することを検討してください。上記のようにフィールドを簡単にソートできるため、長い目で見れば手間とメンテナンスを大幅に節約できます。

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

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

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

于 2008-09-30T15:48:35.300 に答える
9

それで、あなたが満足のいく答えを見つけたことは知っていますが、私はしばらくの間この問題に苦労していました。以前は、SQL ではうまく処理できないと判断しており、JSON で JavaScript を使用する必要がありました。配列。

これが、SQLを使用して解決した方法です。これが他の人に役立つことを願っています:

次のようなデータがありました。

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

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

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

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

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

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

于 2011-06-15T06:07:04.943 に答える
6
  1. テーブルにソート キー (ランク) を追加します。ORDER BY rank

  2. 「発売日」欄をご利用ください。ORDER BY release_date

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

于 2008-09-30T15:57:22.203 に答える
5

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

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

12 南新宿

MySQL のLENGTH()inudf_NaturalSortFormat関数を使用すると、文字列のバイト長が正しく返されず、代わりにCHAR_LENGTH()which を使用すると正しい文字長が返されます。

私の場合、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 元のコメントとしてこれを追加したはずですが、十分な評判がありません(まだ)

于 2014-12-05T02:47:01.880 に答える
4

注文するには:
0
1
2
10
23
101
205
1000
a
aac
b
casdsadsa
css

次のクエリを使用します。

選択する
    column_name
から
    テーブル名
オーダーバイ
    column_name REGEXP '^\d*[^\da-z&\.\' \-\"\!\@\#\$\%\^\*\(\)\;\:\\,\?\/ \~\`\|\_\-]' DESC,
    列名 + 0、
    column_name;
于 2016-06-13T15:16:55.110 に答える
4

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

数字の長い文字列がある場合、別の方法として、数字の各文字列の先頭に数字 (固定幅、ゼロ埋め込み) を追加する方法があります。たとえば、連続する数字が 99 桁を超えない場合、「スーパー ブラスト 10 ウルトラ」のソート キーは「スーパー ブラスト 0210 ウルトラ」になります。

于 2008-10-02T04:31:08.843 に答える
4

もう 1 つのオプションは、mysql からデータをプルした後にメモリ内でソートを行うことです。パフォーマンスの観点からは最適なオプションではありませんが、巨大なリストをソートしていない場合は問題ありません。

Jeff の投稿を見ると、使用している可能性のある言語のアルゴリズムがたくさん見つかります。 人間のための並べ替え : 自然な並べ替え順序

于 2008-09-30T15:54:00.513 に答える
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-10-17T12:35:27.323 に答える
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 年から公開されているので、息をのむことはありません。

于 2011-03-07T16:24:42.923 に答える
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
于 2015-03-02T11:33:19.863 に答える
0

natsortもあります。これはdrupalプラグインの一部となることを目的としていますが、スタンドアロンでは正常に機能します。

于 2011-06-20T07:58:55.437 に答える
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」がある場合、「Final Fantasy {11}」などのバージョンをラップします。

于 2020-11-12T21:58:23.083 に答える
-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

于 2012-11-12T15:11:34.217 に答える