سؤال

هل هناك طريقة أنيقة للحصول على فرز طبيعي وفعال في قاعدة بيانات MySQL؟

على سبيل المثال إذا كان لدي مجموعة البيانات هذه:

  • الخيال النهائي
  • فاينل فانتسي 4
  • فاينل فانتسي 10
  • فاينل فانتسي 12
  • فاينل فانتسي 12:سلاسل بروماثيا
  • مغامرة فاينل فانتسي
  • أصول الخيال النهائي
  • تكتيكات فاينل فانتسي

اي شيء اخر رائع الحل هو تقسيم أسماء الألعاب إلى مكوناتها

  • عنوان:"فاينل فانتسي"
  • رقم: "12"
  • الترجمة:"سلاسل بروماثيا"

للتأكد من أنهم يخرجون بالترتيب الصحيح؟(10 بعد 4 وليس قبل 2).

يعد القيام بذلك بمثابة ألم في ** لأنه بين الحين والآخر هناك لعبة أخرى تكسر آلية تحليل عنوان اللعبة (على سبيل المثال."مطرقة حرب 40000"، "جيمس بوند 007")

هل كانت مفيدة؟

المحلول

أعتقد أن هذا هو سبب تصنيف الكثير من الأشياء حسب تاريخ الإصدار.

قد يكون الحل هو إنشاء عمود آخر في الجدول الخاص بك لـ "SortKey".يمكن أن تكون هذه نسخة منقحة من العنوان تتوافق مع النمط الذي قمت بإنشائه لسهولة الفرز أو العداد.

نصائح أخرى

هنا حل سريع:

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

فقط وجدت هذا:

SELECT names FROM your_table ORDER BY games + 0 ASC

يقوم بالفرز الطبيعي عندما تكون الأرقام في المقدمة، وقد يعمل في المنتصف أيضًا.

نفس الوظيفة التي نشرها @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, ".")

لا تسمح MySQL بهذا النوع من "الفرز الطبيعي"، لذا يبدو أن أفضل طريقة للحصول على ما تبحث عنه هي تقسيم إعداد بياناتك كما هو موضح أعلاه (حقل معرف منفصل، وما إلى ذلك)، أو الفشل قم بإجراء فرز بناءً على عنصر غير عنوان، أو عنصر مفهرس في قاعدة البيانات الخاصة بك (التاريخ، المعرف المدرج في قاعدة البيانات، وما إلى ذلك).

إن قيام قاعدة البيانات بالفرز نيابةً عنك سيكون دائمًا أسرع من قراءة مجموعات كبيرة من البيانات في لغة البرمجة التي تختارها وفرزها هناك، لذلك إذا كان لديك أي تحكم على الإطلاق في مخطط قاعدة البيانات هنا، فابحث في إضافة الحقول التي تم فرزها بسهولة كما هو موضح أعلاه، ستوفر عليك الكثير من المتاعب والصيانة على المدى الطويل.

تظهر طلبات إضافة "الفرز الطبيعي" من وقت لآخر على أخطاء MySQL و منتديات المناقشة, ، وتدور العديد من الحلول حول تجريد أجزاء معينة من بياناتك وإحالتها إلى ORDER BY جزء من الاستعلام، على سبيل المثال

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

يمكن تقديم هذا النوع من الحلول للعمل على مثال Final Fantasy أعلاه، لكنه ليس مرنًا بشكل خاص ومن غير المرجح أن يمتد بشكل واضح إلى مجموعة بيانات بما في ذلك، على سبيل المثال، "Warhammer 40,000" و"James Bond 007" على ما أخشى. .

لقد كتبت هذه الوظيفة ل 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

لذلك، على الرغم من أنني أعلم أنك وجدت إجابة مرضية، فقد كنت أعاني من هذه المشكلة لفترة من الوقت، وقد قررنا مسبقًا أنه لا يمكن القيام بذلك بشكل جيد في SQL وكان علينا استخدام جافا سكريبت على JSON مجموعة مصفوفة.

إليك كيفية حل المشكلة باستخدام SQL فقط.نأمل أن يكون هذا مفيدًا للآخرين:

كان لدي بيانات مثل:

Scene 1
Scene 1A
Scene 1B
Scene 2A
Scene 3
...
Scene 101
Scene XXA1
Scene XXA2

في الواقع لم "ألقي" الأشياء على الرغم من أنني أفترض أن ذلك ربما نجح أيضًا.

قمت أولاً باستبدال الأجزاء التي لم تتغير في البيانات، في هذه الحالة "المشهد"، ثم قمت بإجراء LPAD لترتيب الأمور.يبدو أن هذا يسمح جيدًا لسلاسل ألفا بالفرز بشكل صحيح بالإضافة إلى السلاسل المرقمة.

لي ORDER BY تبدو الجملة كالتالي:

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

