【问题标题】:MySQL database with unique fields ignored ending spaces具有唯一字段的 MySQL 数据库忽略了结尾空格
【发布时间】:2012-07-27 17:02:11
【问题描述】:

我的项目需要从用户的输入开始,其中空格在单词的左侧,空格在右侧,例如“apple”。如果用户输入'apple'或者'apple',无论是单词的左右一个空格还是多个空格,我都需要这样存储。

此字段具有 Unique 属性,但我尝试在左侧插入带有空格的单词,并且效果很好。但是当我尝试在右侧插入带有间距的单词时,它会从单词右侧修剪掉所有间距。

所以我正在考虑在空格后的单词右侧添加一个特殊字符。但我希望这个问题有更好的解决方案。

CREATE TABLE strings
( id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
string varchar(255) COLLATE utf8_bin NOT NULL,
created_ts timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (id), UNIQUE KEY string (string) )
ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COLLATE=utf8_bin

【问题讨论】:

  • 你能发布你的表架构吗?

标签: mysql mysql5


【解决方案1】:

问题是 MySQL 在进行字符串比较时会忽略尾随空格。看 http://dev.mysql.com/doc/refman/5.7/en/char.html

所有 MySQL 排序规则都是 PADSPACE 类型。这意味着在比较 MySQL 中的所有 CHAR、VARCHAR 和 TEXT 值时,不考虑任何尾随空格。

...

对于去除尾随填充字符或比较忽略它们的情况,如果列具有需要唯一值的索引,则将仅在尾随填充字符数量上不同的值插入到列中将导致重复键错误.例如,如果表包含“a”,则尝试存储“a”会导致重复键错误。

(此信息适用于 5.7;对于 8.0,此信息已更改,请参见下文)

like 运算符的部分给出了此行为的示例(并表明 like 确实尊重尾随空格):

mysql> SELECT 'a' = 'a ', 'a' LIKE 'a ';
+------------+---------------+
| 'a' = 'a ' | 'a' LIKE 'a ' |
+------------+---------------+
|          1 |             0 |
+------------+---------------+
1 row in set (0.00 sec)

不幸的是,UNIQUE 索引似乎使用标准字符串比较来检查是否已经存在这样的值,因此忽略了尾随空格。 这与使用VARCHARCHAR 无关,在这两种情况下插入都被拒绝,因为唯一检查失败。如果有办法使用like 语义进行UNIQUE 检查,那么我不知道。

您可以将值存储为VARBINARY

mysql> create table test_ws ( `value` varbinary(255) UNIQUE );
Query OK, 0 rows affected (0.13 sec)

mysql> insert into test_ws (`value`) VALUES ('a');
Query OK, 1 row affected (0.08 sec)

mysql> insert into test_ws (`value`) VALUES ('a ');
Query OK, 1 row affected (0.06 sec)

mysql> SELECT CONCAT( '(', value, ')' ) FROM test_ws;
+---------------------------+
| CONCAT( '(', value, ')' ) |
+---------------------------+
| (a)                       |
| (a )                      |
+---------------------------+
2 rows in set (0.00 sec)

您最好不要在此列上按字母顺序排序,因为排序将发生在字节值上,而这不是用户所期望的(大多数用户,无论如何)。

另一种方法是修补 MySQL 并编写自己的排序规则,该排序规则类型为 NO PAD。不确定是否有人愿意这样做,但如果你愿意,请告诉我;)

编辑:根据https://dev.mysql.com/doc/refman/8.0/en/char.html,同时 MySQL 有类型为 NO PAD 的排序规则:

大多数 MySQL 排序规则都有一个 PAD SPACE 的 pad 属性。例外情况是基于 UCA 9.0.0 及更高版本的 Unicode 排序规则,其填充属性为 NO PAD。

https://dev.mysql.com/doc/refman/8.0/en/charset-unicode-sets.html

基于 4.0.0 之后的 UCA 版本的 Unicode 排序规则在排序规则名称中包含该版本。因此,utf8mb4_unicode_520_ci 是基于 UCA 5.2.0 权重键,而 utf8mb4_0900_ai_ci 是基于 UCA 9.0.0 权重键。

所以如果你尝试:

  create table test_ws ( `value` varbinary(255) UNIQUE )
    character set utf8mb4 collate utf8mb4_0900_ai_ci;

您可以插入带有或不带有尾随空格的值

您可以找到所有可用的 NO PAD 归类:

 show collation where Pad_attribute='NO PAD';

【讨论】:

    【解决方案2】:

    这与 CHAR 与 VARCHAR 无关。 SQL Server 在字符串比较时不考虑尾随空格,这在检查唯一键约束时也适用。所以并不是说不能插入尾随空格的值,而是一旦插入,就不能再插入更多或更少空格的值了。

    作为您的问题的解决方案,您可以添加一个保持字符串长度的列,并将长度和字符串值作为复合唯一键约束。

    SQL Server 2012 中,您甚至可以将长度列设为计算列,这样您就完全不用担心值了。有关 SQL Server 2012 的示例,请参阅 http://sqlfiddle.com/#!6/32e94。(我敢打赌,在 MySQL 中也有类似的东西。)

    【讨论】:

      【解决方案3】:

      您可能需要了解 VARCHAR 和 CHAR 类型之间的区别。

      The CHAR and VARCHAR Types

      当存储 CHAR 值时,它们会用空格右填充到指定的长度。检索 CHAR 值时,除非启用 PAD_CHAR_TO_FULL_LENGTH SQL 模式,否则将删除尾随空格。

      对于 VARCHAR 列,超过列长度的尾随空格在插入之前会被截断并生成警告,而不管使用的 SQL 模式如何。对于 CHAR 列,无论 SQL 模式如何,都会以静默方式截断插入值中多余的尾随空格。

      VARCHAR 值在存储时不会被填充。按照标准 SQL,在存储和检索值时保留尾随空格。

      结论:如果要在文本字符串的右侧保留空格,请使用 CHAR 类型(而不是 VARCHAR)。

      【讨论】:

      • 问题是关于 UNIQUE 约束,它确实忽略了 CHARVARCHARTEXT 列上的尾随空格。
      • 我认为按照我的建议将字段类型更改为 CHAR 将解决问题。
      • 是的,问题是我不知道提供了多少空间,它可能是一个或多个。因此,它可能会或可能不会提供准确的空白计数。另一个问题是字段类型是唯一属性,这意味着如果输入apple apple 。会出错。
      • 正如我在上面的回答中所解释的,如果您将字段类型更改为 CHAR,您将解决问题:所有尾随空格都保留在 CHAR 字段中。
      • 还有 UNIQUE vals 的问题。
      【解决方案4】:

      感谢@kennethc。他的回答对我有用。 将字符串长度字段添加到表和唯一键。

      CREATE TABLE strings
      ( id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
      string varchar(255) COLLATE utf8_bin NOT NULL,
      created_ts timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
      string_length int(3),
      PRIMARY KEY (id), UNIQUE KEY string (string,string_length) )
      ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COLLATE=utf8_bin
      

      在 MySQL 中,可以使用如下几个触发器来更新字符串长度字段:

      CREATE TRIGGER `string_length_insert` BEFORE INSERT ON `strings` FOR EACH ROW SET NEW.string_length = char_length(NEW.string);
      CREATE TRIGGER `string_length_update` BEFORE UPDATE ON `strings` FOR EACH ROW SET NEW.string_length = char_length(NEW.string);
      

      【讨论】:

        猜你喜欢
        • 2014-08-28
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-01-21
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多