PostgreSQL: Operadores e Expressões no PostgreSQL
Última atualização: 2026-08-26
1. O Que Você Vai Aprender
- Operadores aritméticos
+ - * / %e seu comportamento de overflow e precisão - Operadores de comparação e lógicos
- Concatenação de strings
||e operadores de correspondência de padrão - Conversões de tipo
::eCAST() - Construtores de linha
ROW()e operadores de intervalo@> <@ &< &> - - Expressões de subconsulta
IN / EXISTS / ANY / ALL
2. A História
Charlie é um analista financeiro em uma plataforma SaaS, responsável por:
- Calcular o valor com impostos de cada pedido (
total * 1.08) - Verificar se um pedido está dentro de um determinado intervalo de datas
- Converter datas em formato string para o tipo DATE para comparação
- Verificar se os intervalos de preço se sobrepõem
Essas tarefas exigem que ele seja fluente nos vários operadores e expressões do PostgreSQL.
3. Conceito: Operadores Aritméticos
(1) Operadores Aritméticos Básicos
| Operador | Significado | Exemplo | Resultado |
|---|---|---|---|
+ |
Adição | 100 + 8 |
108 |
- |
Subtração | 500 - 50 |
450 |
* |
Multiplicação | 29.99 * 3 |
89.97 |
/ |
Divisão inteira (trunca) | 7 / 2 |
3 |
/ |
Divisão numérica (com decimal) | 7.0 / 2 |
3.5 |
% |
Módulo | 10 % 3 |
1 |
^ |
Potência | 2 ^ 10 |
1024 |
| ` | /` | Raiz quadrada | ` |
| ` | /` | Raiz cúbica | |
! |
Fatorial | 5 ! |
120 |
@ |
Valor absoluto | @ -15 |
15 |
▶ Exemplo: Calcular Valor com Impostos
SELECT
order_id,
total_amount,
total_amount * 0.08 AS tax,
total_amount * 1.08 AS total_with_tax
FROM orders
WHERE order_status = 'completed';
Output:
id | name | value
----+----------+-------
1 | example | 42
(1 row)
▶ Exemplo: Divisão Inteira vs Divisão Numérica
SELECT
7 / 2 AS int_div,
7.0 / 2 AS numeric_div,
7 / 2.0 AS numeric_div2,
CAST(7 AS NUMERIC) / 2 AS cast_div;
Output:
id | name | value
----+----------+-------
1 | example | 42
(1 row)
(2) Precedência de Operadores
| Precedência | Operador | Descrição |
|---|---|---|
| 1 (mais alta) | . |
acesso a membro |
| 2 | :: |
conversão de tipo |
| 3 | [ |
subscrito de array |
| 4 | - (unário), @, ! |
operações unárias |
| 5 | ^ |
potência |
| 6 | *, /, % |
multiplicação / divisão / módulo |
| 7 | +, - |
adição / subtração |
| 8 | ` | |
| 9 | operadores de comparação | = <> < > <= >= |
| 10 | IS, IN, BETWEEN |
predicados |
| 11 | NOT |
negação lógica |
| 12 | AND |
conjunção lógica |
| 13 (mais baixa) | OR |
disjunção lógica |
▶ Exemplo: Precedência e Parênteses
SELECT
2 + 3 * 4 AS no_parens,
(2 + 3) * 4 AS with_parens;
Output:
id | name | value
----+----------+-------
1 | example | 42
(1 row)
(3) Overflow e Precisão
O INTEGER do PostgreSQL varia de cerca de -2,1 bilhões a 2,1 bilhões; excedê-lo gera um erro. Para cálculos monetários, use NUMERIC(precision, scale) ou DECIMAL.
| Tipo | Intervalo | Precisão | Ideal para |
|---|---|---|---|
SMALLINT |
-32768 ~ 32767 | inteiro | contagens de pequeno intervalo |
INTEGER |
±2,1 bilhões | inteiro | inteiros gerais |
BIGINT |
±9,2 quintilhões | inteiro | IDs de grande intervalo |
NUMERIC(p,s) |
ilimitado | precisão arbitrária | cálculos monetários |
▶ Exemplo: Precisão Segura para Cálculos Monetários
SELECT
order_id,
total_amount::NUMERIC(12,2) AS amount,
(total_amount::NUMERIC(12,2) * 0.08)::NUMERIC(12,2) AS tax,
(total_amount::NUMERIC(12,2) * 1.08)::NUMERIC(12,2) AS total_with_tax
FROM orders;
Output:
id | name | value
----+----------+-------
1 | example | 42
(1 row)
4. Conceito: Operadores de Comparação
(1) Comparação Básica
| Operador | Significado | 1 <> 2 |
NULL = NULL |
|---|---|---|---|
= |
Igual | FALSE | NULL |
<> / != |
Diferente | TRUE | NULL |
< |
Menor que | TRUE | NULL |
> |
Maior que | FALSE | NULL |
<= |
Menor ou igual | TRUE | NULL |
>= |
Maior ou igual | FALSE | NULL |
Qualquer comparação com NULL resulta em NULL, não TRUE ou FALSE.
▶ Exemplo: Operações de Comparação
SELECT product_name, unit_price
FROM products
WHERE unit_price >= 100
AND unit_price < 500
AND category = 'Electronics';
Output:
id | name | value
----+----------+-------
1 | example | 42
(1 row)
(2) BETWEEN e Comparações Compostas
| Expressão | Equivalente |
|---|---|
x BETWEEN a AND b |
x >= a AND x <= b |
x NOT BETWEEN a AND b |
x < a OR x > b |
▶ Exemplo: Comparação de Intervalo com BETWEEN
SELECT order_id, total_amount, order_date
FROM orders
WHERE total_amount BETWEEN 100 AND 1000
AND order_date BETWEEN '2025-01-01' AND '2025-12-31';
Output:
id | name | value
----+----------+-------
1 | example | 42
(1 row)
(3) IS DISTINCT FROM
IS DISTINCT FROM trata NULL como um valor comparável, evitando a armadilha NULL das comparações padrão.
| Expressão | NULL = NULL |
NULL IS DISTINCT FROM NULL |
|---|---|---|
| Resultado | NULL | FALSE |
1 IS DISTINCT FROM NULL |
— | TRUE |
▶ Exemplo: IS DISTINCT FROM
SELECT order_id, old_status, new_status
FROM order_status_log
WHERE old_status IS DISTINCT FROM new_status;
Output:
id | name | value
----+----------+-------
1 | example | 42
(1 row)
5. Conceito: Operadores Lógicos
(1) Tabela Verdade da Lógica de Três Valores
O PostgreSQL usa lógica de três valores (TRUE / FALSE / NULL); quando NULL participa de uma operação lógica, o resultado pode ser contraintuitivo.
| a | b | a AND b | a OR b |
|---|---|---|---|
| T | T | T | T |
| T | F | F | T |
| T | N | N | T |
| F | T | F | T |
| F | F | F | F |
| F | N | F | N |
| N | T | N | T |
| N | F | F | N |
| N | N | N | N |
▶ Exemplo: NULL em Operações Lógicas
SELECT
TRUE AND NULL AS and_result,
TRUE OR NULL AS or_result,
FALSE AND NULL AS and_false,
FALSE OR NULL AS or_null,
NOT NULL AS not_result;
Output:
id | name | value
----+----------+-------
1 | example | 42
(1 row)
▶ Exemplo: Comportamento de NULL no WHERE
-- Isso exclui linhas onde discount_rate é NULL
SELECT product_name, discount_rate
FROM products
WHERE discount_rate > 0 OR discount_rate = 0;
-- Linhas NULL NÃO são incluídas porque NULL > 0 é NULL, NULL = 0 é NULL
-- Correção: tratar NULL explicitamente
SELECT product_name, discount_rate
FROM products
WHERE discount_rate > 0 OR discount_rate = 0 OR discount_rate IS NULL;
Output:
count
-------
5
(1 row)
6. Conceito: Operadores de String
(1) Concatenação de Strings ||
|| é o operador de concatenação de strings do PostgreSQL, diferente do + do SQL Server.
| Operador | Significado | Exemplo |
|---|---|---|
| ` | ` |
▶ Exemplo: Concatenação de Strings
SELECT
customer_id,
first_name || ' ' || last_name AS full_name,
'Order #' || order_id || ': $' || total_amount::TEXT AS order_summary
FROM orders o
JOIN customers c ON c.customer_id = o.customer_id;
Output:
result
----------
42.50
(1 row)
(2) Concatenando Strings com NULL
Concatenar qualquer string com NULL resulta em NULL; use COALESCE para lidar com isso.
| Expressão | Resultado |
|---|---|
| `'A' | |
| `'A' |
▶ Exemplo: Concatenação Segura de Strings
SELECT
product_name || COALESCE(' - ' || description, '') AS product_info
FROM products;
Output:
id | name | value
----+----------+-------
1 | example | 42
(1 row)
7. Conceito: Conversões de Tipo
(1) :: vs CAST()
| Forma | Exemplo | SQL Padrão | Descrição |
|---|---|---|---|
::type |
'2025-01-01'::DATE |
Não (específico do PG) | Conciso, recomendado no PG |
CAST(expr AS type) |
CAST('2025-01-01' AS DATE) |
Sim | Portável, verboso |
▶ Exemplo: String para Data
SELECT
order_id,
order_date::DATE AS date_only,
'2025-06-15'::DATE AS literal_date,
CAST('2025-06-15 14:30:00' AS TIMESTAMP) AS full_timestamp
FROM orders
WHERE order_date::DATE >= '2025-01-01'::DATE;
Output:
id | name | value
----+----------+-------
1 | example | 42
(1 row)
▶ Exemplo: Conversão de Tipo Numérico
SELECT
total_amount::NUMERIC(12,2) AS rounded,
total_amount::INTEGER AS truncated,
total_amount::TEXT AS as_text
FROM orders;
Output:
id | name | value
----+----------+-------
1 | example | 42
(1 row)
(2) Cenários Comuns de Conversão
| Origem → Destino | Uso |
|---|---|
| TEXT → DATE | '2025-01-01'::DATE |
| TEXT → TIMESTAMP | CAST(ts AS TIMESTAMP) |
| NUMERIC → TEXT | 123.45::TEXT |
| INTEGER → NUMERIC | id::NUMERIC |
| TEXT → INTEGER | '42'::INTEGER |
▶ Exemplo: Verificação de Intervalo de Datas de Charlie
SELECT order_id, total_amount, order_date
FROM orders
WHERE order_date >= '2025-01-01'::DATE
AND order_date < '2025-07-01'::DATE
AND total_amount::NUMERIC(12,2) > 500;
Output:
id | name | value
----+----------+-------
1 | example | 42
(1 row)
8. Conceito: Construtores de Linha e Expressões de Subconsulta
(1) Construtor ROW
ROW(val1, val2, ...) constrói um valor de linha anônimo, útil para comparações no nível de linha.
▶ Exemplo: Comparação com Construtor de Linha
SELECT order_id, order_status, total_amount
FROM orders
WHERE ROW(order_status, total_amount) = ROW('completed', 500);
Output:
id | name | value
----+----------+-------
1 | example | 42
(1 row)
(2) Comparação de Linha e IN
Um construtor de linha pode ser combinado com IN para correspondência de múltiplas colunas.
▶ Exemplo: Correspondência IN com Múltiplas Colunas
SELECT order_id, customer_id, order_status
FROM orders
WHERE (order_status, customer_id) IN (
('completed', 1001),
('completed', 1002),
('pending', 1003)
);
Output:
id | name | value
----+----------+-------
1 | example | 42
(1 row)
(3) Comparação de Expressões de Subconsulta
| Expressão | Semântica | Tipo de retorno |
|---|---|---|
col IN (subquery) |
Igual a qualquer valor no resultado da subconsulta | BOOLEAN |
col = ANY(subquery) |
Mesmo que IN | BOOLEAN |
col > ALL(subquery) |
Maior que todos os valores na subconsulta | BOOLEAN |
EXISTS (subquery) |
Se a subconsulta retornou alguma linha | BOOLEAN |
▶ Exemplo: Subconsulta de Comparação ALL
SELECT product_name, unit_price, category
FROM products
WHERE unit_price > ALL (
SELECT AVG(unit_price) FROM products GROUP BY category
);
Output:
result
----------
42.50
(1 row)
9. Conceito: Operadores de Intervalo
(1) Tipos de Intervalo
O PostgreSQL possui tipos de intervalo integrados como int4range, int8range, numrange, daterange e tstzrange.
▶ Exemplo: Criando um daterange
SELECT daterange('2025-01-01'::DATE, '2025-06-30'::DATE) AS h1_range;
Output:
id | name | value
----+----------+-------
1 | example | 42
(1 row)
(2) Referência de Operadores de Intervalo
| Operador | Significado | Exemplo |
|---|---|---|
@> |
Contém elemento | '[1,5]'::int4range @> 3 → TRUE |
<@ |
Está contido por | 3 <@ '[1,5]'::int4range → TRUE |
&& |
Sobreposição | '[1,5]'::int4range && '[3,8]'::int4range → TRUE |
&< |
Não se estende à direita de | '[1,3]'::int4range &< '[2,5]'::int4range → TRUE |
&> |
Não se estende à esquerda de | '[3,5]'::int4range &> '[1,3]'::int4range → TRUE |
| `- | -` | É adjacente a |
+ |
União | '[1,3]'::int4range + '[3,6]'::int4range → [1,6) |
* |
Interseção | '[1,5]'::int4range * '[3,8]'::int4range → [3,5) |
- |
Diferença | '[1,5]'::int4range - '[3,8]'::int4range → [1,3) |
▶ Exemplo: Verificando Sobreposição de Intervalos de Data
SELECT o1.order_id, o2.order_id
FROM orders o1, orders o2
WHERE o1.customer_id = o2.customer_id
AND o1.order_id < o2.order_id
AND daterange(o1.order_date, o1.delivery_date) &&
daterange(o2.order_date, o2.delivery_date);
Output:
id | name | value
----+----------+-------
1 | example | 42
(1 row)
▶ Exemplo: Verificação de Contenção de Intervalo de Preço
SELECT product_name, unit_price
FROM products
WHERE numrange(100, 500) @> unit_price::NUMERIC;
Output:
id | name | value
----+----------+-------
1 | example | 42
(1 row)
10. Fluxograma: Decisão de Conversão de Tipo
flowchart TD
A[Precisa de conversão de tipo?] --> B{Tipo alvo?}
B -->|Data/Hora| C["::DATE / ::TIMESTAMP"]
B -->|Numérico| D["::NUMERIC / ::INTEGER"]
B -->|String| E["::TEXT"]
B -->|Portabilidade necessária| F["CAST(expr AS type)"]
C --> G{A origem tem formato?}
G -->|Formato padrão| H[Conversão direta com ::]
G -->|Formato personalizado| I["TO_DATE() / TO_TIMESTAMP()"]
D --> J[Cuidado com perda de precisão]
E --> K[Cuidado com concatenação NULL]
H --> L[Concluído]
I --> L
J --> L
K --> L
F --> L
style A fill:#e1f5fe
style L fill:#c8e6c9
11. Exemplo Abrangente
Charlie concluiu todos os cálculos que sua análise financeira exigia.
-- Passo 1: Cálculo de impostos com controle de precisão
SELECT
order_id,
total_amount::NUMERIC(12,2) AS amount,
(total_amount::NUMERIC(12,2) * 0.08)::NUMERIC(12,2) AS tax,
(total_amount::NUMERIC(12,2) * 1.08)::NUMERIC(12,2) AS total_with_tax
FROM orders
WHERE order_status = 'completed'
AND order_date >= '2025-01-01'::DATE;
-- Passo 2: Verificação de intervalo de data com daterange
SELECT order_id, customer_id, order_date, total_amount
FROM orders
WHERE daterange('2025-01-01'::DATE, '2025-07-01'::DATE) @> order_date::DATE
AND order_status <> 'cancelled';
-- Passo 3: Conversão de string para data e formatação
SELECT
order_id,
'Order #' || order_id || ' - ' ||
COALESCE(customer_name, 'Unknown') ||
' ($' || total_amount::TEXT || ')' AS order_label
FROM orders o
LEFT JOIN customers c ON c.customer_id = o.customer_id
WHERE o.order_date::DATE >= '2025-01-01'::DATE
ORDER BY o.total_amount DESC;
-- Passo 4: Construtor de linha e sobreposição de intervalo
SELECT
p1.product_name AS product_a,
p2.product_name AS product_b,
numrange(p1.unit_price, p1.unit_price * 1.2) AS price_range_a,
numrange(p2.unit_price, p2.unit_price * 1.2) AS price_range_b
FROM products p1
JOIN products p2 ON p1.product_id < p2.product_id
WHERE p1.category = p2.category
AND numrange(p1.unit_price, p1.unit_price * 1.2) &&
numrange(p2.unit_price, p2.unit_price * 1.2)
AND p1.is_active = true AND p2.is_active = true;
❓ Perguntas Frequentes
P: A divisão inteira 7/2 retorna 3 ou 3.5? R: Retorna 3. No PostgreSQL, dividir dois inteiros trunca para um inteiro. Para obter 3.5, pelo menos um operando deve ser um tipo decimal: 7.0/2 ou 7::NUMERIC/2.
P: Qual é melhor,
::ouCAST()? R:::é mais conciso e é sintaxe específica do PostgreSQL;CAST()é a forma padrão SQL. Para projetos puramente PostgreSQL,::é recomendado; useCAST()quando precisar de compatibilidade entre bancos de dados.
P: Quais são as armadilhas do NULL em operações lógicas? R:
TRUE AND NULL = NULL(não TRUE),FALSE OR NULL = NULL(não FALSE),NOT NULL = NULL. No WHERE, NULL é tratado como não TRUE, então as linhas relevantes são excluídas.
P: Por que concatenar uma string com NULL resulta em NULL? R: O padrão SQL diz que qualquer concatenação de string com NULL resulta em NULL. Substitua NULL por
COALESCE(col, '')antes de concatenar.
P: Quando a comparação com construtor de linha é útil? R: Quando você precisa comparar uma combinação de várias colunas de uma vez — por exemplo,
WHERE (status, amount) = ROW('completed', 500)— é mais limpo que comparar duas colunas separadamente, e também funciona com IN de múltiplas colunas.
P: Os operadores de intervalo só funcionam em tipos Range integrados? R: Sim, operadores como
@> <@ &&atuam nos tipos Range integrados do PostgreSQL (int4range, numrange, daterange, etc.). Colunas comuns devem primeiro ser transformadas em um valor Range antes da comparação.
P: O operador de potência
^tem precedência maior que multiplicação/divisão? R: Sim,^se liga mais fortemente que* / %.2^3^2 = 2^(3^2) = 512(associativo à direita), não(2^3)^2 = 64.
P: Qual a diferença entre
IS DISTINCT FROMe<>? R: Com<>,NULL <> NULLretorna NULL (nem TRUE nem FALSE).IS DISTINCT FROMtrata NULL como um valor comparável:NULL IS DISTINCT FROM NULLretorna FALSE e1 IS DISTINCT FROM NULLretorna TRUE.
📖 Resumo
- Operadores aritméticos: cuidado com truncamento de divisão inteira e precisão; use
NUMERIC(p,s)para valores monetários - Operadores de comparação resultam em NULL quando comparados com NULL; use
IS DISTINCT FROMpara comparação segura - Na lógica de três valores,
NULL AND TRUE = NULLeNULL OR FALSE = NULL - Concatenação de strings
||retorna NULL com NULL; trate comCOALESCE ::é sintaxe de conversão específica do PG;CAST()é o padrão SQL- Construtores de linha
ROW()suportam comparação de múltiplas colunas e IN de múltiplas colunas - Operadores de intervalo
@> <@ &&fornecem verificações eficientes de contenção e sobreposição de Range
📝 Exercícios
-
⭐ Escreva uma consulta que calcule
unit_price * quantitypara cada linha emorder_items, converta o resultado paraNUMERIC(10,2)e ordene pelo valor da linha em ordem decrescente. -
⭐⭐ Escreva uma consulta que use
daterangee o operador@>para encontrar pedidos cujaorder_dateesteja no primeiro trimestre de 2025 (1 de janeiro – 31 de março) e use||para construir uma string de resumo do pedido (formato:"Order #id - customer_name - $amount"), tratando nomes de cliente NULL. -
⭐⭐⭐ Escreva uma consulta que encontre pares de produtos na mesma categoria cujos intervalos de preço se sobreponham (construa o intervalo com
numrange(unit_price, unit_price * 1.2)e verifique a sobreposição com&&) e mostre se a comparação com construtor de linhaROW(a.price, a.category) = ROW(b.price, b.category)é igual para cada par.