sqlpostgresqlstringsutf-8

char_length no SQL: contando caracteres, nao bytes

char_length retorna o numero de caracteres de uma string, octet_length o seu tamanho em bytes, e LENGTH conta caracteres ou bytes conforme PostgreSQL, MySQL ou ClickHouse.

5 min de leituraReferênciasql · postgresql · strings · utf-8 · mysql · clickhouse

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');        -- 4
SELECT char_length('cafe');        -- 4
SELECT char_length('Moscow');      -- 6

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:

-- one visible character, two bytes in UTF-8
SELECT char_length(U&'a\0301') AS chars,   -- 1
       octet_length(U&'a\0301') AS bytes;  -- 2

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;   -- byte budget for an index prefix

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.

-- 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');   -- 4

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.

Pratique com exercícios reais

Resolva exercícios no treinador de SQL com correção instantânea e dicas.

Abrir o treinador