【问题标题】:MySQL character ordering: numbers before question markMySQL字符排序:问号前的数字
【发布时间】:2022-01-09 20:18:30
【问题描述】:

我最近将 MySQL 数据存储从某个令人讨厌的多年过时版本升级到 8.0.26。

在一个特定的表中,我存储与每条记录相关的日期,但偶尔会有未知的未来日期。这些始终以 YYYY-MM-?? 格式存储,因此字段类型是 VARCHAR(10) 而不是 DATE,如果可以始终准确则可以预期。 否则字段数据是可靠的 YYYY-MM-DD。

但是,对这些数据进行排序的查询最近已按预期停止工作,MySQL 认为这种未知日期应该在确切日期之前排序。

查询归结为如下内容:SELECT * FROM table WHERE date_field <= CURDATE()

(今天是 12 月 3 日,所以 CURDATE 的计算结果为 2021-12-03。使用文字字符串值 2021-12-03 而不是 CURDATE 函数时也会发生同样的情况,所以这绝对是一个排序问题而不是数据类型之间的冲突。)

在之前运行的那些旧 MySQL 版本中,2021-12-??将评估高于/大于 03 之类的确切日期,因此不会返回。这也符合ASCII sort ordering 的预期。但是,现在有这样的吗?记录也被返回,问号字符显然被排序为之前/小于一个数字。

目前,我可以通过在查询中使用 REPLACE 来强制执行正确和预期的行为,但这是过程繁重、丑陋且不方便:SELECT * FROM table WHERE REPLACE(date_field , '??', '99') <= CURDATE()

谁能解释为什么会发生这种情况以及我应该如何纠正它?考虑到标准的 ASCII 排序和以前的经验(多年的经验),它可能是一个 MySQL 错误吗?


编辑:感谢最初的回复指出我的整理。该数据库几乎完全使用纯英语,只有偶尔的口音(等),所以我过去很少需要触摸默认设置。

根据 ProGu 和 Álvaro González 的回复,我已经开始四处挖掘并测试不涉及实际表/数据库的查询确实按建议返回。但是,一旦我尝试在真实表上运行任何东西,它的行为仍然不符合预期。

表在 InnoDB 上,整个数据库的所有表和(文本)字段都是 utf8mb4/utf8mb4_0900_ai_ci。我已经尝试在查询级别和通过更改实际的表和字段排序规则来强制排序规则,但无论我选择哪个,总是返回讨厌的 2021-12-??。我尝试了各种查询格式都无济于事:

SELECT * FROM table WHERE date_field <= CURDATE() ORDER BY date_field COLLATE utf8mb4_0900_ai_ci DESC

SELECT * FROM table WHERE date_field COLLATE utf8mb4_0900_ai_ci <= CURDATE() COLLATE utf8mb4_0900_ai_ci ORDER BY date_field COLLATE utf8mb4_0900_ai_ci DESC

基于Álvaro的代码测试,正确返回2021-12-03

with sample_data (sample_value) as (
    select '2021-12-??'
    union all select '2021-12-03'
)
select *
from sample_data
where sample_value <= CURDATE()
order by sample_value COLLATE utf8mb4_0900_ai_ci DESC LIMIT 1;

我的整理经验不足吗?我错过了一些非常明显的事情吗?


编辑 2

所有表格和所有文本列(加上连接)已设置为utf8mb4utf8mb4_0900_ai_ci

参见this DB Fiddle,它也错误地返回2021-12-??,显然小于比较值(当前日期,2021-12-08)。我找不到返回真正较小值的排序规则(示例数据中的2021-10-31)。

回到 Rick 最初的回复: SELECT "2021-12-??" &lt; "2021-12-03"

这将返回 1,即 03 大于 ??。为什么? ASCII 排序很清楚,数字字符在 - 小于 - 问号字符之前。

在我的帖子的原始版本中,在我看来,当 MySQL 使用数字作为字符串而不是 int 时,字符排序是错误的。

比较:

SELECT "?" &lt; "0"; = 1

SELECT "?" &lt; 0; = 0

【问题讨论】:

  • 这是字符集问题,dbfiddle.uk/…
  • @ProGu 这有 99% 以上的可能性是答案。写下来。
  • 你的意思是 YYYY 而不是 YYY?
  • 我当然做到了!谢谢瑞克。
  • 说实话,我不完全确定你最新的 sn-p 试图说明什么。但肯定还有一个与curdate() 相关的问题。我已编辑我的答案以添加相关信息。

