MySQL 'Order By' - 正确排序字母数字混合的方法

92

我想按照以下顺序(数字1-12)对下面的数据进行排序:

1
2
3
4
5
6
7
8
9
10
11
12

然而,我的查询 - 使用order by xxxxx asc - 无论如何都是以第一个数字为首要因素进行排序:

1
10
11
12
2
3
4
5
6
7
8
9

有什么技巧可以使它更好地排序吗?

此外,为了充分说明,这可能是字母和数字的混合(虽然现在不是),例如:

A1
534G
G46A
100B
100A
100JE

等等....

谢谢!

更新:有人要求查询

select * from table order by name asc

相关链接:https://dev59.com/eKjka4cB1Zd3GeqPDsJ4 - Paul Spiegel
20个回答

154

人们使用不同的技巧来做到这一点。我通过谷歌找到了一些结果,每个都遵循不同的技巧。看一下它们:

编辑:

我刚刚添加了每个链接的代码,以供未来的访问者参考。

MySQL中的字母数字排序

给定输入

1A 1a 10A 9B 21C 1C 1D

期望输出

1A 1C 1D 1a 9B 10A 21C

查询

Bin Way
===================================
SELECT 
tbl_column, 
BIN(tbl_column) AS binray_not_needed_column
FROM db_table
ORDER BY binray_not_needed_column ASC , tbl_column ASC

-----------------------

Cast Way
===================================
SELECT 
tbl_column, 
CAST(tbl_column as SIGNED) AS casted_column
FROM db_table
ORDER BY casted_column ASC , tbl_column ASC

MySQL中的自然排序

给定输入

表:sorting_test
 -------------------------- -------------
| alphanumeric VARCHAR(75) | integer INT |
 -------------------------- -------------
| test1                    | 1           |
| test12                   | 2           |
| test13                   | 3           |
| test2                    | 4           |
| test3                    | 5           |
 -------------------------- -------------

期望输出

 -------------------------- -------------
| alphanumeric VARCHAR(75) | integer INT |
 -------------------------- -------------
| test1                    | 1           |
| test2                    | 4           |
| test3                    | 5           |
| test12                   | 2           |
| test13                   | 3           |
 -------------------------- -------------

查询

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

混合数字和字母的排序

给定输入

2a, 12, 5b, 5a, 10, 11, 1, 4b

期望输出

1, 2a, 4b, 5a, 5b, 10, 11, 12

查询

SELECT version
FROM version_sorting
ORDER BY CAST(version AS UNSIGNED), version;

13
在这篇文章中加入更全面的信息会更好。 - showdev
3
我已经包含了它,希望它会有所帮助 :) - affaz
这些对我都没用 :/有没有类似这样的列表推荐?https://pastebin.com/d4kXq6HS理想输出是:https://pastebin.com/kJ4Zc3XY - err
1
如果输入是A1、A10、B1、C11、D8,那该怎么办呢?没有任何技巧可行。 - Kira Katou
我刚刚发布了一个真正的、通用的自然排序函数,适用于MySQL,并且应该正确处理所有示例,以及更多其他情况。请查看我的回答“MySQL中的自然排序”:https://dev59.com/CnVC5IYBdhLWcg3w4Vf6#58154535 - Doin
如果我有 1A, Goofy, 1a, Final Fantasy,使用 CAST()BIN() 将返回错误的结果,它将变成 Final Fantasy, Goofy, 1A, 1a 而不是 1A, 1a, Final Fantasy, Goofy - vee

18

只需这样做:

SELECT * FROM table ORDER BY column `name`+0 ASC

在数字后添加 +0 表示:

0、 10、 11、 2、 3、 4

变成:

0、 2、 3、 4、 10、 11


2
这非常危险!在我的查询中,它运行良好,我投票支持了答案,但当我刷新后,它不再起作用!然后我继续随机刷新查询100次,同样的查询有时可用有时不可用!不要依赖这个!我的表末尾有一个数字,这是我的查询:SELECT TABLE_NAME FROM information_schema.TABLES WHERE TABLE_SCHEMA = 'my_database' AND TABLE_NAME LIKE '%my_table%' ORDER BY TABLE_NAME+0 DESC LIMIT 1。 - Tarik
5
可能是因为你正在使用 information_schema,它只提供了估计值,而没有完全聚合。 - AO_
1
@Andrew Odendaal的答案对我来说总是有效的,使用ORDER BY 'name'+0 ASC。 - IlludiumPu36