من الواضح أن هذا لا يساعد في حل المشكلة الأصلية التي لم تكن موحدة تمامًا - لكنني أتخيل أن هذا قد ينجح في العديد من المشكلات الأخرى ذات الصلة، لذا ضعها هناك.

  1. أضف مفتاح الفرز (الرتبة) في الجدول الخاص بك. ORDER BY rank

  2. استخدم عمود "تاريخ الإصدار". ORDER BY release_date

  3. عند استخراج البيانات من SQL، اجعل كائنك يقوم بالفرز، على سبيل المثال، إذا تم الاستخراج في مجموعة، فاجعله TreeSet، واجعل نموذج البيانات الخاص بك ينفذ Comparable ويقوم بتفعيل خوارزمية الفرز الطبيعي هنا (سيكون فرز الإدراج كافيًا إذا كنت تستخدم لغة بدون مجموعات) حيث ستقرأ الصفوف من SQL واحدًا تلو الآخر أثناء إنشاء النموذج الخاص بك وإدراجه في المجموعة)

فيما يتعلق بأفضل رد من ريتشارد توث https://stackoverflow.com/a/12257917/4052357

احترس من السلاسل المشفرة بترميز UTF8 والتي تحتوي على أحرف وأرقام بحجم 2 بايت (أو أكثر)، على سبيل المثال.

12 南新宿

باستخدام MySQL LENGTH() في udf_NaturalSortFormat ستُرجع الدالة طول البايت للسلسلة وتكون غير صحيحة، فبدلاً من ذلك استخدمها CHAR_LENGTH() والتي سوف تعيد طول الحرف الصحيح.

في حالتي باستخدام LENGTH() تسبب في عدم اكتمال الاستعلامات مطلقًا مما أدى إلى استخدام وحدة المعالجة المركزية بنسبة 100% لـ MySQL

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
;;

ملاحظة.كنت سأضيف هذا كتعليق على النص الأصلي ولكن ليس لدي سمعة كافية (حتى الآن)

لكي يطلب:
0
1
2
10
23
101
205
1000
أ
الجميح للسيارات
ب
casdsadsa
المغلق

استخدم هذا الاستعلام:

SELECT 
    column_name 
FROM 
    table_name 
ORDER BY
    column_name REGEXP '^\d*[^\da-z&\.\' \-\"\!\@\#\$\%\^\*\(\)\;\:\\,\?\/\~\`\|\_\-]' DESC, 
    column_name + 0, 
    column_name;

إذا كنت لا ترغب في إعادة اختراع العجلة أو كنت تعاني من صداع بسبب وجود الكثير من التعليمات البرمجية التي لا تعمل، فما عليك سوى استخدام دروبال الفرز الطبيعي ...ما عليك سوى تشغيل SQL الذي يأتي مضغوطًا (MySQL أو Postgre)، وهذا كل شيء.عند إجراء استعلام، ما عليك سوى الطلب باستخدام:

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

هناك خيار آخر وهو إجراء الفرز في الذاكرة بعد سحب البيانات من الخلية.على الرغم من أنه لن يكون الخيار الأفضل من وجهة نظر الأداء، إلا أنه إذا كنت لا تقوم بفرز قوائم ضخمة، فيجب أن تكون على ما يرام.

إذا ألقيت نظرة على مشاركة جيف، يمكنك العثور على الكثير من الخوارزميات لأي لغة قد تعمل بها.الفرز للبشر:ترتيب الفرز الطبيعي

أضف حقلاً لـ "مفتاح الفرز" الذي يحتوي على كافة سلاسل الأرقام المبطنة صفرًا بطول ثابت ثم قم بالفرز في هذا الحقل بدلاً من ذلك.

إذا كان لديك سلاسل طويلة من الأرقام، فهناك طريقة أخرى تتمثل في إضافة عدد من الأرقام (عرض ثابت، مبطن صفر) لكل سلسلة من الأرقام.على سبيل المثال، إذا لم يكن لديك أكثر من 99 رقمًا متتاليًا، فسيكون مفتاح الفرز بالنسبة لـ "Super Blast 10 Ultra" هو "Super Blast 0210 Ultra".

يمكنك أيضًا إنشاء "عمود الفرز" بطريقة ديناميكية:

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

وبهذه الطريقة لن تضطر إلى الحفاظ على عمود الفرز بالترتيب الصحيح أثناء إضافة البيانات.يمكنك أيضًا تغيير ترتيب الفرز الخاص بك اعتمادًا على ما تحتاجه.

لقد جربت العديد من الحلول ولكن في الواقع الأمر بسيط للغاية:

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
...
*/

إذا كنت تستخدم 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، لذلك لن أحبس أنفاسي.

نسخة مبسطة غير udf لأفضل استجابة لـ @plaix/Richard Toth/Luke Hoggett، والتي تعمل فقط مع العدد الصحيح الأول في الحقل، هي

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

أيضا هناك ناتسورت.الغرض منه هو أن يكون جزءًا من أ دروبال البرنامج المساعد, ، لكنه يعمل بشكل جيد بمفرده.

أعلم أن هذا الموضوع قديم ولكن أعتقد أنني وجدت طريقة للقيام بذلك:

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

قم بإلغاء ذلك، فقد قام بفرز المجموعة التالية بشكل غير صحيح (إنها عديمة الفائدة لول):

Final Fantasy 1 Final Fantasy 2 Final Fantasy 5 Final Fantasy 7 Final Fantasy 7:Advent Kids Final Fantasy 12 Final Fantasy 112 FF1 FF2

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top