PostgreSQL: Operadores e Expressões no PostgreSQL

Última atualização: 2026-08-26

1. O Que Você Vai Aprender


2. A História

Charlie é um analista financeiro em uma plataforma SaaS, responsável por:

  1. Calcular o valor com impostos de cada pedido (total * 1.08)
  2. Verificar se um pedido está dentro de um determinado intervalo de datas
  3. Converter datas em formato string para o tipo DATE para comparação
  4. 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

SQL
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:

TEXT 📖 Somente leitura
 id | name     | value 
----+----------+-------
  1 | example  | 42
(1 row)

▶ Exemplo: Divisão Inteira vs Divisão Numérica

SQL
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:

TEXT 📖 Somente leitura
 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

SQL
SELECT
  2 + 3 * 4 AS no_parens,
  (2 + 3) * 4 AS with_parens;

Output:

TEXT 📖 Somente leitura
 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

SQL
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:

TEXT 📖 Somente leitura
 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

SQL
SELECT product_name, unit_price
FROM products
WHERE unit_price >= 100
  AND unit_price < 500
  AND category = 'Electronics';

Output:

TEXT 📖 Somente leitura
 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

SQL
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:

TEXT 📖 Somente leitura
 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

SQL
SELECT order_id, old_status, new_status
FROM order_status_log
WHERE old_status IS DISTINCT FROM new_status;

Output:

TEXT 📖 Somente leitura
 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

SQL
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:

TEXT 📖 Somente leitura
 id | name     | value 
----+----------+-------
  1 | example  | 42
(1 row)

▶ Exemplo: Comportamento de NULL no WHERE

SQL
-- 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:

TEXT 📖 Somente leitura
 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

SQL
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:

TEXT 📖 Somente leitura
  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

SQL
SELECT
  product_name || COALESCE(' - ' || description, '') AS product_info
FROM products;

Output:

TEXT 📖 Somente leitura
 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

SQL
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:

TEXT 📖 Somente leitura
 id | name     | value 
----+----------+-------
  1 | example  | 42
(1 row)

▶ Exemplo: Conversão de Tipo Numérico

SQL
SELECT
  total_amount::NUMERIC(12,2) AS rounded,
  total_amount::INTEGER AS truncated,
  total_amount::TEXT AS as_text
FROM orders;

Output:

TEXT 📖 Somente leitura
 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

SQL
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:

TEXT 📖 Somente leitura
 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

SQL
SELECT order_id, order_status, total_amount
FROM orders
WHERE ROW(order_status, total_amount) = ROW('completed', 500);

Output:

TEXT 📖 Somente leitura
 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

SQL
SELECT order_id, customer_id, order_status
FROM orders
WHERE (order_status, customer_id) IN (
  ('completed', 1001),
  ('completed', 1002),
  ('pending', 1003)
);

Output:

TEXT 📖 Somente leitura
 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

SQL
SELECT product_name, unit_price, category
FROM products
WHERE unit_price > ALL (
  SELECT AVG(unit_price) FROM products GROUP BY category
);

Output:

TEXT 📖 Somente leitura
  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

SQL
SELECT daterange('2025-01-01'::DATE, '2025-06-30'::DATE) AS h1_range;

Output:

TEXT 📖 Somente leitura
 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

SQL
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:

TEXT 📖 Somente leitura
 id | name     | value 
----+----------+-------
  1 | example  | 42
(1 row)

▶ Exemplo: Verificação de Contenção de Intervalo de Preço

SQL
SELECT product_name, unit_price
FROM products
WHERE numrange(100, 500) @> unit_price::NUMERIC;

Output:

TEXT 📖 Somente leitura
 id | name     | value 
----+----------+-------
  1 | example  | 42
(1 row)

10. Fluxograma: Decisão de Conversão de Tipo

100%
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.

SQL
-- 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, :: ou CAST()? R: :: é mais conciso e é sintaxe específica do PostgreSQL; CAST() é a forma padrão SQL. Para projetos puramente PostgreSQL, :: é recomendado; use CAST() 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 FROM e <>? R: Com <>, NULL <> NULL retorna NULL (nem TRUE nem FALSE). IS DISTINCT FROM trata NULL como um valor comparável: NULL IS DISTINCT FROM NULL retorna FALSE e 1 IS DISTINCT FROM NULL retorna TRUE.


📖 Resumo


📝 Exercícios

  1. ⭐ Escreva uma consulta que calcule unit_price * quantity para cada linha em order_items, converta o resultado para NUMERIC(10,2) e ordene pelo valor da linha em ordem decrescente.

  2. ⭐⭐ Escreva uma consulta que use daterange e o operador @> para encontrar pedidos cuja order_date esteja 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.

  3. ⭐⭐⭐ 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 linha ROW(a.price, a.category) = ROW(b.price, b.category) é igual para cada par.

Web-Tutorial.com

Equipe Técnica Web-Tutorial

Uma plataforma de tutoriais mantida por diversos desenvolvedores. Cada tutorial é escrito e revisado por profissionais da área correspondente. Trabalhamos para manter nosso conteúdo preciso e confiável — se encontrar algum problema, avise-nos.

100%