Quando voce valida um nome de usuario ou corta um texto para caber num limite, quase sempre quer contar caracteres, nao bytes. Em UTF-8 um unico caractere pode ocupar de um a quatro bytes, entao uma contagem ingenua quebra assim que surgem acentos, cirilico ou texto CJK. A seguir vemos como char_length difere de octet_length e por que LENGTH e uma ma escolha para contar caracteres em codigo portavel.
char_length conta caracteres
char_length (sinonimo character_length) retorna o numero de caracteres de uma string, sem importar quantos bytes eles ocupam em disco. Em latim puro o resultado e obvio e coincide com o que voce ve:
SELECT char_length('acai');
SELECT char_length('cafe');
SELECT char_length('Moscow');
As duas primeiras palavras representam as acentuadas "açaí" e "café": cada uma tem quatro caracteres, mas em UTF-8 cada letra acentuada ocupa dois bytes, entao a string e mais longa em bytes do que em caracteres. A diferenca aparece assim que um valor contem um caractere fora do ASCII. Pegue uma letra com acento escrita como a mais uma marca combinante, U&'a\0301'. O olho ve um glifo, mas o UTF-8 o codifica como dois bytes, entao char_length e octet_length divergem:
SELECT char_length(U&'a\0301') AS chars,
octet_length(U&'a\0301') AS bytes;
char_length responde "quantos caracteres uma pessoa vai ver" e retorna 1, enquanto octet_length responde "quanto espaco isso ocupa na memoria ou em disco" e retorna 2. A mesma logica vale para substring e left: eles operam sobre caracteres, entao cortar por caracteres nunca parte um caractere multibyte ao meio, ao passo que um corte manual por bytes faz isso e produz texto corrompido.
octet_length e por que bytes nao sao caracteres
octet_length retorna o tamanho de uma string em bytes. Para ASCII puro ele coincide com char_length, mas para texto acentuado ou ideografico os dois divergem. A consulta abaixo poe as duas medidas lado a lado para que a diferenca apareca na propria saida:
SELECT name,
char_length(name) AS chars,
octet_length(name) AS bytes
FROM users
WHERE country IN ('BR', 'JP', 'RU');
Um guia rapido de UTF-8:
- letras latinas e digitos: 1 byte por caractere;
- acentos e cirilico: geralmente 2 bytes;
- a maioria dos ideogramas CJK: 3 bytes;
- emoji e sinais raros: 4 bytes.
Assim, uma string de quatro ideogramas da char_length = 4 mas octet_length = 12. Essa diferenca atinge direto os limites de coluna: se uma coluna for declarada VARCHAR(10), o Postgres aplica esse limite em caracteres, entao o valor cabe, enquanto um limite de 10 bytes feito a mao o rejeitaria.
Validacao de tamanho
Regras de negocio quase sempre falam de caracteres, entao use char_length em filtros e verificacoes. Encontre usuarios com nome curto demais e email longo demais:
SELECT id, name, email
FROM users
WHERE char_length(name) < 2
OR char_length(email) > 254;
A mesma regra como restricao CHECK sobre char_length, para que um nome curto ou longo demais nunca chegue na tabela:
ALTER TABLE employees
ADD CONSTRAINT name_len_chk
CHECK (char_length(name) BETWEEN 2 AND 100);
octet_length e util quando voce esbarra num teto tecnico rigido em bytes, como o tamanho de uma chave num sistema externo ou o prefixo de um indice:
SELECT id, email
FROM users
WHERE octet_length(email) > 191;
Na pratica, guarde uma regra simples: limites pensados para pessoas (nome, titulo, comentario) sao contados em caracteres com char_length, enquanto limites pensados para maquinas (uma chave, um indice, um campo de largura fixa num formato externo) sao contados em bytes com octet_length. Misturar as duas medidas numa unica regra e a forma segura de entregar um bug que so dispara com entradas acentuadas ou ideograficas, justamente as que voce nunca ve em dados de teste so com latim, onde char_length e octet_length coincidem.
Pegadinha: LENGTH se comporta diferente em cada engine
A maior armadilha e o LENGTH, porque existe em todo lugar mas significa algo diferente em cada engine.
- No PostgreSQL,
length(text) conta caracteres (como char_length), mas length(bytea) conta bytes.
- No MySQL,
LENGTH() conta bytes, e os caracteres vem de CHAR_LENGTH().
- No ClickHouse,
length() sobre um String conta bytes, e os caracteres vem de lengthUTF8().
Nos exemplos abaixo a string 'cafe' representa "café", com um acento sobre a ultima letra, entao em UTF-8 ela ocupa 5 bytes para 4 caracteres. A funcao de bytes retorna 5 e a de caracteres retorna 4; sobre a palavra ASCII pura cafe ambas retornariam 4 e a pegadinha ficaria invisivel.
SELECT LENGTH('cafe'),
CHAR_LENGTH('cafe');
SELECT length('cafe'),
lengthUTF8('cafe');
Por causa dessa divergencia, uma verificacao de tamanho escrita em Postgres com length() vira em silencio de caracteres para bytes ao ser portada para MySQL ou ClickHouse: sobre a palavra acentuada café, length() da 4 no Postgres mas 5 no MySQL e no ClickHouse. Com dados em latim os testes passam, mas a primeira linha com um acento ou cirilico ultrapassa um limite que antes se mantinha. Por isso, ao migrar, rode as verificacoes de tamanho sobre strings com NULL, valores vazios, acentos, cirilico e emoji, nao so sobre dados ASCII onde length, char_length e octet_length coincidem.
O mesmo risco atinge o desempenho: char_length(col) num WHERE ou num CHECK e uma funcao sobre a coluna e pode esconder do planejador um indice comum sobre ela. Em tabelas grandes, olhe o plano de execucao e, se preciso, crie um indice por expressao ou guarde o tamanho numa coluna gerada.
A conclusao e simples: nunca confie no LENGTH para contar caracteres em codigo portavel. Escreva char_length no Postgres, CHAR_LENGTH no MySQL e lengthUTF8 no ClickHouse, para que suas regras de validacao continuem firmes ao migrar dados cheios de acentos e ideogramas. E lembre de mais um detalhe: char_length conta pontos de codigo Unicode, nao os "grafemas" que uma pessoa percebe. Um emoji com modificador de tom de pele ou uma bandeira de dois simbolos podem relatar um tamanho maior que um. Para a maioria das validacoes isso e suficiente, mas se voce corta por posicoes exibidas, contar caracteres "visiveis" exige logica separada na camada de aplicacao.
Quando voce valida um nome de usuario ou corta um texto para caber num limite, quase sempre quer contar caracteres, nao bytes. Em UTF-8 um unico caractere pode ocupar de um a quatro bytes, entao uma contagem ingenua quebra assim que surgem acentos, cirilico ou texto CJK. A seguir vemos como
char_lengthdifere deoctet_lengthe por queLENGTHe uma ma escolha para contar caracteres em codigo portavel.char_length conta caracteres
char_length(sinonimocharacter_length) retorna o numero de caracteres de uma string, sem importar quantos bytes eles ocupam em disco. Em latim puro o resultado e obvio e coincide com o que voce ve:SELECT char_length('acai'); -- 4 SELECT char_length('cafe'); -- 4 SELECT char_length('Moscow'); -- 6As duas primeiras palavras representam as acentuadas "açaí" e "café": cada uma tem quatro caracteres, mas em UTF-8 cada letra acentuada ocupa dois bytes, entao a string e mais longa em bytes do que em caracteres. A diferenca aparece assim que um valor contem um caractere fora do ASCII. Pegue uma letra com acento escrita como
amais uma marca combinante,U&'a\0301'. O olho ve um glifo, mas o UTF-8 o codifica como dois bytes, entaochar_lengtheoctet_lengthdivergem:-- one visible character, two bytes in UTF-8 SELECT char_length(U&'a\0301') AS chars, -- 1 octet_length(U&'a\0301') AS bytes; -- 2char_lengthresponde "quantos caracteres uma pessoa vai ver" e retorna1, enquantooctet_lengthresponde "quanto espaco isso ocupa na memoria ou em disco" e retorna2. A mesma logica vale parasubstringeleft: eles operam sobre caracteres, entao cortar por caracteres nunca parte um caractere multibyte ao meio, ao passo que um corte manual por bytes faz isso e produz texto corrompido.octet_length e por que bytes nao sao caracteres
octet_lengthretorna o tamanho de uma string em bytes. Para ASCII puro ele coincide comchar_length, mas para texto acentuado ou ideografico os dois divergem. A consulta abaixo poe as duas medidas lado a lado para que a diferenca apareca na propria saida:SELECT name, char_length(name) AS chars, octet_length(name) AS bytes FROM users WHERE country IN ('BR', 'JP', 'RU');Um guia rapido de UTF-8:
Assim, uma string de quatro ideogramas da
char_length = 4masoctet_length = 12. Essa diferenca atinge direto os limites de coluna: se uma coluna for declaradaVARCHAR(10), o Postgres aplica esse limite em caracteres, entao o valor cabe, enquanto um limite de 10 bytes feito a mao o rejeitaria.Validacao de tamanho
Regras de negocio quase sempre falam de caracteres, entao use
char_lengthem filtros e verificacoes. Encontre usuarios com nome curto demais e email longo demais:SELECT id, name, email FROM users WHERE char_length(name) < 2 OR char_length(email) > 254;A mesma regra como restricao
CHECKsobrechar_length, para que um nome curto ou longo demais nunca chegue na tabela:ALTER TABLE employees ADD CONSTRAINT name_len_chk CHECK (char_length(name) BETWEEN 2 AND 100);octet_lengthe util quando voce esbarra num teto tecnico rigido em bytes, como o tamanho de uma chave num sistema externo ou o prefixo de um indice:SELECT id, email FROM users WHERE octet_length(email) > 191; -- byte budget for an index prefixNa pratica, guarde uma regra simples: limites pensados para pessoas (nome, titulo, comentario) sao contados em caracteres com
char_length, enquanto limites pensados para maquinas (uma chave, um indice, um campo de largura fixa num formato externo) sao contados em bytes comoctet_length. Misturar as duas medidas numa unica regra e a forma segura de entregar um bug que so dispara com entradas acentuadas ou ideograficas, justamente as que voce nunca ve em dados de teste so com latim, ondechar_lengtheoctet_lengthcoincidem.Pegadinha: LENGTH se comporta diferente em cada engine
A maior armadilha e o
LENGTH, porque existe em todo lugar mas significa algo diferente em cada engine.length(text)conta caracteres (comochar_length), maslength(bytea)conta bytes.LENGTH()conta bytes, e os caracteres vem deCHAR_LENGTH().length()sobre umStringconta bytes, e os caracteres vem delengthUTF8().Nos exemplos abaixo a string
'cafe'representa "café", com um acento sobre a ultima letra, entao em UTF-8 ela ocupa 5 bytes para 4 caracteres. A funcao de bytes retorna5e a de caracteres retorna4; sobre a palavra ASCII puracafeambas retornariam4e a pegadinha ficaria invisivel.-- MySQL: byte count vs character count SELECT LENGTH('cafe'), -- 5 (e with accent = 2 bytes) CHAR_LENGTH('cafe'); -- 4-- ClickHouse: byte count vs character count SELECT length('cafe'), -- 5 lengthUTF8('cafe'); -- 4Por causa dessa divergencia, uma verificacao de tamanho escrita em Postgres com
length()vira em silencio de caracteres para bytes ao ser portada para MySQL ou ClickHouse: sobre a palavra acentuada café,length()da4no Postgres mas5no MySQL e no ClickHouse. Com dados em latim os testes passam, mas a primeira linha com um acento ou cirilico ultrapassa um limite que antes se mantinha. Por isso, ao migrar, rode as verificacoes de tamanho sobre strings com NULL, valores vazios, acentos, cirilico e emoji, nao so sobre dados ASCII ondelength,char_lengtheoctet_lengthcoincidem.O mesmo risco atinge o desempenho:
char_length(col)numWHEREou numCHECKe uma funcao sobre a coluna e pode esconder do planejador um indice comum sobre ela. Em tabelas grandes, olhe o plano de execucao e, se preciso, crie um indice por expressao ou guarde o tamanho numa coluna gerada.A conclusao e simples: nunca confie no
LENGTHpara contar caracteres em codigo portavel. Escrevachar_lengthno Postgres,CHAR_LENGTHno MySQL elengthUTF8no ClickHouse, para que suas regras de validacao continuem firmes ao migrar dados cheios de acentos e ideogramas. E lembre de mais um detalhe:char_lengthconta pontos de codigo Unicode, nao os "grafemas" que uma pessoa percebe. Um emoji com modificador de tom de pele ou uma bandeira de dois simbolos podem relatar um tamanho maior que um. Para a maioria das validacoes isso e suficiente, mas se voce corta por posicoes exibidas, contar caracteres "visiveis" exige logica separada na camada de aplicacao.