标签: mysql ascii collation


【解决方案1】:

这是一个排序规则问题。您可能依赖于默认排序规则,但它已经改变了。

您可以在查询级别更改排序规则以确定哪些适合您的需要,然后相应地调整表或列排序规则:

with sample_data (sample_value) as (
    select '?'
    union all select '0'
)
select *
from sample_data
order by sample_value COLLATE utf8mb4_bin;
Result
0
?
with sample_data (sample_value) as (
    select '?'
    union all select '0'
)
select *
from sample_data
order by sample_value COLLATE utf8mb4_0900_as_cs;
Result
?
0

Demo

请注意,我的意思是整理而不是编码。如果不是 UTF-8,您应该能够保留当前编码。


编辑#1:这些 sn-ps 只是决定选择哪种排序规则的工具。该问题的解决方法是不要在查询末尾添加随机的ORDER BY 子句,解决方法是更改​​表(或列)排序规则:

编辑#2:

where sample_value &lt;= CURDATE() 似乎忽略了表排序规则,但这可能是由于从日期类型自动转换。如果您强制强制转换为文本,事情会发生变化:

where sample_value <= cast(curdate() as char(10))

Demo

我的建议是您首先在所有位置(表、连接...)设置一个众所周知的默认排序规则。这可能会解决所有问题。

【讨论】:

  • 谢谢你,阿尔瓦罗!这似乎是在正确的轨道上。不过,我无法确定正确的排序规则 - 我已经更新了上面的原始帖子。
  • utf8mb4_0900_ai_ci works fine 但您最新的 sn-p 并未说明这一点(sample_value &lt;= CURDATE() 仍使用默认表排序规则)。我使用order by 作为验证给定排序规则呈现预期顺序的一种方式,我并不是说要解决这个问题是添加一个order by 子句!您需要打开您喜欢的 MySQL 客户端并编辑表排序规则以将其设置为 utf8mb4_0900_ai_ci
  • 感谢您的进一步关注,Álvaro,但您错过了我的意思:utf8mb4_0900_ai_ci 已经是表和列的默认排序规则。我将尝试在 DB Fiddle 中为我的问题创建一个更好的工作示例。
【解决方案2】:

我认为您需要稍微更改代码。尚不清楚以下哪种情况适用于您的代码。

mysql> SELECT "2021-12-??" < "2021-12-03";
+-----------------------------+
| "2021-12-??" < "2021-12-03" |
+-----------------------------+
|                           1 |
+-----------------------------+

mysql> SELECT "2021-12-??" < CURDATE();
ERROR 1525 (HY000): Incorrect DATE value: '2021-12-??'

mysql> SELECT "2021-12-??" < CAST(CURDATE() AS CHAR);
+----------------------------------------+
| "2021-12-??" < CAST(CURDATE() AS CHAR) |
+----------------------------------------+
|                                      1 |
+----------------------------------------+

mysql> SELECT DATE(NOW()) <= CAST(CURDATE() AS CHAR);
+----------------------------------------+
| DATE(NOW()) <= CAST(CURDATE() AS CHAR) |
+----------------------------------------+
|                                      1 |
+----------------------------------------+

【讨论】:

  • 谢谢瑞克。一个好地方,但已经用DATE_FORMAT(NOW(), '%Y-%m-%d') 满足了我真实的实时代码。为了清楚起见,在帖子示例中省略了,但也许需要提及!
  • @Aaron - DATE_FORMAT(NOW(), '%Y-%m-%d') 给出与CURDATE() 相同的结果。但是,它可能有不同的数据类型或排序规则问题。我认为DATE_FORMAT 会产生VARCHAR,这可能存在排序问题。同时,CHARDATE 给出了 DATE 数据类型,这导致了我的第二个示例中的问题。玩转各种选项;看看哪一个适合你。
猜你喜欢
  • 1970-01-01
  • 2011-07-13
  • 1970-01-01
  • 1970-01-01
  • 2012-08-19
  • 2018-01-21
  • 2021-11-22
  • 1970-01-01
  • 2012-06-19
相关资源
最近更新 更多