O par ascii e chr converte um caractere no seu codigo numerico e vice-versa. Parece trivial, mas sobre essas duas funcoes se apoiam a geracao de rotulos de letras, a insercao de caracteres de controle como a quebra de linha e o trabalho cuidadoso com Unicode. Vamos ver as duas no PostgreSQL e apontar onde MySQL e ClickHouse se comportam diferente.
O fato central a fixar desde o inicio: em um banco com codificacao UTF-8 ascii retorna o ponto de codigo Unicode completo do primeiro caractere, nao o valor de um byte, portanto e a inversa exata de chr. E isso que diferencia o PostgreSQL do MySQL, cuja funcao homonima ASCII() olha apenas o primeiro byte. Trabalhar com codigos de caracteres brilha ao diagnosticar dados invisiveis: tabulacoes, quebras de linha, caracteres de controle e importacoes suspeitas onde o olho nao distingue um espaco de um espaco inquebravel. Uma consulta com ascii(col) mostra o codigo real de um caractere na propria expressao, nao como o terminal o desenha.
ascii: o codigo do primeiro caractere
ascii(text) retorna o ponto de codigo do primeiro caractere de uma string e ignora o resto. Para uma string vazia o resultado e zero. Em um banco UTF-8 este e um ponto de codigo Unicode real, nao um numero de byte.
SELECT ascii('A');
SELECT ascii('a');
SELECT ascii('Apple');
SELECT ascii('');
Como apenas o primeiro caractere conta, a funcao serve para classificar pela inicial. Agrupe funcionarios pelo codigo em maiuscula da primeira letra:
SELECT ascii(upper(left(name, 1))) AS first_code,
count(*) AS people
FROM employees
GROUP BY first_code
ORDER BY first_code;
A jogada pratica central e comparar codigos em vez de letras. Por exemplo, selecione usuarios cujo nome comeca com uma letra latina entre A e M:
SELECT id, name
FROM users
WHERE ascii(upper(left(name, 1))) BETWEEN ascii('A') AND ascii('M');
chr: o caractere para um codigo
chr(integer) faz o inverso: retorna o caractere para um ponto de codigo dado. No PostgreSQL e um ponto de codigo Unicode completo, nao apenas ASCII: chr(233) da um e acentuado, chr(8364) o sinal do euro, e qualquer codigo acima de 127 e codificado como UTF-8. O argumento 0 nao e permitido, e um valor grande demais lanca um erro em vez de retornar um caractere de lixo.
SELECT chr(65);
SELECT chr(97);
SELECT chr(8364);
O idioma chr(ascii(x) + n) desloca uma letra pelo alfabeto somando um deslocamento ao seu codigo. Gere rotulos A, B, C, ... para numerar linhas em um relatorio:
SELECT chr(ascii('A') + (n - 1)) AS label
FROM generate_series(1, 5) AS g(n);
A mesma abordagem constroi codigos de letras para departamentos ou identificadores curtos sem uma tabela de consulta a parte. Basta manter a aritmetica dentro da faixa pretendida: depois de ascii('Z') vem nao letras, mas os sinais [, \, ], entao um contador de A a Z precisa reiniciar ou passar para rotulos de duas letras na mao.
Caracteres de controle: quebras de linha e tabulacoes
chr e a forma padrao de produzir caracteres invisiveis que sao incomodos ou impossiveis de digitar dentro de um literal de string. Os mais comuns sao a quebra de linha chr(10) e a tabulacao chr(9); cole-os aos seus dados com || e voce monta um valor de varias linhas na propria consulta.
SELECT u.name || chr(10) || u.email AS contact_card
FROM users AS u
WHERE u.id = 1;
Monte uma exportacao tipo CSV de pedidos em que as linhas sao separadas por quebra de linha e os campos por tabulacao:
SELECT string_agg(
o.id || chr(9) || o.amount || chr(9) || o.status,
chr(10) ORDER BY o.id
) AS report
FROM orders AS o
WHERE o.status = 'paid';
Codigos uteis que vale a pena memorizar:
chr(9) e uma tabulacao horizontal;
chr(10) e uma quebra de linha (LF);
chr(13) e um retorno de carro (CR);
chr(13) || chr(10) e o par CRLF para compatibilidade com Windows.
Pegadinha: no PostgreSQL o par e simetrico, no MySQL nao
Ao contrario de um mito comum, no PostgreSQL ascii nao corta um caractere ate um byte. Em um banco com codificacao UTF-8 ambas as funcoes operam sobre pontos de codigo Unicode completos e sao inversas mutuas: para um e acentuado ascii retorna 233 e chr(233) reconstroi a mesma letra. A ida e volta chr(ascii(x)) e correta nao apenas para ASCII, mas para qualquer caractere Unicode, desde que a codificacao do banco seja UTF-8. A armadilha aparece ao levar o codigo para outro engine, onde uma funcao com o mesmo nome significa algo bem diferente.
SELECT chr(ascii('A'));
SELECT chr(233);
A armadilha de verdade se esconde justamente aqui, nas diferencas entre engines:
- No MySQL a funcao se chama
ASCII() e retorna o valor do primeiro byte, nao o ponto de codigo — justamente o corte que o PostgreSQL nao faz. Para o ponto Unicode completo use ORD(). A inversa e CHAR(N USING utf8mb4), e sem charset CHAR() retorna uma string binaria.
- No ClickHouse existe
char() (aceita varios codigos de uma vez) e ajudantes de ponto de codigo como ord sobre UTF-8; os nomes e o comportamento diferem do Postgres.
A pegadinha com ascii e chr quase nunca esta na funcao do PostgreSQL em si, mas nos valores extremos e na passagem para outro engine: o primeiro caractere versus a string inteira, o Unicode multibyte e as diferentes expectativas sobre a codificacao. Antes de migrar entre PostgreSQL, MySQL e ClickHouse, rode uma tabela pequena com NULL, uma string vazia e alguns caracteres fora do ASCII — digamos cirilico e um emoji. Os engines costumam concordar nas letras latinas e divergir justamente onde os dados de teste sao limpos demais: codigo como ascii(name) vai retornar em silencio numeros diferentes no PostgreSQL e no MySQL para a mesma letra é.
Outro ponto pratico sao os indices. A expressao ascii(upper(left(name, 1))) em um WHERE e calculada por linha, e um B-tree comum sobre name nao ajuda. Se esse filtro esta em um caminho quente, crie um indice de expressao para ele ou leve o primeiro codigo para uma coluna gerada. Em uma amostra pequena a diferenca e invisivel, mas em tabelas grandes uma varredura sequencial sobre ascii(...) sai caro.
A conclusao pratica: dentro do PostgreSQL, ascii e chr sao simetricas e trabalham com Unicode completo, entao para gerar rotulos latinos e caracteres de controle sao confiaveis. O perigo nao e o par em si, mas o nome: ASCII significa "primeiro byte" no MySQL e "ponto de codigo Unicode" no PostgreSQL, e e preciso ter essa diferenca em mente ao portar consultas.
O par
asciiechrconverte um caractere no seu codigo numerico e vice-versa. Parece trivial, mas sobre essas duas funcoes se apoiam a geracao de rotulos de letras, a insercao de caracteres de controle como a quebra de linha e o trabalho cuidadoso com Unicode. Vamos ver as duas no PostgreSQL e apontar onde MySQL e ClickHouse se comportam diferente.O fato central a fixar desde o inicio: em um banco com codificacao UTF-8
asciiretorna o ponto de codigo Unicode completo do primeiro caractere, nao o valor de um byte, portanto e a inversa exata dechr. E isso que diferencia o PostgreSQL do MySQL, cuja funcao homonimaASCII()olha apenas o primeiro byte. Trabalhar com codigos de caracteres brilha ao diagnosticar dados invisiveis: tabulacoes, quebras de linha, caracteres de controle e importacoes suspeitas onde o olho nao distingue um espaco de um espaco inquebravel. Uma consulta comascii(col)mostra o codigo real de um caractere na propria expressao, nao como o terminal o desenha.ascii: o codigo do primeiro caractere
ascii(text)retorna o ponto de codigo do primeiro caractere de uma string e ignora o resto. Para uma string vazia o resultado e zero. Em um banco UTF-8 este e um ponto de codigo Unicode real, nao um numero de byte.SELECT ascii('A'); -- 65 SELECT ascii('a'); -- 97 SELECT ascii('Apple'); -- 65, only the first character matters SELECT ascii(''); -- 0Como apenas o primeiro caractere conta, a funcao serve para classificar pela inicial. Agrupe funcionarios pelo codigo em maiuscula da primeira letra:
SELECT ascii(upper(left(name, 1))) AS first_code, count(*) AS people FROM employees GROUP BY first_code ORDER BY first_code;A jogada pratica central e comparar codigos em vez de letras. Por exemplo, selecione usuarios cujo nome comeca com uma letra latina entre A e M:
SELECT id, name FROM users WHERE ascii(upper(left(name, 1))) BETWEEN ascii('A') AND ascii('M');chr: o caractere para um codigo
chr(integer)faz o inverso: retorna o caractere para um ponto de codigo dado. No PostgreSQL e um ponto de codigo Unicode completo, nao apenas ASCII:chr(233)da um e acentuado,chr(8364)o sinal do euro, e qualquer codigo acima de 127 e codificado como UTF-8. O argumento 0 nao e permitido, e um valor grande demais lanca um erro em vez de retornar um caractere de lixo.SELECT chr(65); -- A SELECT chr(97); -- a SELECT chr(8364); -- the euro signO idioma
chr(ascii(x) + n)desloca uma letra pelo alfabeto somando um deslocamento ao seu codigo. Gere rotulos A, B, C, ... para numerar linhas em um relatorio:SELECT chr(ascii('A') + (n - 1)) AS label FROM generate_series(1, 5) AS g(n); -- A, B, C, D, EA mesma abordagem constroi codigos de letras para departamentos ou identificadores curtos sem uma tabela de consulta a parte. Basta manter a aritmetica dentro da faixa pretendida: depois de
ascii('Z')vem nao letras, mas os sinais[,\,], entao um contador de A a Z precisa reiniciar ou passar para rotulos de duas letras na mao.Caracteres de controle: quebras de linha e tabulacoes
chre a forma padrao de produzir caracteres invisiveis que sao incomodos ou impossiveis de digitar dentro de um literal de string. Os mais comuns sao a quebra de linhachr(10)e a tabulacaochr(9); cole-os aos seus dados com||e voce monta um valor de varias linhas na propria consulta.SELECT u.name || chr(10) || u.email AS contact_card FROM users AS u WHERE u.id = 1;Monte uma exportacao tipo CSV de pedidos em que as linhas sao separadas por quebra de linha e os campos por tabulacao:
SELECT string_agg( o.id || chr(9) || o.amount || chr(9) || o.status, chr(10) ORDER BY o.id ) AS report FROM orders AS o WHERE o.status = 'paid';Codigos uteis que vale a pena memorizar:
chr(9)e uma tabulacao horizontal;chr(10)e uma quebra de linha (LF);chr(13)e um retorno de carro (CR);chr(13) || chr(10)e o par CRLF para compatibilidade com Windows.Pegadinha: no PostgreSQL o par e simetrico, no MySQL nao
Ao contrario de um mito comum, no PostgreSQL
asciinao corta um caractere ate um byte. Em um banco com codificacao UTF-8 ambas as funcoes operam sobre pontos de codigo Unicode completos e sao inversas mutuas: para um e acentuadoasciiretorna 233 echr(233)reconstroi a mesma letra. A ida e voltachr(ascii(x))e correta nao apenas para ASCII, mas para qualquer caractere Unicode, desde que a codificacao do banco seja UTF-8. A armadilha aparece ao levar o codigo para outro engine, onde uma funcao com o mesmo nome significa algo bem diferente.-- round trip works for ASCII SELECT chr(ascii('A')); -- A -- chr handles full Unicode code points directly SELECT chr(233); -- e with acute accentA armadilha de verdade se esconde justamente aqui, nas diferencas entre engines:
ASCII()e retorna o valor do primeiro byte, nao o ponto de codigo — justamente o corte que o PostgreSQL nao faz. Para o ponto Unicode completo useORD(). A inversa eCHAR(N USING utf8mb4), e sem charsetCHAR()retorna uma string binaria.char()(aceita varios codigos de uma vez) e ajudantes de ponto de codigo comoordsobre UTF-8; os nomes e o comportamento diferem do Postgres.A pegadinha com
asciiechrquase nunca esta na funcao do PostgreSQL em si, mas nos valores extremos e na passagem para outro engine: o primeiro caractere versus a string inteira, o Unicode multibyte e as diferentes expectativas sobre a codificacao. Antes de migrar entre PostgreSQL, MySQL e ClickHouse, rode uma tabela pequena com NULL, uma string vazia e alguns caracteres fora do ASCII — digamos cirilico e um emoji. Os engines costumam concordar nas letras latinas e divergir justamente onde os dados de teste sao limpos demais: codigo comoascii(name)vai retornar em silencio numeros diferentes no PostgreSQL e no MySQL para a mesma letra é.Outro ponto pratico sao os indices. A expressao
ascii(upper(left(name, 1)))em umWHEREe calculada por linha, e um B-tree comum sobrenamenao ajuda. Se esse filtro esta em um caminho quente, crie um indice de expressao para ele ou leve o primeiro codigo para uma coluna gerada. Em uma amostra pequena a diferenca e invisivel, mas em tabelas grandes uma varredura sequencial sobreascii(...)sai caro.A conclusao pratica: dentro do PostgreSQL,
asciiechrsao simetricas e trabalham com Unicode completo, entao para gerar rotulos latinos e caracteres de controle sao confiaveis. O perigo nao e o par em si, mas o nome:ASCIIsignifica "primeiro byte" no MySQL e "ponto de codigo Unicode" no PostgreSQL, e e preciso ter essa diferenca em mente ao portar consultas.