MySQL-选择字符串的前10个字节

时间:2018-07-25 11:36:48

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

聪明的男人和女人

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

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

一些有关#bytes <250的字段的测试(详细信息: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 € "

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

已经感谢您,耶隆(Jeroen)

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

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

3 个答案:

答案 0 :(得分:1)

  

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

这真的是您想要做的吗?可以(如已经指出的那样)通过将多字节字符拆分为垃圾来弄乱字符串。

  

Amazon按字节计算字段长度

请为此提供证据。

  

相差大约10%,但是相差很大。

最大值可以是4的因数。表情符号和某些汉字需要4个字节才能进行UTF-8(utf8mb4)编码。

如果Amazon正在使用latin1进行编码(这与不同于 ),那么首先您需要检查字符串是否可以在latin1中进行编码。西欧文字可以,但亚洲文字不能。当然,您可以获得“字节”,导致文本混乱,尤其是当您截断到某个字节而不是字符边界时。

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

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

然后,您可以将CONVERT(@tekst USING latin1)LEFT(..., 10)或其他任何方式一起使用。

更好?

如果Amazon有效使用了latin1,则使用latin1。也就是说,声明您的字符串:

 for_amazon VARCHAR(10) CHARACTER SET latin1

和/或与SET NAMES latin1

连接

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

任何一种都将提供转换(在存储之前与提取之前),以便您提供给Amazon的字节为latin1。

注意事项:如果您将中文(或俄语或希腊语等)存储在该列中,则会被弄乱。

答案 1 :(得分:0)

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

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

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

但是,里克·詹姆斯(Rick James)提供了很多很好的建议。尽管只有您才能判断与您相关的程度以及您的具体情况。

答案 2 :(得分:0)

谢谢@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