【问题标题】:MySQL - select first 10 bytes of a stringMySQL - 选择字符串的前 10 个字节
【发布时间】:2019-01-02 05:10:36
【问题描述】:

你好聪明的男人和女人,

如何选择字符串的前 x 个字节?

用例:我正在优化产品描述文本以上传到亚马逊,亚马逊按 utf8 中的字节(不是我之前所说的 latin1)来衡量字段长度,而不是按字符。另一方面,MySQL 似乎是基于字符的操作。 (例如,函数 left() 是基于字符的,而不是基于字节的)。差异(使用英语、法语、西班牙语和德语)大约为 10%,但差异可能很大。

关于#bytes http://wiki.devliegendebrigade.nl/Format_inventarisbestanden_(Amazon)#Veldlengte):

OK, char_length: 248,   byte length latin1: 248,   byte length utf8: 248
OK, char_length: 249,   byte length latin1: 249,   byte length utf8: 249
OK, char_length: 249,   byte length latin1: 249,   byte length utf8: 249
OK, char_length: 249,   byte length latin1: 249,   byte length utf8: 249

Not OK, char_length: 250,   byte length latin1: 250,   byte length utf8: 250
Not OK, char_length: 249,   byte length latin1: 249,   byte length utf8: 252
Not OK, char_length: 248,   byte length latin1: 248,   byte length utf8: 252
Not OK, char_length: 249,   byte length latin1: 249,   byte length utf8: 252
Not OK, char_length: 249,   byte length latin1: 249,   byte length utf8: 257

插图:

set @tekst="Jantje zag € pruimen hangen";

select
   char_length(@tekst),   # 27 characters
   length(@tekst);        # 29 bytes

select left(@tekst, 15)   # Result: "Jantje zag € pr"

# Ideally, I'm looking for something like this:

select left_bytes_utf8(@tekst, 15)   # Result: "Jantje zag € "

一种方法可能是通过迭代调用自身的存储过程,但我怀疑周围有更有效的解决方案。

已经谢谢你了,杰伦

P.s.:编辑了问题:将 2x“latin1”更改为“utf8”。这实际上有点令人困惑:上传应该是 Latin1,但字段大小是使用 utf8 以字节为单位衡量的

P.p.s:更新:这些上传适用于英语、法语、西班牙语和德语的亚马逊网站。字符不会比“ø”(直径)、“€”、“è”、“é”、“ü”和“ö”更具异国情调。全部在 Latin1 编码中,但在 utf8 中是多字节的。

【问题讨论】:

  • 为什么不自己在相关列上使用 latin1 编码 (docs)?那么你也将是基于字节的。
  • left() 或 substring() 之类的函数仍然是基于字符的,并且问题仍然存在 - 或者我不明白你的意思
  • 哦等等,你想要 15 个字节的 UTF-8?你的亚马逊故事让我失望:P
  • 哪种字符编码并不重要。随便选一个
  • 嗯,latin1 的 15 个字节与 latin1 的 15 个字符完全一样

标签: mysql stored-procedures character-encoding string-length iso-8859-1


【解决方案1】:
SELECT CONVERT(LEFT(CONVERT(@tekst USING binary), 15) USING utf8);

只要 UTF-8 字符串仍然是一个有效的 UTF-8 字符串,它就会给你减少到 15 个字节的字符串(MySQL 会拒绝给你一个无效的字符串,例如,如果你剪掉一个多字节字符,并且给你NULL。)如果这不起作用,你可以通过省略最后一次重新转换为UTF-8来获取原始字节,但你必须自己将它们解码为有用的东西:

SELECT LEFT(CONVERT(@tekst USING binary), 15);

不过,Rick James 给出了很多很好的建议;虽然只有您可以判断它与您相关的程度以及您的具体情况。

【讨论】:

    【解决方案2】:

    如何选择字符串的前 x 个字节?

    这真的是你想做的吗?这可能(如前所述)通过将多字节字符拆分为垃圾来破坏字符串。

    亚马逊按字节计算字段长度

    请提供证据证明这一点。

    差异大约为 10%,但差异可能很大。

    最大值可以是 4 倍。表情符号和某些汉字需要 4 个字节用于 UTF-8 (utf8mb4) 编码。

    如果亚马逊在latin1 中编码(这与“按字节”相同),那么首先你需要检查字符串是否可以在 latin1 中编码。西欧文本可以,但亚洲文本不能。当然,您可以获得“字节”,这会导致文本损坏,特别是如果您截断到某个字节而不是字符边界。

    SELECT CONVERT(CONVERT(@tekst USING latin1) USING utf8) = @tekst;
    

    如果转换成功,将返回 1 (true)。

    然后你可以使用CONVERT(@tekst USING latin1)LEFT(..., 10) 或其他。

    更好?

    如果亚马逊有效地使用 latin1,那么使用 latin1。也就是说,声明你的字符串:

     for_amazon VARCHAR(10) CHARACTER SET latin1
    

    和/或与SET NAMES latin1联系

    或者你可以有更大的领域,然后做LEFT(..., 10)

    两者都将提供转换(在存储之前与在获取时),以便您提供给亚马逊的字节将是 latin1。

    警告:如果你在列中存储中文(或俄文或希腊文等),它会被搞砸。

    【讨论】:

    • 谢谢!我相应地澄清了这个问题
    【解决方案3】:

    谢谢@Amadan 和@Rick James!感谢您的输入,我能够提出一个多字节安全的字节左函数:

    CREATE DEFINER=`root`@`localhost` FUNCTION `left_byte`(
        input_string text,
        input_position integer
    ) RETURNS text CHARSET utf8
    BEGIN
    
    # Byte-wise left function
    ################################################################################
    #
    # * multibyte-safe for characters of up to 4 bytes (=max # bytes utf8)
    # * utf8 Assumed to be the general encoding
    
    return 
    ifnull
    (
        ifnull
        (
            ifnull
            (
                convert(left(convert(input_string using binary), input_position) using utf8),
                convert(left(convert(input_string using binary), input_position-1) using utf8)
            ),
            convert(left(convert(input_string using binary), input_position-2) using utf8)
        ),
        convert(left(convert(input_string using binary), input_position-3) using utf8)
    );    
    END
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2017-11-29
      • 2013-04-27
      • 2016-03-09
      • 2020-07-20
      • 1970-01-01
      • 2013-02-23
      • 1970-01-01
      • 2020-05-12
      相关资源
      最近更新 更多