当列可以以字母开头或结尾时,使用 MySQL 对 varchar 列进行数字排序并强制转换为无符号

Hol*_*ood 3 mysql sql sorting natural-sort

我遇到了一些我不确定如何处理的事情。我正在构建一个数据库来存储关于运动卡的信息,当我想查看某些卡片时,我在排序时遇到了一些问题。

作为背景,每张卡片(数据库中的行)都有关于年份的信息,设置卡片来自,卡片上的玩家和卡片号码(还有更多的信息,但这就是所有相关的信息)。当我看到结果时,我希望按年份排序,然后是设置,然后是玩家,然后是卡号。除了卡号之外的所有东西都工作正常,因为年份只是一个整数,并且 set 和 player 都是 varchars,所以很容易对它们进行排序。但是,卡号是我遇到的一些问题。

卡号列是一个 varchar,因为卡号可以包括字母、数字和破折号。最常见的卡号是直数(即 1、2、3、4、5)、直字母(Ex-A、Ex-B、Ex-C)、数字后跟字母(1a、1b , 2, 3a, 3b, 3c),或后跟数字的字母(A1、A2、A3、A4、A5)。这是我目前设置 SQL 字符串的排序部分的方式:

order by year desc, cardset asc, subset asc, cast(cardNum as unsigned) asc;
Run Code Online (Sandbox Code Playgroud)

这可以很好地处理大多数事情。但是我遇到的问题是,当一组卡的卡号中有相同的字母,然后有一个数字时。我希望排序基本上忽略前导字母,然后按数字排序。但是,有时它不会正确执行此操作,特别是当有超过 5 张卡片需要排序时。

具体来说,它错误地将具有以下卡号的某些卡按以下顺序排序:

  • BCP61
  • BCP97
  • BCP32
  • BCP135

什么时候应该导致:

  • BCP32
  • BCP61
  • BCP97
  • BCP135

它目前正在对直数或后跟字母的数字进行正确排序(即 1、2、3、4、5 或 1、2、3a、3b、4、5a、5b)。我不知道它不正确地对直字母进行排序有任何问题,但我目前也有很少的测试用例,所以我不确定它是否是 100%。

除了不知道如何在不弄乱其他排序的情况下修改我的 SQL 排序语句之外,我真的不知道它是如何得出上面 BCP 示例的顺序的。关于如何纠正它的任何想法?我想尝试忽略卡号中的字母,直到我们得到数字,但这会导致卡号中只有字母的卡出现重大问题。所以我有点卡住了。

绝对最坏最坏,我可能会将卡号列分成 2 个不同的列,一个用于更具描述性的部分,另一个用于我想要排序的部分。这可能最终会正常工作,但需要大量工作才能恢复正常!

编辑 - 这里有一些更多信息,包括我的数据库中的数据(抱歉格式化,不知道如何在此处制作表格):

| year | cardSet | subset                     | cardNum |
| 2016 | Bowman  |                            | 52      |
| 2016 | Bowman  |                            | 54      |
| 2016 | Bowman  |                            | 147     |
| 2016 | Bowman  | Chrome Prospects           | BCP32   |
| 2016 | Bowman  | Chrome Prospects           | BCP61   |
| 2016 | Bowman  | Chrome Prospects           | BCP97   |
| 2016 | Bowman  | Chrome Prospects           | BCP135  |
| 2016 | Topps   |                            | 1       |
| 2016 | Topps   |                            | 2a      |
| 2016 | Topps   |                            | 2b      |
| 2016 | Topps   |                            | 3       |
Run Code Online (Sandbox Code Playgroud)

我希望我的排序按以下顺序吐出结果:

  • 2016 鲍曼 52
  • 2016 鲍曼 54
  • 2016 鲍曼 147
  • 2016 Bowman Chrome 展望 BCP32
  • 2016 鲍曼镀铬前景 BCP61
  • 2016 Bowman Chrome 展望 BCP97
  • 2016 鲍曼镀铬前景 BCP125
  • 2016 年冠军 1
  • 2016年托普斯2a
  • 2016 年托普斯 2b
  • 2016 顶级 3