17

我不喜欢这个,但是这个会起作用。

order by lpad(name, 10, 0)  <-- assuming maximum string length is 10
                            <-- you can adjust to a bigger length if you want to

这对于我的场景USA-0027-1,USA-0027-2,USA-0027-10,USA-0027-12实际上是有效的。 - dbinott
这对我也起作用了,谢谢你,你救了我的另一天。 - Sumit Kumar Gupta
我也可以用这个方法。这是我最喜欢的解决方案。非常感谢! - Marc Ruef

17

我知道这篇文章已经关闭了,但我认为我的方法可能会对一些人有所帮助。所以在这里:

我的数据集非常类似,但是有点更加复杂。它包括数字、字母数字混合数据:

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

我希望 '-' 符号排在最前面,然后是数字,最后是文本。

所以我要这样做:

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
99
102
Chair
Dog
Table
Windows

整个思路是对 SELECT 语句进行简单的检查,并对结果进行排序。


我尝试在select语句中使用(name = '-') boolDash,但无法使其工作。但是,当我直接将name = '-'放入order by语句中时,它可以正常工作。 - Yep_It's_Me

11

这适用于以下数据类型: Data1, Data2,Data3......,Data21。这意味着"Data"字符串在所有行中是共同的。

对于ORDER BY ASC,它将完美地排序,对于ORDER BY DESC则不适用。

SELECT * FROM table_name ORDER BY LENGTH(column_name), column_name ASC;

ALTER TABLE 的类似查询语句是什么? - Ashish Kamble
哇...像魔术一样,点赞了。 - Harsh Tripathi

5
我在IT技术方面有一些不错的成果,我使用了

某些方法/工具


SELECT alphanumeric, integer FROM sorting_test ORDER BY CAST(alphanumeric AS UNSIGNED), alphanumeric ASC

3

这种类型的问题以前已经被问过。

你所说的排序类型被称为“自然排序”。 你想要进行排序的数据是字母数字混合的。 最好创建一个新列进行排序。

如需进一步帮助,请查看 mysql中的自然排序


3
如果您需要对一个没有任何标准格式的字母数字列进行排序。
SELECT * FROM table ORDER BY (name = '0') DESC, (name+0 > 0) DESC, name+0 ASC, name ASC

你可以使用额外的逻辑来支持非字母数字字符,以适应这个解决方案。

这是我在整个互联网上找到的唯一有效的解决方案。 - Lenin Zapata

2

与其尝试编写某个函数并减缓 SELECT 查询的速度,我想到了另一种方法...

在数据库中创建一个额外的字段,保存以下类的结果。当您插入新行时,运行将自然排序的字段值通过此类,并将其结果保存在额外的字段中。然后,不要按原始字段排序,而是按额外的字段排序。

String nsFieldVal = new NaturalSortString(getFieldValue(), 4).toString()

The above means:
- Create a NaturalSortString for the String returned from getFieldValue()
- Allow up to 4 bytes to store each character or number (4 bytes = ffff = 65535)

| field(32)  |  nsfield(161)                            |   
  a1            300610001

String sortString = new NaturalSortString(getString(), 4).toString()

字符串 sortString = new NaturalSortString(getString(), 4).toString()

import StringUtils;

/**
 * Creates a string that allows natural sorting in a SQL database
 * eg, 0 1 1a 2 3 3a 10 100 a a1 a1a1 b
 */
public class NaturalSortString {

    private String inStr;
    private int byteSize;
    private StringBuilder out = new StringBuilder();

    /**
     * A byte stores the hex value (0 to f) of a letter or number.
     * Since a letter is two bytes, the minimum byteSize is 2.
     *
     * 2 bytes = 00 - ff  (max number is 255)
     * 3 bytes = 000 - fff (max number is 4095)
     * 4 bytes = 0000 - ffff (max number is 65535)
     *
     * For example:
     * dog123 = 64,6F,67,7B and thus byteSize >= 2.      
     * dog280 = 64,6F,67,118 and thus byteSize >= 3.
     *
     * For example:
     * The String, "There are 1000000 spots on a dalmatian" would require a byteSize that can 
     * store the number '1000000' which in hex is 'f4240' and thus the byteSize must be at least 5
     *
     * The dbColumn size to store the NaturalSortString is calculated as:
     * > originalStringColumnSize x byteSize + 1
     * The extra '1' is a marker for String type - Letter, Number, Symbol
     * Thus, if the originalStringColumn is varchar(32) and the byteSize is 5:
     * > NaturalSortStringColumnSize = 32 x 5 + 1 = varchar(161)
     *
     * The byteSize must be the same for all NaturalSortStrings created in the same table.
     * If you need to change the byteSize (for instance, to accommodate larger numbers), you will
     * need to recalculate the NaturalSortString for each existing row using the new byteSize.
     *
     * @param str        String to create a natural sort string from
     * @param byteSize   Per character storage byte size (minimum 2)
     * @throws Exception See the error description thrown
     */
    public NaturalSortString(String str, int byteSize) throws Exception {
        if (str == null || str.isEmpty()) return;
        this.inStr = str;
        this.byteSize = Math.max(2, byteSize);  // minimum of 2 bytes to hold a character
        setStringType();
        iterateString();
    }

