SQL Funções Numéricas
A Loja Aurora vive de números: preços, estoques, fretes, salários. A Ana precisa de contas o tempo todo: o preço com 10% de desconto, arredondado em centavos; quantas caixas de 12 unidades cabem no estoque; a porcentagem de clientes VIP. O SQL faz essas contas direto no SELECT. Nesta aula você vai ver as operações e as funções numéricas do SQLite, e duas armadilhas que pegam até gente experiente: a divisão inteira (no SQLite, 7 / 2 dá 3!) e os números "quebrados" do ponto flutuante.
- As quatro operações e o resto da divisão (
%). - Por que
7 / 2dá3e7 / 2.0dá3.5, e como isso estraga médias e porcentagens. roundpara arredondar em casas decimais, e por que ele não mostra4.90.ceil,flooretrunc: arredondar para cima, para baixo e cortar.abs,signe as funções matemáticas:sqrt,pow,log,pi…random(), o sorteio, e por que ele não serve para resultados fixos.- Por que
0.1 + 0.2 = 0.3dá "falso".
Os exemplos rodam de verdade no seu navegador, num SQLite com o banco da Loja Aurora, e cada exemplo parte do banco limpo. As funções matemáticas (sqrt, pow, log…) são opcionais no SQLite; o editor do guia e o sqlite3 da Ana vêm com elas ligadas, assim como as versões oficiais do SQLite para Linux, Windows e Mac.
A ideia: a calculadora do caixa
No caixa da loja, a calculadora faz as contas e a Ana decide o que fazer com o resultado: arredondar os centavos, arredondar para cima ("preciso de 3 caixas, não de 2,9"), cortar a parte quebrada. O SQL tem a calculadora (os operadores + - * / %) e as decisões (as funções round, ceil, floor…). Como nas Funções de Texto, nada disso muda a tabela: só o que o SELECT mostra.
1. As operações
SELECT nome, preco, estoque,
preco * estoque AS valor_em_estoque,
preco + 5 AS mais_5,
preco - 1.5 AS menos_1_e_50
FROM produtos
WHERE id IN (1, 2, 3);
| Operador | Faz | Exemplo | Resultado |
|---|---|---|---|
+ | Soma | 7 + 2 | 9 |
- | Subtração | 7 - 2 | 5 |
* | Multiplicação | 7 * 2 | 14 |
/ | Divisão | 7 / 2 | 3 (!) — seção 2 |
% | Resto da divisão | 7 % 2 | 1 |
A ordem é a da escola: *, / e % antes de + e -. Na dúvida, use parênteses: (preco + 5) * 2. E NULL em qualquer conta dá NULL (aula O Valor NULL).
2. A divisão inteira: 7 / 2 = 3
Esta é a armadilha número um do SQLite. Veja:
SELECT 7 / 2 AS inteiro_por_inteiro,
7 / 2.0 AS com_ponto_zero,
7.0 / 2 AS outro_com_ponto,
7 % 2 AS resto,
-7 / 2 AS negativo;
Quando os dois lados da divisão são números inteiros (INTEGER), o SQLite faz uma divisão inteira: divide e joga fora a parte depois da vírgula. 7 / 2 é 3,5, e o SQLite fica só com o 3. É como dividir 7 bombons entre 2 crianças sem partir nenhum: cada uma ganha 3, e o resto (7 % 2) é 1 bombom. Com números negativos, o corte é em direção ao zero: -7 / 2 dá -3.
Basta um dos lados ser número real (com ponto) para a divisão ser "de verdade": 7 / 2.0 e 7.0 / 2 dão 3.5.
Onde isso morde: médias e porcentagens
A coluna estoque é inteira. A Ana quis calcular a média de estoque dos produtos "na mão", somando e dividindo pela quantidade:
SELECT SUM(estoque) AS total,
COUNT(*) AS produtos,
SUM(estoque) / COUNT(*) AS media_na_mao,
AVG(estoque) AS media_avg
FROM produtos;
3301 ÷ 45 é 73,355…, mas a conta "na mão" deu 73: inteiro dividido por inteiro. A AVG (aula AVG()) sempre devolve um número real, por isso acertou. Com porcentagens o estrago é maior:
SELECT SUM(vip) AS vips,
COUNT(*) AS clientes,
SUM(vip) * 100 / COUNT(*) AS pct_errada,
ROUND(SUM(vip) * 100.0 / COUNT(*), 1) AS pct_certa
FROM clientes;
5 VIPs em 30 clientes são 16,67%. A conta só com inteiros deu 16; trocando o 100 por 100.0, a conta inteira passa a ser real e dá 16.7 (com o ROUND da seção 3).
| Jeito de evitar a divisão inteira | Exemplo |
|---|---|
Escrever um dos números com .0 | SUM(vip) * 100.0 / COUNT(*) |
Multiplicar por 1.0 | SUM(estoque) * 1.0 / COUNT(*) |
Converter com CAST | CAST(SUM(estoque) AS REAL) / COUNT(*) |
| Usar a função pronta, quando existe | AVG(estoque) |
Experimente: a média de estoque "na mão" saiu 73, sem as casas decimais. Mude a conta para ela dar o valor certo, 73,3…, sem usar AVG.
SELECT SUM(estoque) / COUNT(*) AS media FROM produtos;
SELECT SUM(estoque) * 1.0 / COUNT(*) AS media FROM produtos;
A divisão inteira também é útil
Às vezes cortar a parte quebrada é exatamente o que se quer. Os produtos vão para caixas de 12 unidades: quantas caixas cheias dá para montar, e quantas unidades sobram?
SELECT nome, estoque,
estoque / 12 AS caixas_cheias,
estoque % 12 AS sobram
FROM produtos
WHERE id IN (1, 5, 11, 14);
A mochila tem 35 no estoque: 2 caixas cheias (24 mochilas) e sobram 11. A divisão inteira dá as caixas; o % dá a sobra. Um uso clássico do % é descobrir se um número é par: id % 2 = 0.
3. round: arredondar
round ("arredondar") recebe o número e quantas casas decimais manter. Sem o segundo argumento, arredonda para o inteiro mais próximo:
SELECT nome, preco,
preco * 0.9 AS com_desconto,
round(preco * 0.9, 2) AS em_centavos,
round(preco * 0.9) AS em_reais
FROM produtos
WHERE id IN (1, 7, 14);
- preco * 0.9
- 10% de desconto (paga-se 90% do preço). O caderno de 12.35 sai por 11.115: três casas, que não existem em dinheiro.
- round(…, 2)
- Duas casas, os centavos: 11.115 vira
11.12. A metade (o 5) arredonda para cima. - round(…)
- Sem casas: o real mais próximo,
11.0.
Repare que o round sempre devolve um número real: mesmo round(…) sem casas mostra 11.0, com o .0.
O round não "completa" as casas
Um engano comum é achar que o round(x, 2) mostra sempre duas casas:
SELECT nome, salario,
round(salario * 1.05, 2) AS com_aumento,
printf('%.2f', salario * 1.05) AS com_aumento_2_casas
FROM funcionarios
WHERE id IN (2, 8);
O Davi passa a ganhar 2782.5, e não 2782.50. O round arredonda o número; o número 2782.50 é o mesmo que 2782.5, e o SQLite escreve o mais curto. Para mostrar duas casas, quem resolve é a printf (aula Funções de Texto), que devolve texto. A regra: round para fazer contas e guardar; printf para exibir.
As metades
SELECT round(2.5) AS dois_e_meio,
round(-2.5) AS menos_dois_e_meio,
round(3.14159, 2) AS pi_2_casas,
round(2.675, 2) AS surpresa;
No SQLite, a metade arredonda para longe do zero: 2.5 vira 3.0 e -2.5 vira -3.0. E o 2.675? Deveria dar 2.68, mas deu 2.67. Não é defeito do round: o computador não consegue guardar 2,675 exato e guarda algo como 2,67499999…, que arredonda para baixo. A seção 6 explica.
4. ceil, floor e trunc: para cima, para baixo, cortar
O round vai para o mais perto. Às vezes você precisa de uma direção:
SELECT ceil(4.1) AS ceil_41,
floor(4.9) AS floor_49,
trunc(4.9) AS trunc_49,
ceil(-4.9) AS ceil_menos_49,
floor(-4.1) AS floor_menos_41,
trunc(-4.9) AS trunc_menos_49;
| Função | Faz | 4.9 | -4.9 |
|---|---|---|---|
ceil(x) ou ceiling(x) | Para cima: o menor inteiro que não é menor que x (ceiling = "teto") | 5.0 | -4.0 |
floor(x) | Para baixo: o maior inteiro que não é maior que x (floor = "chão") | 4.0 | -5.0 |
trunc(x) | Corta a parte depois do ponto (truncate), em direção ao zero | 4.0 | -4.0 |
round(x) | O inteiro mais perto | 5.0 | -5.0 |
Para números positivos, floor e trunc dão o mesmo; a diferença aparece nos negativos. O caso de uso clássico do ceil é "quantas caixas eu preciso para guardar tudo?": com 35 mochilas e caixas de 12, são 2,9 caixas, e ninguém compra 0,9 caixa: são 3.
SELECT nome, estoque,
estoque / 12 AS caixas_cheias,
ceil(estoque / 12.0) AS caixas_precisas
FROM produtos
WHERE id IN (1, 5, 11, 14);
Repare no 12.0: com estoque / 12, a divisão inteira já cortaria o 2,9 para 2 antes do ceil agir, e o ceil(2) continuaria 2.
5. abs, sign e as funções matemáticas
abs ("absoluto") tira o sinal: a distância até o zero. sign ("sinal") diz só o sinal: -1, 0 ou 1. Servem, por exemplo, para comparar o preço de catálogo com o preço cobrado num pedido, sem se importar se a diferença foi para cima ou para baixo:
SELECT i.pedido_id, p.nome,
p.preco AS catalogo,
i.preco_unitario AS cobrado,
round(i.preco_unitario - p.preco, 2) AS diferenca,
abs(round(i.preco_unitario - p.preco, 2)) AS distancia,
sign(i.preco_unitario - p.preco) AS sinal
FROM itens_pedido AS i
JOIN produtos AS p ON p.id = i.produto_id
WHERE i.preco_unitario <> p.preco;
Os três itens vendidos em promoção (os casos de "preço histórico" da loja): todos com sinal -1, ou seja, cobrados abaixo do catálogo.
As funções matemáticas
SELECT sqrt(16) AS raiz_de_16,
pow(2, 10) AS dois_elevado_a_10,
pi() AS pi,
log10(1000) AS log10_de_1000,
ln(exp(1)) AS ln_de_e,
log(2, 8) AS log_base_2_de_8,
mod(7.5, 2) AS mod_com_real;
| Função | O que faz | Exemplo → resultado |
|---|---|---|
sqrt(x) | Raiz quadrada (square root) | sqrt(16) → 4.0; sqrt(-1) → NULL |
pow(x, y) ou power(x, y) | x elevado a y (power, "potência") | pow(2, 10) → 1024.0 |
exp(x) | e (2,718…) elevado a x | exp(1) → 2.71828182845905 |
ln(x) | Logaritmo natural (base e) | ln(exp(1)) → 1.0 |
log(x) ou log10(x) | Logaritmo na base 10 | log(1000) → 3.0 |
log2(x) | Logaritmo na base 2 | log2(8) → 3.0 |
log(b, x) | Logaritmo de x na base b | log(2, 8) → 3.0 |
pi() | O número π | 3.14159265358979 |
mod(x, y) | Resto da divisão, também com números reais | mod(7.5, 2) → 1.5 |
sin, cos, tan, degrees, radians… | Trigonometria (ângulos em radianos) | degrees(pi()) → 180.0 |
O mod e o % parecem iguais, mas o % transforma os dois lados em inteiros antes: 7.5 % 2 dá 1.0 (ele calculou 7 % 2), enquanto mod(7.5, 2) dá 1.5.
random(): o sorteio
random() ("aleatório") devolve um número inteiro sorteado, enorme, positivo ou negativo, diferente a cada chamada. Como o resultado muda toda vez, ele aparece aqui numa sessão de terminal da Ana, e não num quadro de resultado:
$ sqlite3 loja.db SQLite version 3.50.4 2025-07-30 19:33:53 Enter ".help" for usage hints. sqlite> SELECT random(); -5499406115346342480 sqlite> SELECT random(); -8850731515571685751 « outro número sqlite> SELECT abs(random()) % 6 + 1 AS dado; 4 sqlite> SELECT abs(random()) % 6 + 1 AS dado; 1 « um dado de 6 faces sqlite> SELECT nome FROM produtos ORDER BY random() LIMIT 1; Jogo de Panelas 5 peças sqlite> SELECT nome FROM produtos ORDER BY random() LIMIT 1; Jogo de Tabuleiro Trilha Mística « um produto sorteado sqlite> .quit
abs(random())tira o sinal: um número enorme e positivo.% 6fica com o resto da divisão por 6: um número de 0 a 5.+ 1passa para 1 a 6: um dado.
O ORDER BY random() embaralha as linhas: com LIMIT 1, sorteia um produto, por exemplo para o "produto do dia". No editor do guia, random() também funciona, e dá um número diferente a cada Rodar.
6. Os números "quebrados": ponto flutuante
Os números com ponto (o tipo REAL) são guardados em binário, e muitos números que parecem simples para nós, como 0,1, não têm escrita exata em binário, assim como 1/3 não tem escrita exata em decimal (0,3333…). O computador guarda um valor quase igual. Na maioria das vezes ninguém percebe, mas numa comparação aparece:
SELECT 0.1 + 0.2 AS soma,
0.1 + 0.2 = 0.3 AS igual_a_0_3,
(0.1 + 0.2) - 0.3 AS diferenca,
round(0.1 + 0.2, 2) = 0.3 AS arredondado_igual;
A soma aparece como 0.3 (o SQLite mostra só 15 algarismos), mas a comparação = 0.3 deu 0, falso! A diferença é minúscula, 0,0000000000000000555 (o e-17 quer dizer "vezes 10 elevado a −17"), mas existe. Arredondando antes de comparar, a igualdade volta a ser verdadeira.
Com dinheiro, isso aparece nas somas grandes e nas médias: a média do valor dos itens vendidos é 84.2609090909091, com uma cauda de casas que não são centavos de nada.
SELECT AVG(quantidade * preco_unitario) AS media_crua,
round(AVG(quantidade * preco_unitario), 2) AS media_em_centavos
FROM itens_pedido;
- Arredonde com
round(x, 2)o resultado final das contas com dinheiro. - Nunca compare reais com
=sem arredondar antes:round(a, 2) = round(b, 2). - Sistemas que precisam de exatidão total (bancos, contabilidade) costumam guardar centavos em inteiros: 12,35 vira
1235. Inteiros são sempre exatos.
7. Formas diferentes de fazer
| Quero… | Use | Exemplo com 35 mochilas, caixas de 12 |
|---|---|---|
| caixas cheias (cortar) | estoque / 12 (dois inteiros) ou floor(estoque / 12.0) | 2 / 2.0 |
| caixas necessárias (para cima) | ceil(estoque / 12.0) ou (estoque + 11) / 12 | 3.0 / 3 |
| o que sobra | estoque % 12 ou mod(estoque, 12) | 11 / 11.0 |
| a divisão exata | estoque / 12.0 ou estoque * 1.0 / 12 | 2.91666666666667 |
| arredondar em centavos | round(x, 2) | — |
| mostrar com 2 casas | printf('%.2f', x) (texto) | — |
O (estoque + 11) / 12 é um truque antigo de programador: somar "divisor − 1" antes da divisão inteira faz ela arredondar para cima, e o resultado fica inteiro.
Quando dá errado
As contas quase nunca dão erro no SQLite: elas devolvem um resultado errado, ou NULL. Por isso os casos abaixo merecem atenção.
SELECT 7 / 0 AS inteiro,
7.0 / 0 AS real,
7 % 0 AS resto;
O que aconteceu: três NULL, sem erro. Uma média com COUNT(*) igual a 0 no divisor dá NULL em silêncio. (No PostgreSQL e no SQL Server, dividir por zero dá erro.)
Como consertar: se o divisor pode ser zero, decida o que mostrar: coalesce(a / b, 0), ou um CASE WHEN b = 0 THEN … END (aula CASE).
SELECT COUNT(*) / 45 * 100 AS pct_errada,
COUNT(*) * 100.0 / 45 AS pct_certa
FROM produtos
WHERE descontinuado = 1;
O que aconteceu: 2 produtos descontinuados em 45 deveriam dar 4,4%, mas a primeira conta deu 0. Ela faz primeiro 2 / 45, que na divisão inteira é 0, e depois 0 * 100. Duas armadilhas juntas: a divisão inteira e a ordem da conta.
Como consertar: multiplique primeiro e use 100.0: COUNT(*) * 100.0 / 45.
SELECT round(1234.5678, -2) AS centenas;
O que aconteceu: 1235.0, e não 1200. Em outros bancos, round(x, -2) arredonda para a centena; no SQLite, um número de casas negativo é tratado como 0.
Como consertar: divida, arredonde e multiplique de volta: round(1234.5678 / 100) * 100 dá 1200.0.
SELECT '12.35' * 2 AS texto_numero,
'3 bananas' + 1 AS texto_com_letras,
'R$ 12,35' * 2 AS com_moeda;
O que aconteceu: o SQLite tenta transformar o texto em número. '12.35' funciona (24.7). '3 bananas' vira 3, aproveitando só o começo. E 'R$ 12,35' vira 0, porque começa com letra. Nenhum erro.
Como consertar: guarde números em colunas numéricas (INTEGER ou REAL), com ponto e sem símbolo de moeda (aula Os Tipos no SQLite).
SELECT abs(-9223372036854775808);
O que aconteceu: integer overflow, "estouro de inteiro". O maior inteiro do SQLite é 9223372036854775807 (cerca de 9 quintilhões), e o menor é −9223372036854775808. O abs do menor não cabe. É um dos poucos erros de conta de verdade; na prática, só aparece com números absurdos (ou com abs(random()), numa chance em 18 quintilhões).
- Divisão inteira: no PostgreSQL e no SQL Server,
7 / 2também dá3, como no SQLite. No MySQL, não:7 / 2dá3.5000, e a divisão inteira é um operador separado,7 DIV 2. - Divisão por zero: o MySQL devolve NULL, como o SQLite; o PostgreSQL e o SQL Server dão erro.
log(x)com um argumento: no SQLite e no PostgreSQL é o logaritmo na base 10; no MySQL e no SQL Server é o logaritmo natural. Para não se confundir, use os nomes explícitoslog10eln(o SQL Server não temln: lá, o natural é oLOG).roundcom metades: o MySQL arredonda2.5para3em números exatos (DECIMAL), mas um número aproximado como25E-1pode ir para2(o par mais próximo). Os bancos com servidor têm o tipoDECIMAL/NUMERICexato, bom para dinheiro; no SQLite, ele viraREALouINTEGER.
-- MySQL SELECT 7 / 2; -- 3.5000 SELECT 7 DIV 2; -- 3
Referência rápida
| Escrita | O que faz | Exemplo → resultado |
|---|---|---|
a / b | Divisão (inteira se os dois forem inteiros) | 7 / 2 → 3; 7 / 2.0 → 3.5 |
a % b | Resto da divisão inteira | 7 % 2 → 1 |
abs(x) | Valor sem sinal | abs(-4.9) → 4.9 |
sign(x) | -1, 0 ou 1 | sign(-5) → -1 |
round(x, n) | Arredonda em n casas (real); metade vai para longe do zero | round(11.115, 2) → 11.12 |
ceil(x) / ceiling(x) | Para cima | ceil(2.1) → 3.0 |
floor(x) | Para baixo | floor(2.9) → 2.0 |
trunc(x) | Corta a parte decimal | trunc(-2.9) → -2.0 |
sqrt(x), pow(x, y), exp(x) | Raiz, potência, e elevado a x | pow(2, 3) → 8.0 |
ln(x), log(x)/log10(x), log2(x), log(b, x) | Logaritmos | log(100) → 2.0 |
mod(x, y) | Resto, também com reais | mod(7.5, 2) → 1.5 |
pi() | π | 3.14159265358979 |
random() | Inteiro sorteado (muda a cada chamada) | abs(random()) % 6 + 1 → um dado |
CAST(x AS REAL) / CAST(x AS INTEGER) | Converte (para inteiro, corta) | CAST(4.9 AS INTEGER) → 4 |
Exercícios
1. Quanto dá?
No SQLite, quanto vale 9 / 4?
2. Duas casas
Complete para arredondar o preço com desconto em centavos:
3. Qual destas contas dá 3.5 no SQLite?
4. A loja tem 35 mochilas e as guarda em caixas de 12. Qual expressão dá o número de caixas necessárias para guardar todas?
5. O que round(2782.5, 2) mostra no SQLite?
6. Por que 0.1 + 0.2 = 0.3 dá 0 (falso) no SQLite?
Missão final: as caixas da papelaria
Missão: o estoque da papelaria (categoria 1) vai ser guardado em caixas de 12 unidades. A consulta calcula as caixas com estoque / 12, e a Ana descobriu que faltou caixa: as 35 mochilas precisam de 3 caixas, e a conta disse 2. Conserte a coluna caixas para dar o número de caixas necessárias para guardar todo o estoque de cada produto.
SELECT nome, estoque, estoque / 12 AS caixas FROM produtos WHERE categoria_id = 1;
SELECT nome, estoque, ceil(estoque / 12.0) AS caixas FROM produtos WHERE categoria_id = 1;
Com ceil(estoque / 12.0), as mochilas dão 3.0; com o truque (estoque + 11) / 12, dão 3. As duas respostas estão certas. Repare que ceil(estoque / 12), sem o .0, continua errado: a divisão inteira corta antes do ceil.
Resumo
- Operadores:
+ - * /e%(resto). NULL em qualquer conta dá NULL; dividir por zero dá NULL, sem erro. - No SQLite, inteiro / inteiro = divisão inteira:
7 / 2dá3. Ponha um.0(7 / 2.0,* 100.0) para a divisão de verdade. Cuidado com médias e porcentagens feitas "na mão". round(x, n)arredonda em n casas e devolve real; ele não completa zeros (2782.5). Para mostrar2782.50, useprintf('%.2f', x).ceilarredonda para cima,floorpara baixo,trunccorta;abstira o sinal esigndiz qual é.- Funções matemáticas:
sqrt,pow,exp,ln,log(base 10 no SQLite),log2,mod,pi. random()sorteia um inteiro a cada chamada; nunca use em resultados que precisam ser fixos.- Números reais são aproximados:
0.1 + 0.2 = 0.3é falso. Com dinheiro, arredonde comround(x, 2)antes de comparar e de mostrar.