但是,这是我在上面的排序语句中给出的结果:

  • 2016 鲍曼 52
  • 2016 鲍曼 54
  • 2016 鲍曼 147
  • 2016 鲍曼镀铬前景 BCP62
  • 2016 Bowman Chrome 展望 BCP97
  • 2016 Bowman Chrome 展望 BCP32
  • 2016 鲍曼镀铬前景 BCP125
  • 2016 年冠军 1
  • 2016年托普斯2a
  • 2016 年托普斯 2b
  • 2016 顶级 3

它处理仅包含数字或数字后跟字母的卡号,但当卡号以字母开头并后跟数字时,它往往会把事情搞砸。

我尝试在注释中使用 length() 技巧,以便我的 SQL 的排序部分是:

order by year desc, cardset asc, subset asc, length(cardNum), cardNum asc
Run Code Online (Sandbox Code Playgroud)

这确实解决了我在上面描述的问题,但在我的示例中弄乱了“Topps”部分 - 无论如何,它都会将带有数字后面的字母的卡片放在最后。这是我得到的顺序:

  • 2016 鲍曼 52
  • 2016 鲍曼 54
  • 2016 鲍曼 147
  • 2016 Bowman Chrome 展望 BCP32
  • 2016 鲍曼镀铬前景 BCP61
  • 2016 Bowman Chrome 展望 BCP97
  • 2016 鲍曼镀铬前景 BCP125
  • 2016 年冠军 1
  • 2016 顶级 3
  • 2016年托普斯2a
  • 2016 年托普斯 2b

Pau*_*gel 5

MariaDB 10MySQL 8支持REGEXP_REPLACE。使用它,您可以定义一个自定义函数:

DROP FUNCTION IF EXISTS zerofill_numbers;
CREATE FUNCTION zerofill_numbers(str TEXT, len TINYINT)
    RETURNS text
    NO SQL
    DETERMINISTIC
    RETURN REGEXP_REPLACE(
        REGEXP_REPLACE(str, '(\\d+)', LPAD('\\1', len+2, 0)),
        REPLACE('0*(\\d{$len})', '$len', len),
        '\\1'
    );
Run Code Online (Sandbox Code Playgroud)

现在给出以下测试数据:

DROP TABLE IF EXISTS `strings`;
CREATE TABLE IF NOT EXISTS `strings` (`str` text);
INSERT INTO `strings` (`str`) VALUES
    ('Bowman 52'),
    ('Bowman 54'),
    ('Bowman 147'),
    ('Bowman Chrome Prospects BCP32'),
    ('Bowman Chrome Prospects BCP61'),
    ('Bowman Chrome Prospects BCP97'),
    ('Bowman Chrome Prospects BCP125'),
    ('Topps 1'),
    ('Topps 3'),
    ('Topps 2a'),
    ('Topps 2b'),
    ('v9.9.3'),
    ('v9.10.3'),
    ('v11.3.4'),
    ('v9.9.11'),
    ('v11.3'),
    ('0.9'),
    ('0.11'),
    ('s09'),
    ('s11'),
    ('s0'),
    ('v9.0.1');
Run Code Online (Sandbox Code Playgroud)

我们可以用以下方式对其进行排序:

SELECT s.str
FROM strings s
ORDER BY zerofill_numbers(s.str, 10)
Run Code Online (Sandbox Code Playgroud)

结果如下:

0.9
0.11
Bowman 52
Bowman 54
Bowman 147
Bowman Chrome Prospects BCP32
Bowman Chrome Prospects BCP61
Bowman Chrome Prospects BCP97
Bowman Chrome Prospects BCP125
s0
s09
s11
Topps 1
Topps 2a
Topps 2b
Topps 3
v9.0.1
v9.9.3
v9.9.11
v9.10.3
v11.3
v11.3.4
Run Code Online (Sandbox Code Playgroud)

该函数将用零填充字符串中的任何数字,直到它具有定义的长度。

注意 1:这不会正确排序十进制数(请参阅0.90.11)。您也不应该尝试将它用于带符号的数字。

注意 2:此功能基于以下答案:https : //stackoverflow.com/a/46099386/5563083 - 因此,如果您发现此答案有帮助,请去投票源。

注3:如果不想定义自定义函数,可以使用同样的方法内联:

SELECT *
FROM strings
ORDER BY
  REGEXP_REPLACE(REGEXP_REPLACE(str, '(\\d+)', LPAD('\\1', 10+2, 0)), '0*(\\d{10})', '\\1')
Run Code Online (Sandbox Code Playgroud)