    private void setStringType() {
        char firstchar = inStr.toLowerCase().subSequence(0, 1).charAt(0);
        if (Character.isLetter(firstchar))     // letters third
            out.append(3);
        else if (Character.isDigit(firstchar)) // numbers second
            out.append(2);
        else                                   // non-alphanumeric first
            out.append(1);
    }

    private void iterateString() throws Exception {
        StringBuilder n = new StringBuilder();
        for (char c : inStr.toLowerCase().toCharArray()) { // lowercase for CASE INSENSITIVE sorting
            if (Character.isDigit(c)) {
                // group numbers
                n.append(c);
                continue;
            }
            if (n.length() > 0) {
                addInteger(n.toString());
                n = new StringBuilder();
            }
            addCharacter(c);
        }
        if (n.length() > 0) {
            addInteger(n.toString());
        }
    }

    private void addInteger(String s) throws Exception {
        int i = Integer.parseInt(s);
        if (i >= (Math.pow(16, byteSize)))
            throw new Exception("naturalsort_bytesize_exceeded");
        out.append(StringUtils.padLeft(Integer.toHexString(i), byteSize));
    }

    private void addCharacter(char c) {
        //TODO: Add rest of accented characters
        if (c >= 224 && c <= 229) // set accented a to a
            c = 'a';
        else if (c >= 232 && c <= 235) // set accented e to e
            c = 'e';
        else if (c >= 236 && c <= 239) // set accented i to i
            c = 'i';
        else if (c >= 242 && c <= 246) // set accented o to o
            c = 'o';
        else if (c >= 249 && c <= 252) // set accented u to u
            c = 'u';
        else if (c >= 253 && c <= 255) // set accented y to y
            c = 'y';

        out.append(StringUtils.padLeft(Integer.toHexString(c), byteSize));
    }

    @Override
    public String toString() {
        return out.toString();
    }
}

为了完整起见,下面是 StringUtils.padLeft 方法:
public static String padLeft(String s, int n) {
    if (n - s.length() == 0) return s;
    return String.format("%0" + (n - s.length()) + "d%s", 0, s);
}

结果应该如下所示

-1
-a
0
1
1.0
1.01
1.1.1
1a
1b
9
10
10a
10ab
11
12
12abcd
100
a
a1a1
a1a2
a-1
a-2
áviacion
b
c1
c2
c12
c100
d
d1.1.1
e

1
在MySQL中进行自然排序的最佳方法是对单独的排序键进行排序,这是正确的。但是,你的解决方案(a)需要外部代码来创建这些键,(b)在排序文本部分时忽略了排序规则,(c)在排序键表示方面效率极低,(d)与将字符串中所有数字左填充到固定字符长度(与https://dev59.com/CnVC5IYBdhLWcg3w4Vf6页面上的其他几个解决方案相比)相比,**绝对没有任何优势**。抱歉,但这个答案很糟糕:被踩了。 - Doin

2
这应该像这样对字母数字字段进行排序: 1/ 仅限数字,按1,2,3,4,5,6,7,8,9,10,11顺序排列等等... 2/ 然后是文本字段,例如:1foo、2bar、aaa11aa、aaa22aa、b5452等等...
SELECT  MyField
FROM MyTable
order by 
    IF( MyField REGEXP '^-?[0-9]+$' = 0, 
    9999999999 ,  
    CAST(MyField AS DECIMAL) 
    ), MyField

该查询检查数据是否为数字,如果不是,则将其设置为9999999999,然后首先按此列排序,然后按文本数据排序。

祝好运!


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接