A cláusula WHERE
O segurança na porta
Imagine uma casa noturna com mil pessoas dentro. Você não quer falar com todo mundo: quer encontrar seu amigo que está usando uma jaqueta vermelha. Ao invés de olhar cada rosto, você diz ao segurança: "Só deixe passar quem estiver de jaqueta vermelha." O segurança filtra a multidão, e só as pessoas correspondentes chegam até você.
Esse segurança é a cláusula WHERE. No curso anterior, você aprendeu SELECT e FROM: como escolher quais colunas exibir e de qual tabela ler. Mas sem WHERE, toda query retorna todas as linhas da tabela. É como pedir ao segurança para deixar todo mundo passar.
WHERE é a cláusula mais importante para aplicações reais. Quase nenhuma query em produção roda sem ela.
Como o WHERE funciona no pipeline da query
Lembre-se do pipeline de query do curso anterior: Parser → Planner → Executor. A cláusula WHERE afeta todos os três estágios:
SELECT name, email FROM users WHERE active = true;
flowchart TB
A["Parser<br/>converte WHERE active = true em um nó de filtro na AST"] --> B["Planner<br/>verifica se existe um index em active.<br/>Sim -> Index Scan (rápido). Não -> Sequential Scan (lê cada linha)"]
B --> C["Executor<br/>avalia a condição para cada linha.<br/>Só passam linhas onde active = true; linhas rejeitadas são descartadas"]
O executor aplica sua condição WHERE como um predicado: um teste verdadeiro/falso avaliado contra cada linha candidata. Se o predicado retorna true, a linha é incluída no result set. Se false ou NULL, a linha é excluída.
Esta é uma ideia importante: WHERE não busca dados. Ele elimina dados. Toda linha começa como candidata; WHERE decide quais candidatas sobrevivem.
Operadores de comparação
SQL oferece seis operadores de comparação que funcionam com números, strings, datas e a maioria dos outros tipos de dados:
| Operador | Significado | Exemplo |
|---|---|---|
= |
Igual a | WHERE level = 0 |
!= ou <> |
Diferente de | WHERE status != 'completed' |
< |
Menor que | WHERE position < 5 |
> |
Maior que | WHERE position > 3 |
<= |
Menor ou igual | WHERE level <= 1 |
>= |
Maior ou igual | WHERE level >= 2 |
Perceba que SQL usa = para comparação, não ==. Essa é uma armadilha comum para programadores vindos de Ruby, JavaScript ou Python, onde = significa atribuição e == significa comparação. Em SQL, = sempre significa comparação: não existe operador de atribuição em uma query SELECT.
Filtrando por números
Vamos usar as tabelas deste próprio app. A tabela courses tem uma coluna level armazenada como inteiro (0 = intern, 1 = junior, 2 = mid, 3 = senior). Os títulos ficam numa coluna JSON, title, com uma chave por idioma, então title->>'en' lê o título em inglês como texto:
-- Encontrar todos os cursos nível junior
SELECT title->>'en' AS title, level
FROM courses
WHERE level = 1
ORDER BY position;
Resultado:
title | level
-------------------------+-------
Raw SQL: select & from | 1
SQL: where & operators | 1
...
(101 rows)
O equivalente em ActiveRecord:
# ActiveRecord
Catalog::Course.where(level: :junior)
# SQL gerado pelo Rails
SELECT "courses".* FROM "courses" WHERE "courses"."level" = 1
Perceba que o Rails traduz o símbolo :junior para o inteiro 1 porque Catalog::Course usa enum :level. O ActiveRecord nunca cola input cru no SQL: com prepared statements ligados, o valor 1 vai separado do texto SQL; com eles desligados (como no log de desenvolvimento deste app), o Rails coloca o valor entre aspas e escapado. Nos dois casos, uma condição em hash está protegida contra SQL injection.
Filtrando por strings
Comparações de strings usam aspas simples em SQL. Aspas duplas são reservadas para identificadores (nomes de tabelas e colunas):
-- Encontrar um usuário por email
SELECT id, name, email
FROM users
WHERE email = '[email protected]';
Em ActiveRecord:
Identity::User.where(email: '[email protected]')
# SQL: SELECT "users".* FROM "users" WHERE "users"."email" = '[email protected]'
Comparações de strings no PostgreSQL são case-sensitive por padrão. 'Alice' e 'alice' são valores diferentes. Se você precisar de comparação case-insensitive, vai aprender sobre ILIKE na Aula 3.
Erro comum: Usar aspas duplas em volta de valores de string.
-- ERRADO: aspas duplas significam nome de coluna
WHERE email = "[email protected]"
-- PostgreSQL lê como: WHERE email = (coluna chamada [email protected])
-- Erro: column "[email protected]" does not exist
-- CORRETO: aspas simples para valores de string
WHERE email = '[email protected]'
Filtrando com operadores de desigualdade
Operadores de desigualdade são essenciais para queries de faixa: encontrar registros acima, abaixo ou fora de um limite:
-- Encontrar aulas após a posição 3 em um curso
SELECT title->>'en' AS title, position
FROM lessons
WHERE course_id = 5
AND position > 3
ORDER BY position;
Resultado:
title | position
---------------------------+----------
IN, BETWEEN & Ranges | 4
NULL Operators | 5
Dates & Times | 6
Indexes & Sargability | 7
(4 rows)
Equivalente em ActiveRecord:
Catalog::Lesson.where(course_id: 5).where('position > ?', 3).order(:position)
# SQL: SELECT "lessons".* FROM "lessons"
# WHERE "lessons"."course_id" = 5 AND (position > 3)
# ORDER BY "lessons"."position" ASC
Perceba o placeholder ?: esta é uma query parametrizada. O ActiveRecord substitui ? pelo valor 3, devidamente escapado. Nunca interpole valores diretamente em strings SQL: isso cria vulnerabilidades de SQL injection.
A cláusula WHERE e NULL
Uma regra crítica: você não pode usar = ou != para comparar com NULL. Esta query retorna zero linhas, mesmo se alguns usuários não tiverem nome:
-- ERRADO: retorna nenhuma linha
SELECT * FROM users WHERE name = NULL;
-- CORRETO: use IS NULL
SELECT * FROM users WHERE name IS NULL;
Isso acontece porque SQL usa lógica de três valores: toda comparação com NULL retorna NULL (não true, não false). E WHERE só mantém linhas onde a condição é true. Vamos explorar operadores NULL em profundidade na Aula 5.
Erros comuns
Erro: Escrever WHERE active = true para colunas booleanas achando que é a única forma.
A nuance: No PostgreSQL, WHERE active, WHERE active = true e WHERE active IS TRUE todas funcionam, mas se comportam diferente com valores NULL. WHERE active e WHERE active = true ambas excluem NULLs. WHERE active IS TRUE também exclui NULLs. Mas WHERE active IS NOT FALSE inclui NULLs. Seja explícito sobre sua intenção.
Erro: Esquecer que != não captura valores NULL.
Por que está errado: WHERE status != 'completed' NÃO retorna linhas onde status é NULL, porque NULL != 'completed' resulta em NULL, não true. Se você quer essas linhas também, use: WHERE status != 'completed' OR status IS NULL.
Resumo
| Conceito | Ponto Chave |
|---|---|
| Cláusula WHERE | Filtra linhas avaliando um predicado: só linhas que retornam true são incluídas |
| Operadores de comparação | =, !=/<>, <, >, <=, >=: funcionam com números, strings e datas |
| Valores de string | Sempre use aspas simples ('texto'), nunca aspas duplas (essas são para identificadores) |
| Comparações com NULL | = NULL nunca funciona: use IS NULL ou IS NOT NULL |
ActiveRecord .where |
Gera cláusulas WHERE parametrizadas: previne SQL injection automaticamente |
| Impacto no query planner | Condições WHERE determinam se o planner usa Index Scan ou Sequential Scan |
| Queries parametrizadas | Use placeholders ? no ActiveRecord: nunca interpole input do usuário em strings SQL |
Continue com a aula 2
AND, OR e NOT
Combinar condições com AND, OR e NOT, por que AND vem antes de OR, e os equivalentes no ActiveRecord.
Com uma conta grátis você também tem os quizzes, seu progresso e suas anotações, e a discussão.