Referência

Referência SQL

Comandos, sintaxe e notas breves — do SELECT às funções de janela, índices e transações. Abra um artigo para se aprofundar.

Básico: SELECT e filtragem9

SELECT … FROM
Artigo
SELECT col1, col2 FROM table_name;

Escolha colunas de uma tabela.

Veja também:····
WHERE
Artigo
SELECT * FROM t
WHERE col = 5 AND status = 'active';

Filtra linhas: =, <>, <, >, AND, OR, IN, BETWEEN, LIKE.

Veja também:····
ORDER BY
Artigo
SELECT * FROM t ORDER BY created_at DESC;

Ordena o resultado. ASC crescente (padrão), DESC decrescente.

Veja também:····
LIMIT
Artigo
SELECT * FROM t ORDER BY id LIMIT 10;

Limita o número de linhas retornadas.

Veja também:····
BETWEEN
WHERE price BETWEEN 100 AND 500
WHERE created_at BETWEEN '2024-01-01' AND '2024-01-31'

Intervalo inclusivo em ambas as extremidades: x >= inferior E x <= superior. Com timestamps, um limite direito às 00:00 corta o último dia inteiro.

LIKE
WHERE name LIKE 'Ivan%'   -- % any tail
WHERE code LIKE '_X-%'    -- _ exactly one char

Busca por padrão: % — qualquer número de caracteres (inclusive zero), _ — exatamente um. Diferencia maiúsculas no PostgreSQL (ver ILIKE).

IN (value list)
WHERE status IN ('paid', 'shipped', 'done')

Mais curto que uma cadeia de OR: o valor está na lista. NOT IN com um NULL na lista silenciosamente não retorna nada.

OFFSET
SELECT * FROM t ORDER BY id
LIMIT 10 OFFSET 20;   -- page 3 by 10

Pula N linhas — paginação junto com LIMIT. Sem ORDER BY as páginas são instáveis. OFFSETs grandes são lentos — a paginação por cursor (WHERE id > last) é mais rápida.

NULLS FIRST / LASTPostgreSQL
SELECT * FROM t ORDER BY score DESC NULLS LAST;

Onde os NULLs são ordenados. Por padrão o PostgreSQL os coloca por último com ASC e primeiro com DESC — NULLS LAST/FIRST torna isso explícito.

Junção de tabelas (JOIN)9

INNER JOIN
Artigo
SELECT * FROM a JOIN b ON a.id = b.a_id;

Apenas as linhas que têm correspondência em ambas as tabelas.

Veja também:·····
LEFT JOIN
Artigo
SELECT * FROM a LEFT JOIN b ON a.id = b.a_id;

Todas as linhas da tabela à esquerda; NULL à direita se não houver correspondência.

Veja também:·····
RIGHT JOIN
SELECT * FROM a RIGHT JOIN b ON a.id = b.a_id;

Todas as linhas da tabela à direita; NULL à esquerda se não houver correspondência. O espelho do LEFT JOIN — na prática costuma-se trocar as tabelas e escrever LEFT.

USING / NATURAL JOIN
SELECT * FROM orders JOIN users USING (user_id);
-- NATURAL JOIN joins by ALL same-named columns — avoid

USING (col) é a forma curta de ON a.col = b.col, com uma única coluna na saída. NATURAL JOIN une por TODAS as colunas homônimas — frágil, melhor evitar.

Aliases
Artigo
SELECT u.name
FROM users u
JOIN orders o ON o.user_id = u.id;

Nomes curtos para as tabelas — obrigatórios quando as colunas têm o mesmo nome.

Veja também:····
CROSS JOIN
Artigo
SELECT * FROM sizes CROSS JOIN colors;

Produto cartesiano — cada linha à esquerda com cada linha à direita. Para gerar todas as combinações.

Veja também:·····
FULL OUTER JOIN
Artigo
SELECT * FROM a FULL OUTER JOIN b ON a.id = b.a_id;

Todas as linhas de ambas as tabelas; NULL onde não há correspondência. O MySQL não tem FULL JOIN — emule com LEFT ∪ RIGHT.

Veja também:·····
Self-join
Artigo
SELECT e.name, m.name AS manager
FROM employees e
LEFT JOIN employees m ON e.manager_id = m.id;

Uma tabela unida a si mesma via alias — para hierarquias como «funcionário → gestor».

Veja também:·····
Anti-join (LEFT JOIN + IS NULL)
Artigo
SELECT u.*
FROM users u
LEFT JOIN orders o ON o.user_id = u.id
WHERE o.id IS NULL;

Linhas à esquerda SEM correspondência à direita — «usuários sem pedidos». Alternativa a NOT EXISTS.

Veja também:·····

Agregação e agrupamento31

COUNT
Artigo
SELECT COUNT(*), COUNT(email) FROM users;

Conta linhas de um grupo. COUNT(*) — todas as linhas, COUNT(col) — apenas não-NULL, COUNT(DISTINCT col) — valores únicos.

Veja também:···
SELECT SUM(amount) FROM orders;

Soma de valores numéricos de um grupo. NULLs são ignorados. Retorna NULL (não 0) para grupo vazio.

SELECT AVG(price) FROM products;

Média aritmética. NULLs ficam fora do divisor. Converta uma coluna inteira para numeric ou a parte fracionária é truncada.

SELECT MIN(created_at) FROM orders;

Menor valor de um grupo. Funciona com números, datas e textos. NULLs são ignorados.

SELECT MAX(created_at) FROM orders;

Maior valor de um grupo. Funciona com números, datas e textos. NULLs são ignorados.

GROUP BY
Artigo
SELECT user_id, COUNT(*) FROM orders GROUP BY user_id;

Agrupa linhas. Toda coluna do SELECT que não seja de agregação deve estar no GROUP BY.

Veja também:···
HAVING
Artigo
SELECT user_id, COUNT(*)
FROM orders
GROUP BY user_id
HAVING COUNT(*) > 5;

Filtra sobre agregados — como WHERE, mas depois do GROUP BY.

Veja também:···
DISTINCT
Artigo
SELECT DISTINCT country FROM users;

Remove as linhas duplicadas do resultado.

Veja também:····
COUNT(*) FILTERPostgreSQL
Artigo
COUNT(*) FILTER (WHERE type = 'view') AS views

Agregado condicional: COUNT/SUM apenas sobre as linhas que satisfazem o WHERE. Substitui três consultas separadas por uma só.

Veja também:·····
STRING_AGGPostgreSQL
Artigo
STRING_AGG(name, ', ' ORDER BY created_at)

Concatena valores em uma única cadeia com um separador. O ORDER BY interno garante a ordem.

Veja também:··
ARRAY_AGGPostgreSQL
Artigo
ARRAY_AGG(amount ORDER BY created_at)

Reúne valores em um array. Útil quando uma linha de auditoria precisa de todo o histórico em uma célula.

Veja também:··
GROUPING SETSPostgreSQL
Artigo
GROUP BY GROUPING SETS ((kind), (user_id), ())

Vários níveis de agrupamento em uma consulta — linha a linha: por kind, por user_id e um total geral.

Veja também:···
ROLLUPPostgreSQL
Artigo
GROUP BY ROLLUP (DATE_TRUNC('month', ts))

O mesmo nível de agrupamento mais uma linha de total geral (uma única linha NULL no final).

Veja também:···
PERCENTILE_CONTPostgreSQL
Artigo
PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY amount)

Mediana / quantil. Mais resistente a valores atípicos do que AVG.

Veja também:···
UNNESTPostgreSQL
Artigo
SELECT tag FROM articles, UNNEST(tags) tag

Expande um array em linhas — uma linha por elemento do array.

Veja também:··
DISTINCT ONPostgreSQL
SELECT DISTINCT ON (user_id) *
FROM orders
ORDER BY user_id, created_at DESC;

«A última linha por chave» em uma única instrução: a primeira linha de cada grupo pelo ORDER BY. O idioma do PostgreSQL que substitui ROW_NUMBER() + rn = 1.

COUNT(DISTINCT)
Artigo
SELECT COUNT(DISTINCT user_id) FROM events;

Conta os valores distintos. Custoso em tabelas grandes — use APPROX_COUNT_DISTINCT / HLL quando uma estimativa basta.

Veja também:···
BOOL_AND / BOOL_OR
Artigo
SELECT BOOL_AND(active), BOOL_OR(is_admin) FROM users;

Agregados booleanos: BOOL_AND é true se TODAS as linhas forem true; BOOL_OR se ao menos uma for. NULLs são ignorados.

Veja também:·
EVERY
Artigo
SELECT dept, EVERY(salary > 0) FROM emp GROUP BY dept;

O sinônimo padrão do SQL para BOOL_AND — true quando a condição vale para todas as linhas do grupo.

Veja também:·
STDDEV
Artigo
SELECT STDDEV_SAMP(amount), STDDEV_POP(amount) FROM orders;

Desvio padrão: _SAMP para uma amostra (divisor n-1), _POP para toda a população (divisor n). STDDEV sozinho equivale a STDDEV_SAMP.

Veja também:···
VARIANCE
Artigo
SELECT VAR_SAMP(amount), VAR_POP(amount) FROM orders;

Variância — o quadrado do desvio padrão. _SAMP para amostra, _POP para população. VARIANCE sozinho equivale a VAR_SAMP.

Veja também:···
MODE() WITHIN GROUPPostgreSQL
Artigo
SELECT MODE() WITHIN GROUP (ORDER BY status) FROM tickets;

A moda — o valor mais frequente do grupo. Em caso de empate vence o primeiro pelo ORDER BY.

Veja também:···
PERCENTILE_DISC
Artigo
SELECT PERCENTILE_DISC(0.5) WITHIN GROUP (ORDER BY amount) FROM orders;

Percentil discreto — retorna um valor que realmente existe nos dados, diferente de PERCENTILE_CONT que interpola.

Veja também:···
BIT_AND / BIT_OR
Artigo
SELECT BIT_OR(flags), BIT_AND(flags) FROM permissions;

Agregados bit a bit sobre uma coluna integer: BIT_OR reúne todos os bits ativos, BIT_AND mantém os comuns a todas as linhas. Para máscaras de flags.

Veja também:·
JSON_AGG / JSONB_AGGPostgreSQL
Artigo
SELECT JSONB_AGG(t ORDER BY t.id) FROM tasks t;

Agrupa linhas em um array JSON — útil para retornar dados aninhados em uma única consulta. JSONB_AGG armazena como jsonb (mais rápido, sem chaves duplicadas).

Veja também:····
JSONB_OBJECT_AGGPostgreSQL
Artigo
SELECT JSONB_OBJECT_AGG(key, value) FROM settings;

Dobra pares chave-valor em um único objeto JSON. Ideal para transformar uma tabela de configurações em um mapa.

Veja também:····
CORR
Artigo
SELECT CORR(price, sales) FROM products;

Coeficiente de correlação de Pearson entre duas colunas: de -1 a 1. Uma medida de associação linear.

Veja também:···
REGR_SLOPE / REGR_INTERCEPT
Artigo
SELECT REGR_SLOPE(y, x), REGR_INTERCEPT(y, x) FROM points;

Inclinação e intercepto da reta de regressão de y sobre x — uma tendência em um único agregado, sem pacote estatístico externo.

Veja também:···
REGR_R2
Artigo
SELECT REGR_R2(y, x) FROM points;

O coeficiente de determinação R² da regressão de y sobre x: 0..1, quão bem a reta se ajusta aos dados.

Veja também:···
MAX(...) FILTER (pivot)PostgreSQL
Artigo
SELECT user_id,
  MAX(amount) FILTER (WHERE kind = 'deposit')  AS deposit,
  MAX(amount) FILTER (WHERE kind = 'withdraw') AS withdraw
FROM tx GROUP BY user_id;

Transforma linhas em colunas (pivot): MAX/SUM com FILTER por categoria. Substitui uma pilha de agregados CASE escritos à mão.

Veja também:···
CUBE
Artigo
SELECT region, product, SUM(amount)
FROM sales
GROUP BY CUBE (region, product);

Todas as combinações de agrupamento de uma vez: por region, por product, por ambos e um total geral. Para relatórios de tabela cruzada.

Veja também:···

Subconsultas6

IN (subquery)
Artigo
SELECT * FROM users WHERE id IN (SELECT user_id FROM orders);

Verifica a pertinência a uma lista gerada por outra consulta.

Veja também:···
EXISTS
Artigo
SELECT * FROM users u
WHERE EXISTS (
  SELECT 1 FROM orders o WHERE o.user_id = u.id
);

Mantém a linha se a consulta interna encontrar ao menos uma linha correspondente.

Veja também:···
Scalar subquery
Artigo
SELECT
  name,
  (SELECT COUNT(*) FROM orders o WHERE o.user_id = u.id) AS cnt
FROM users u;

Uma subconsulta que retorna um único valor — pode ficar dentro do SELECT.

Veja também:···
Correlated subquery
SELECT * FROM orders o
WHERE amount > (
  SELECT AVG(amount) FROM orders i WHERE i.user_id = o.user_id
);

A subconsulta referencia a linha externa — logicamente executa por linha. É assim que funcionam os padrões EXISTS e «acima da média do próprio grupo».

Subquery in FROM
SELECT dept, MAX(cnt)
FROM (
  SELECT dept, user_id, COUNT(*) AS cnt
  FROM sales GROUP BY dept, user_id
) s
GROUP BY dept;

Tabela derivada: o resultado da subconsulta é usado como tabela. O clássico «agregado de um agregado». O alias após o parêntese é obrigatório.

ANY / ALL
WHERE price > ALL (SELECT price FROM basic_plans)
WHERE id = ANY (ARRAY[1, 2, 3])

Comparação contra um conjunto: > ALL — maior que todos, > ANY — maior que pelo menos um. = ANY(array) é o idioma do PostgreSQL que substitui IN para parâmetros de array.

Operações de conjunto (UNION/INTERSECT)3

UNION / UNION ALL
Artigo
SELECT id FROM a
UNION ALL
SELECT id FROM b;

Empilha os resultados de duas consultas (mesmo número de colunas, tipos compatíveis). UNION remove duplicatas, UNION ALL as mantém (e é mais rápido).

Veja também:·
INTERSECT
Artigo
SELECT user_id FROM purchases
INTERSECT
SELECT user_id FROM refunds;

Linhas presentes em AMBAS as consultas. No MySQL — a partir da 8.0.31.

Veja também:·
EXCEPT
Artigo
SELECT user_id FROM users
EXCEPT
SELECT user_id FROM banned;

Linhas da primeira consulta que NÃO estão na segunda. No Oracle é MINUS.

Veja também:·

Funções de janela12

ROW_NUMBER
Artigo
SELECT
  name,
  ROW_NUMBER() OVER (ORDER BY score DESC) AS rn
FROM players;

Número sequencial único para cada linha dentro da janela.

Veja também:····
RANK / DENSE_RANK
Artigo
RANK() OVER (PARTITION BY dept ORDER BY salary DESC)

Classificação com saltos (RANK) ou sem saltos (DENSE_RANK) quando há empates.

Veja também:··
PARTITION BY
Artigo
SUM(amount) OVER (PARTITION BY user_id ORDER BY created_at)

Divide a janela em grupos — o agregado é calculado por grupo.

Veja também:·
LAG / LEAD
Artigo
LAG(price, 1) OVER (ORDER BY date)

Valor da linha anterior (LAG) ou seguinte (LEAD) da janela.

Veja também:··
SUM() OVER (running total)
SELECT created_at, amount,
  SUM(amount) OVER (ORDER BY created_at) AS running_total
FROM payments;

Total acumulado: um agregado com ORDER BY dentro de OVER acumula do início da janela até a linha atual. A tarefa de função de janela mais comum em entrevistas.

NTILE
Artigo
NTILE(4) OVER (ORDER BY score DESC)

Divide as linhas em N grupos de tamanho igual em ordem. Com quantidades desiguais os grupos ficam 3-3-2-2 (os excedentes vão para os de número menor).

Veja também:·····
FIRST_VALUE / LAST_VALUE
Artigo
LAST_VALUE(score) OVER (
  PARTITION BY team_id ORDER BY score DESC
  ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
)

Primeiro / último valor da janela. Para LAST_VALUE é obrigatório ampliar o quadro com ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING — caso contrário, o quadro padrão corta a borda direita.

Veja também:··
PERCENT_RANK
Artigo
PERCENT_RANK() OVER (ORDER BY score DESC, player_id)

Classificação percentil de 0 a 1. Uma segunda coluna de ordenação torna o resultado determinístico quando há empates.

Veja também:··
NTH_VALUE
Artigo
NTH_VALUE(amount, 2) OVER (
  PARTITION BY customer_id ORDER BY amount DESC
  ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
)

O N-ésimo valor da janela. Também requer o quadro ampliado.

Veja também:··
Window frames
Artigo
AVG(x) OVER (ORDER BY d ROWS BETWEEN 6 PRECEDING AND CURRENT ROW)

Janelas deslizantes: «os últimos 7 dias inclusive», «média de 3 dias», etc.

Veja também:····
CUME_DIST
CUME_DIST() OVER (ORDER BY salary)

Fração de linhas com valor ≤ ao atual (0..1]. Parente do PERCENT_RANK, que conta as linhas ESTRITAMENTE menores.

WINDOW clause
SELECT
  ROW_NUMBER() OVER w,
  SUM(amount) OVER w
FROM t
WINDOW w AS (PARTITION BY user_id ORDER BY created_at);

Declare a janela uma vez e reutilize em várias funções — sem copiar e colar PARTITION BY/ORDER BY.

CTEs e recursão (WITH)5

WITH … AS
Artigo
WITH active AS (
  SELECT * FROM users WHERE status = 'active'
)
SELECT * FROM active WHERE country = 'RU';

Um resultado temporário nomeado — divide uma consulta grande em etapas.

Veja também:··
Multiple CTEs
Artigo
WITH a AS (...), b AS (...)
SELECT * FROM a JOIN b ON ...;

Vários CTEs separados por vírgulas, lidos de cima para baixo.

Veja também:··
WITH RECURSIVE
Artigo
WITH RECURSIVE chain AS (
  SELECT id, manager_id FROM emp WHERE manager_id IS NULL
  UNION ALL
  SELECT e.id, e.manager_id FROM emp e JOIN chain c ON e.manager_id = c.id
)
SELECT * FROM chain;

Percorre uma hierarquia: consulta de ancoragem → UNION ALL → passo recursivo. Ideal para organogramas, grafos e cadeias.

Veja também:··
LATERAL
Artigo
FROM customers c
LEFT JOIN LATERAL (
  SELECT * FROM orders WHERE customer_id = c.id
  ORDER BY amount DESC LIMIT 2
) l ON true

A subconsulta enxerga as colunas da linha externa. Ideal para «top-N por X». LEFT JOIN LATERAL … ON true mantém as linhas externas sem correspondência; a forma com vírgula as descarta silenciosamente.

Veja também:·····
generate_seriesPostgreSQL
Artigo
generate_series('2024-01-01'::date, '2024-01-15'::date, '1 day')

Gera um calendário / eixo. Inclui ambas as extremidades. Truque padrão para «preencher com zero os dias faltantes».

Veja também:··

Alteração de dados (DML)11

INSERT
Artigo
INSERT INTO t (col1, col2) VALUES (1, 'a'), (2, 'b');

Adiciona linhas a uma tabela.

Veja também:··
UPDATE
Artigo
UPDATE t SET col = 'x' WHERE id = 5;

Modifica linhas existentes. Sempre inclua WHERE — caso contrário, TODAS as linhas são atualizadas.

Veja também:··
DELETE
Artigo
DELETE FROM t WHERE id = 5;

Exclui linhas. Sempre inclua WHERE.

Veja também:··
TRUNCATE
TRUNCATE TABLE logs;

Esvazia a tabela inteira instantaneamente. Mais rápido que DELETE (sem trabalho por linha), mas sem WHERE, e recusa quando há chaves estrangeiras apontando para a tabela, salvo com CASCADE.

ON CONFLICT DO NOTHINGPostgreSQL
Artigo
INSERT INTO t (id) VALUES (1)
ON CONFLICT (id) DO NOTHING;

Inserção idempotente — ao executar novamente, ignora silenciosamente as linhas que já existem.

Veja também:··
ON CONFLICT DO UPDATEPostgreSQL
Artigo
INSERT INTO t (id, n) VALUES (1, 1)
ON CONFLICT (id) DO UPDATE
  SET n = t.n + EXCLUDED.n;

UPSERT: inserir ou atualizar. EXCLUDED.col é o valor que tentamos inserir.

Veja também:··
MERGEPostgreSQL
Artigo
MERGE INTO t USING src ON t.id = src.id
WHEN MATCHED THEN UPDATE SET ...
WHEN NOT MATCHED THEN INSERT ...;

Postgres 15+ — alternativa ao UPSERT com ramos MATCHED / NOT MATCHED e condições em cada um.

Veja também:··
RETURNINGPostgreSQL
Artigo
INSERT INTO t (name) VALUES ('a')
RETURNING id, name;

Retorna as linhas recém-inseridas / atualizadas / excluídas na mesma instrução — sem uma segunda ida ao servidor.

Veja também:·····
DELETE … USINGPostgreSQL
Artigo
DELETE FROM orders o
USING customers c
WHERE o.customer_id = c.id AND c.country = 'US';

DELETE no estilo JOIN — filtra por outra tabela sem uma subconsulta.

Veja também:··
UPDATE … FROMPostgreSQL
Artigo
UPDATE customers c SET total = s.total
FROM (SELECT customer_id, SUM(amount) AS total FROM orders GROUP BY customer_id) s
WHERE c.id = s.customer_id;

UPDATE em massa impulsionado por uma subconsulta de agregação.

Veja também:··
CTE + DELETE … RETURNINGPostgreSQL
Artigo
WITH moved AS (
  DELETE FROM orders WHERE old RETURNING *
)
INSERT INTO archive SELECT * FROM moved;

Arquivamento atômico: move as linhas em uma única instrução, sem janela de concorrência entre DELETE e INSERT.

Veja também:··

Esquema (DDL)15

CREATE TABLE
Artigo
CREATE TABLE t (
  id INTEGER PRIMARY KEY,
  name VARCHAR(100) NOT NULL
);

Cria uma tabela com colunas tipadas.

Veja também:···
ALTER TABLE
Artigo
ALTER TABLE t ADD COLUMN created_at TIMESTAMP;
ALTER TABLE t DROP COLUMN legacy_code;
ALTER TABLE t RENAME COLUMN name TO full_name;
ALTER TABLE t ALTER COLUMN price TYPE NUMERIC(10,2);

Modifica uma tabela existente: adiciona/remove/renomeia uma coluna, altera seu tipo.

Veja também:···
PRIMARY KEY / UNIQUE / NOT NULL / DEFAULT
CREATE TABLE users (
  id BIGSERIAL PRIMARY KEY,
  email TEXT UNIQUE NOT NULL,
  status TEXT NOT NULL DEFAULT 'active'
);

As restrições básicas: PRIMARY KEY — identificador único da linha (um por tabela), UNIQUE — sem duplicatas, NOT NULL — valor obrigatório, DEFAULT — valor padrão.

DROP TABLE
DROP TABLE IF EXISTS temp_import;
DROP TABLE orders CASCADE;  -- also drops dependent FKs/views

Remove a tabela com todos os dados. IF EXISTS — não falha se não existir; CASCADE — remove também os objetos dependentes. Não há como desfazer.

SERIAL / IDENTITYPostgreSQL
CREATE TABLE t (
  id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY
);
-- legacy spelling: id BIGSERIAL PRIMARY KEY

Id autoincremental. A forma moderna é GENERATED AS IDENTITY (padrão SQL); SERIAL/BIGSERIAL é a grafia antiga baseada em sequence. No MySQL é AUTO_INCREMENT.

CHECK
Artigo
ALTER TABLE products
ADD CONSTRAINT price_positive CHECK (price > 0);

Rejeita valores inválidos no nível do banco de dados, não no código.

Veja também:···
FK ON DELETE
Artigo
FOREIGN KEY (post_id) REFERENCES posts(id)
  ON DELETE CASCADE   -- or SET NULL / RESTRICT

O que fazer com a linha filha quando a linha pai é excluída: propagar em cascata, definir a chave estrangeira como NULL ou bloquear.

Veja também:···
NOT VALID + VALIDATEPostgreSQL
Artigo
ALTER TABLE t
  ADD CONSTRAINT fk REFERENCES p(id) NOT VALID;
ALTER TABLE t VALIDATE CONSTRAINT fk;

Adiciona uma chave estrangeira a uma tabela grande em produção sem um bloqueio pesado: NOT VALID é instantâneo, VALIDATE não bloqueia os escritores.

Veja também:···
GENERATED column
Artigo
total NUMERIC GENERATED ALWAYS AS (price * (1 + tax)) STORED

O valor da coluna é calculado automaticamente — a fórmula fica em um único lugar.

Veja também:···
Partial UNIQUEPostgreSQL
Artigo
CREATE UNIQUE INDEX u ON users (email)
WHERE deleted_at IS NULL;

Unicidade apenas sobre as linhas ativas — para o soft-delete, de modo que os usuários possam se registrar de novo após a exclusão.

Veja também:···
Range partitioningPostgreSQL
Artigo
CREATE TABLE logs (...) PARTITION BY RANGE (ts);
CREATE TABLE logs_2024 PARTITION OF logs
  FOR VALUES FROM ('2024-01-01') TO ('2025-01-01');

Divide uma tabela grande por intervalos. As partições antigas são removidas em milissegundos.

Veja também:···
TRIGGERPostgreSQL
Artigo
CREATE TRIGGER touch BEFORE UPDATE ON notes
FOR EACH ROW EXECUTE FUNCTION touch_updated_at();

Lógica automática no nível do banco de dados — por exemplo, atualizar updated_at sem tocar no código da aplicação.

Veja também:···
MATERIALIZED VIEWPostgreSQL
Artigo
CREATE MATERIALIZED VIEW v AS SELECT ...;
REFRESH MATERIALIZED VIEW v;

Resultado em cache de uma consulta pesada. É atualizado de forma agendada com REFRESH.

Veja também:···
CREATE VIEW
CREATE VIEW active_users AS
SELECT * FROM users WHERE status = 'active';

Uma consulta salva atrás de um nome de tabela: nenhum dado é copiado, cada SELECT na view executa a consulta de novo. Compare com MATERIALIZED VIEW, onde o resultado fica em cache.

CREATE TEMPORARY TABLE
CREATE TEMPORARY TABLE staging AS
SELECT * FROM imports WHERE batch_id = 42;

Uma tabela com escopo de sessão: some ao desconectar e é invisível para outras conexões. O burro de carga do ETL e de cálculos pontuais.

Strings e datas9

LOWER / UPPER / LENGTH
Artigo
LOWER(name), UPPER(code), LENGTH(text)

Minúsculas, maiúsculas, comprimento da cadeia de caracteres.

CONCAT
Artigo
CONCAT(first_name, ' ', last_name)

Concatena cadeias de caracteres. O PostgreSQL também aceita o operador || (no MySQL, || é o OU lógico por padrão, não a concatenação).

Veja também:··
EXTRACT
Artigo
EXTRACT(YEAR FROM created_at), EXTRACT(MONTH FROM created_at)

Extrai uma parte de uma data — ano, mês, dia.

Veja também:···
DATE_TRUNCPostgreSQL
Artigo
DATE_TRUNC('month', created_at)

Trunca um timestamp para baixo até um período (dia/semana/mês). A ferramenta básica para agrupar por tempo.

Veja também:···
NOW / CURRENT_DATE + INTERVAL
Artigo
WHERE created_at >= NOW() - INTERVAL '7 days'

Instante atual (NOW()) / hoje (CURRENT_DATE) e aritmética com intervalos — «nos últimos 7 dias».

Veja também:·
CAST / ::
Artigo
CAST(price AS INTEGER)   -- or price::int

Converte um valor para outro tipo. CAST(x AS type) é padrão; x::type é a forma curta do PostgreSQL.

Veja também:·
TRIM / SUBSTRING / REPLACE
Artigo
TRIM(name), SUBSTRING(code FROM 1 FOR 3), REPLACE(phone, '-', '')

Remove espaços, extrai uma substring, substitui um fragmento. Limpeza de strings do dia a dia.

Veja também:·
SPLIT_PARTPostgreSQL
Artigo
SPLIT_PART(email, '@', 2)   -- domain from an e-mail

Divide uma string por um separador e pega a N-ésima parte. No MySQL — SUBSTRING_INDEX.

Veja também:···
ILIKEPostgreSQL
Artigo
WHERE name ILIKE '%ivan%'

LIKE sem distinção de maiúsculas (PostgreSQL). No MySQL, o LIKE comum já ignora maiúsculas com a colação padrão.

Veja também:···

Funções de string17

LEFT / RIGHT
Artigo
LEFT(code, 3), RIGHT(phone, 4)

Pega os primeiros N caracteres (LEFT) ou os últimos N (RIGHT) de uma string.

Veja também:···
CONCAT_WS
CONCAT_WS(', ', city, street, house)

Une strings com um separador, pulando NULLs — um endereço sem «vírgulas duplas». WS = with separator.

POSITION / STRPOSPostgreSQL
Artigo
POSITION('@' IN email), STRPOS(email, '@')

Posição da primeira ocorrência (a partir de 1), 0 se não encontrar. STRPOS é a forma curta do PostgreSQL.

Veja também:···
LPAD / RPAD
Artigo
LPAD(id::text, 6, '0'), RPAD(name, 20, ' ')

Preenche uma string até um comprimento alvo à esquerda (LPAD) ou à direita (RPAD). Uso clássico: completar um id com zeros.

Veja também:···
INITCAPPostgreSQL
Artigo
INITCAP('john DOE')   -- John Doe

Coloca em maiúscula a primeira letra de cada palavra e o resto em minúscula. Não existe no MySQL.

Veja também:···
REPEAT
Artigo
REPEAT('ab', 3)   -- ababab

Repete uma string N vezes. Útil para placeholders e gráficos de barra em texto.

Veja também:···
REVERSE
Artigo
REVERSE(name)

Inverte uma string caractere a caractere. Às vezes usado para indexar por sufixo.

Veja também:···
char_length
Artigo
char_length(name), char_length('açai')   -- 4

Comprimento da string em CARACTERES (não bytes) — importa para UTF-8. Sinônimo de character_length.

Veja também:···
REGEXP_REPLACEPostgreSQL
Artigo
REGEXP_REPLACE(phone, '[^0-9]', '', 'g')

Substituição por expressão regular. A flag 'g' substitui todas as ocorrências; sem ela, apenas a primeira.

Veja também:·
REGEXP_MATCHESPostgreSQL
Artigo
SELECT (REGEXP_MATCHES(url, '/(\d+)'))[1] AS id;

Retorna os grupos capturados da regex como um array. Com a flag 'g' produz uma linha por correspondência.

Veja também:·
REGEXP_SPLIT_TO_ARRAYPostgreSQL
Artigo
REGEXP_SPLIT_TO_ARRAY('a, b,c', '\s*,\s*')

Divide uma string por um separador regex em um array. Há também …TO_TABLE para linhas.

Veja também:·
TRANSLATEPostgreSQL
Artigo
TRANSLATE(code, 'abc', 'xyz')   -- a->x, b->y, c->z

Substituição caractere a caractere entre dois conjuntos. Caracteres a mais do primeiro conjunto são removidos. Não é REPLACE.

Veja também:·
BTRIM / LTRIM / RTRIMPostgreSQL
Artigo
BTRIM(code, '0'), LTRIM(s), RTRIM(s, '/')

Remove os caracteres indicados de ambas as pontas (BTRIM), da esquerda (LTRIM) ou da direita (RTRIM); espaços por padrão.

Veja também:·
FORMATPostgreSQL
Artigo
FORMAT('Hi %s, id=%L', name, id)

Monta uma string a partir de um template: %s valor, %I identificador, %L literal seguro. Essencial em SQL dinâmico.

Veja também:····
STARTS_WITHPostgreSQL
Artigo
WHERE STARTS_WITH(path, '/api/')

Verifica se uma string começa com um prefixo — mais legível que LIKE 'x%'. Disponível desde o PostgreSQL 11.

Veja também:···
ascii / chrPostgreSQL
Artigo
ascii('A')   -- 65
chr(65)      -- A

Código do primeiro caractere (ascii) e o caractere de um código (chr). No MySQL a inversa se chama CHAR.

Veja também:··
to_hexPostgreSQL
Artigo
to_hex(255)   -- 'ff'

Converte um inteiro em sua string hexadecimal. Útil para cores, máscaras de bits e depuração.

Veja também:··

Números e matemática16

ROUND
Artigo
ROUND(3.14159)        -- 3
ROUND(2.5)            -- banker? no: 3

Arredonda para o inteiro mais próximo. As metades arredondam para longe do zero (2.5 → 3).

Veja também:···
ROUND(x, n)
Artigo
ROUND(3.14159, 2)     -- 3.14
ROUND(12345.6, -2)    -- 12300

Arredonda para n casas decimais; um n negativo arredonda à esquerda do ponto. Só funciona com numeric, não com float.

Veja também:···
CEIL / CEILING
Artigo
CEIL(4.1)   -- 5
CEIL(-4.1)  -- -4

Arredonda para cima até o próximo inteiro. CEILING é um sinônimo.

Veja também:···
FLOOR
Artigo
FLOOR(4.9)   -- 4
FLOOR(-4.1)  -- -5

Arredonda para baixo até o inteiro anterior. Com negativos, afasta-se do zero.

Veja também:···
TRUNCPostgreSQL
Artigo
TRUNC(3.99)     -- 3
TRUNC(3.456, 2) -- 3.45

Descarta a parte fracionária (em direção ao zero), sem arredondar. No MySQL é TRUNCATE(x, n).

Veja também:···
ABS(-7)   -- 7

Valor absoluto — a magnitude sem sinal.

Veja também:···
MOD(10, 3)   -- 1

Resto da divisão. Útil para «cada N-ésima linha» e paridade; o sinal do resultado segue o dividendo.

Veja também:···
POWER
Artigo
POWER(2, 10)   -- 1024

Eleva a uma potência. POW é um sinônimo.

Veja também:··
SQRT
Artigo
SQRT(144)   -- 12

Raiz quadrada. Um argumento negativo gera um erro.

Veja também:··
EXP / LN
Artigo
EXP(1)    -- 2.7182818...
LN(2.718) -- ~1

Exponencial e^x e logaritmo natural (base e). LN(0) e LN(negativo) geram erro.

Veja também:··
LOGPostgreSQL
Artigo
LOG(100)     -- 2  (base 10)
LOG(2, 8)    -- 3  (base 2)

No PostgreSQL LOG(x) é base 10, LOG(b, x) usa uma base arbitrária. Atenção: no MySQL LOG(x) é o logaritmo natural.

Veja também:··
SIGN
Artigo
SIGN(-42)  -- -1
SIGN(0)    -- 0

Sinal de um número: -1, 0 ou 1. Útil para ramificar conforme a direção da mudança.

Veja também:···
GREATEST / LEAST
Artigo
GREATEST(a, b, c), LEAST(a, b, c)

O maior / menor entre os argumentos dentro de uma linha (não é agregação). Argumentos NULL são ignorados.

Veja também:···
RANDOMPostgreSQL
Artigo
SELECT * FROM t ORDER BY RANDOM() LIMIT 5;

Número aleatório em [0,1). No MySQL é RAND(). ORDER BY RANDOM() dá uma amostra aleatória, mas é caro em tabelas grandes.

Veja também:
DIV (integer division)PostgreSQL
Artigo
DIV(7, 2)   -- 3
7 / 2       -- 3 when both are int

Divisão inteira que descarta o resto. No PostgreSQL / já divide inteiro quando ambos os operandos são inteiros; o MySQL usa o operador DIV para isso.

Veja também:···
WIDTH_BUCKETPostgreSQL
Artigo
WIDTH_BUCKET(score, 0, 100, 10)  -- bucket 1..10

Atribui um valor a um balde de histograma de largura igual entre dois limites. Para distribuições e agrupamento por faixas.

Veja também:····

Funções de data e hora16

AGEPostgreSQL
Artigo
AGE(end_ts, start_ts)   -- or AGE(birthday) vs now
AGE('2024-03-01', '2024-01-15')

Diferença entre duas datas como intervalo (anos/meses/dias), não em segundos. Com um único argumento conta a partir de hoje — útil para a idade.

DATE_PARTPostgreSQL
Artigo
DATE_PART('hour', created_at), DATE_PART('dow', created_at)

Forma de função do EXTRACT — extrai uma parte de data/hora como número. O campo é uma string, então é fácil passá-lo dinamicamente.

Veja também:···
EXTRACT(EPOCH FROM …)
Artigo
EXTRACT(EPOCH FROM (ended_at - started_at)) AS seconds

Converte um intervalo ou timestamp em segundos (tempo Unix). A forma padrão de medir uma duração em segundos — depois divida por 60/3600.

Veja também:···
TO_CHARPostgreSQL
Artigo
TO_CHAR(created_at, 'YYYY-MM-DD HH24:MI'), TO_CHAR(amount, 'FM999G999D00')

Formata uma data/número em uma string via um padrão (YYYY, MM, DD, HH24...). Os padrões do PostgreSQL diferem do DATE_FORMAT do MySQL.

Veja também:·
TO_DATEPostgreSQL
Artigo
TO_DATE('2024-03-15', 'YYYY-MM-DD')

Converte uma string em uma data com um padrão explícito. Mais confiável que o cast ::date quando o formato não é padrão.

Veja também:·
TO_TIMESTAMPPostgreSQL
Artigo
TO_TIMESTAMP('2024-03-15 14:30', 'YYYY-MM-DD HH24:MI')
TO_TIMESTAMP(1710512400)   -- from Unix epoch

Converte uma string em um timestamp por padrão, ou constrói um timestamptz a partir de segundos Unix (argumento numérico).

Veja também:·
CURRENT_TIMESTAMP / LOCALTIMESTAMP
Artigo
SELECT CURRENT_TIMESTAMP, LOCALTIMESTAMP;

Hora de início da transação: CURRENT_TIMESTAMP inclui fuso horário (timestamptz), LOCALTIMESTAMP não. Constante dentro de uma mesma transação.

Veja também:·
CURRENT_TIME / CURRENT_DATE
Artigo
SELECT CURRENT_DATE, CURRENT_TIME;

Apenas a data de hoje (CURRENT_DATE) ou apenas a hora (CURRENT_TIME) — sem parênteses; são valores especiais do SQL, não funções.

Veja também:·
Date arithmetic (date + int)PostgreSQL
Artigo
SELECT order_date + 7, due_date - 1, end_dt - start_dt AS days;

Você pode somar/subtrair dias inteiros de um date (date + 7). Subtrair dois date dá um número inteiro de dias; dois timestamp dão um intervalo.

Veja também:·
make_date / make_timePostgreSQL
Artigo
make_date(2024, 3, 15), make_time(14, 30, 0)

Constrói uma data ou hora a partir de números separados de ano/mês/dia, hora/minuto/segundo — sem lidar com strings de formato.

Veja também:·
make_timestamp / make_intervalPostgreSQL
Artigo
make_timestamp(2024, 3, 15, 14, 30, 0)
make_interval(days => 10, hours => 2)

Constrói um timestamp ou intervalo a partir de componentes numéricos. make_interval aceita argumentos nomeados (days =>, hours =>).

Veja também:·
AT TIME ZONEPostgreSQL
Artigo
ts_utc AT TIME ZONE 'Europe/Moscow'
local_ts AT TIME ZONE 'UTC'

Leva um instante para outro fuso horário. Sobre um timestamptz dá a hora local desse fuso; sobre um timestamp sem fuso interpreta-o como hora desse fuso.

Veja também:··
JUSTIFY_INTERVAL / JUSTIFY_HOURSPostgreSQL
Artigo
JUSTIFY_HOURS(INTERVAL '36 hours')   -- 1 day 12:00:00

Normaliza um intervalo: converte as horas excedentes em dias e os dias em meses. Transforma «50 hours» em um legível «2 days 02:00:00».

Veja também:··
DATE_BINPostgreSQL
Artigo
DATE_BIN('15 minutes', ts, TIMESTAMP '2024-01-01')

Ajusta um timestamp para o início de um bucket de largura arbitrária (ex.: 15 minutos) a partir de uma origem. Mais flexível que DATE_TRUNC. Postgres 14+.

Veja também:···
OVERLAPS
Artigo
(start_a, end_a) OVERLAPS (start_b, end_b)

Verifica se dois períodos de tempo se sobrepõem. Útil para encontrar conflitos de reservas ou turnos.

Veja também:··
Time zone cast (timestamptz)PostgreSQL
Artigo
now()::timestamptz, '2024-03-15 10:00'::timestamp

timestamptz guarda o instante em UTC e aplica o fuso na exibição; timestamp é hora «ingênua» sem fuso. Para eventos quase sempre você quer timestamptz.

Veja também:··

CASE e NULL5

CASE WHEN
Artigo
CASE
  WHEN score >= 90 THEN 'A'
  WHEN score >= 70 THEN 'B'
  ELSE 'C'
END

Lógica condicional embutida — como um if/else dentro do SELECT.

Veja também:··
CASE (simple form)
CASE status
  WHEN 'paid' THEN 'Paid'
  WHEN 'shipped' THEN 'On the way'
  ELSE '—'
END

A forma curta para comparar uma expressão contra constantes. Não captura NULL (WHEN NULL nunca corresponde) — para isso use a forma extensa com IS NULL.

COALESCE
Artigo
COALESCE(nickname, full_name, 'Anonymous')

Retorna o primeiro valor não NULL da lista.

Veja também:··
NULLIF
Artigo
NULLIF(divisor, 0)

Transforma um valor em NULL quando ele é igual ao segundo argumento. Útil para evitar a divisão por zero.

Veja também:··
NULL & IS DISTINCT FROM
Artigo
-- = NULL is never true — use IS NULL
WHERE deleted_at IS NULL
-- NULL-safe equality:
WHERE a IS DISTINCT FROM b

Comparar com NULL usando = sempre dá «desconhecido» (não TRUE/FALSE). Use IS NULL para testar; use IS DISTINCT FROM para igualdade segura com NULL.

Veja também:··

JSON / JSONB19

JSONB ->>PostgreSQL
Artigo
payload->>'target'

Extrai um valor de um JSONB como text — para operações com cadeias e comparações.

Veja também:··
JSONB @>PostgreSQL
Artigo
payload @> '{"plan":"pro"}'

O JSONB contém o fragmento indicado? Usa um índice GIN — rápido em tabelas grandes.

Veja também:···
GIN + jsonb_path_opsPostgreSQL
Artigo
CREATE INDEX idx ON events USING GIN (payload jsonb_path_ops)

Índice ideal para consultas @> sobre JSONB. Mais compacto que o jsonb_ops padrão.

Veja também:
JSONB -> / ->>PostgreSQL
Artigo
data->'user'->>'name'   -- -> keeps json, ->> as text

-> extrai um campo/elemento como jsonb (para continuar navegando), ->> como text. Chave por string, índice de array por número.

Veja também:··
#> / #>>PostgreSQL
Artigo
data #>> '{address,city}'   -- text at a nested path

Lê um valor em um caminho aninhado dado como array de chaves: #> como jsonb, #>> como text. Mais curto que encadear ->.

Veja também:··
JSONB_BUILD_OBJECTPostgreSQL
Artigo
jsonb_build_object('id', id, 'name', name)

Constrói um objeto JSON a partir de pares chave, valor alternados. Os tipos dos valores são preservados (números continuam números).

Veja também:····
JSONB_BUILD_ARRAYPostgreSQL
Artigo
jsonb_build_array(id, name, created_at)

Constrói um array JSON a partir dos argumentos dados de quaisquer tipos.

Veja também:····
JSONB_AGGPostgreSQL
Artigo
jsonb_agg(item ORDER BY created_at)

Agregado: reúne um grupo de linhas em um array JSON. O ORDER BY interno fixa a ordem dos elementos.

Veja também:····
JSONB_ARRAY_ELEMENTSPostgreSQL
Artigo
SELECT e FROM t, jsonb_array_elements(t.tags) AS e

Expande um array JSON em linhas — uma linha por elemento. A variante _text retorna text em vez de jsonb.

Veja também:·····
JSONB_ARRAY_LENGTHPostgreSQL
Artigo
jsonb_array_length(data->'items')

Comprimento de um array JSON. Gera erro se o valor não for um array — proteja com jsonb_typeof.

Veja também:·····
JSONB_SETPostgreSQL
Artigo
jsonb_set(data, '{address,city}', '"Lima"')

Retorna uma cópia do JSON com o valor do caminho substituído. create_missing=true (padrão) adiciona a chave se ela faltar.

Veja também:·
JSONB_EACHPostgreSQL
Artigo
SELECT key, value FROM jsonb_each(data)

Expande um objeto JSON em linhas (key, value) — uma por chave. A variante _text retorna value como text.

Veja também:·····
JSONB_OBJECT_KEYSPostgreSQL
Artigo
SELECT jsonb_object_keys(data)

Retorna os nomes das chaves de nível superior de um objeto JSON, uma linha por chave.

Veja também:·····
? / ?| / ?&PostgreSQL
Artigo
data ? 'email'        -- has key?
data ?| array['a','b'] -- any of these keys?

Testes de existência de chaves: ? uma chave, ?| qualquer destas, ?& todas. Apenas nível superior; suportam índice GIN.

Veja também:·····
JSONB || (merge)PostgreSQL
Artigo
data || '{"verified":true}'

Mescla dois valores JSONB: as chaves da direita sobrescrevem as da esquerda (superficial, não recursivo). Útil para uma atualização parcial.

Veja também:·
JSONB - / #- (delete)PostgreSQL
Artigo
data - 'temp'              -- drop a key
data #- '{address,zip}'    -- drop at a path

Remove uma chave/elemento: - por chave ou índice de nível superior, #- em um caminho aninhado. Retorna um novo JSONB.

Veja também:·
to_jsonbPostgreSQL
Artigo
to_jsonb(row_var)   -- whole row as a json object

Converte qualquer valor/linha/array de SQL em jsonb. Uma linha inteira da tabela vira um objeto JSON coluna → valor.

Veja também:·····
JSONB_TYPEOFPostgreSQL
Artigo
jsonb_typeof(data->'price')   -- 'number','string',...

O tipo do valor JSON como text: object, array, string, number, boolean, null. Útil antes de jsonb_array_length etc.

Veja também:·····
JSONB_PRETTYPostgreSQL
Artigo
jsonb_pretty(data)

Formata JSONB com indentação para uma saída legível ou depuração.

Veja também:·····

Desempenho e índices9

CREATE INDEX
CREATE INDEX idx_orders_user ON orders (user_id);

Um índice B-tree comum: acelera buscas por igualdade e por intervalo na coluna. O custo — espaço em disco e escritas um pouco mais lentas.

Partial indexPostgreSQL
Artigo
CREATE INDEX i ON orders (id) WHERE status = 'pending';

Índice apenas sobre o subconjunto «quente» de linhas — mais compacto e mais rápido de varrer.

Veja também:····
Composite index
Artigo
CREATE INDEX i ON orders (customer_id, created_at DESC);

Cobre o filtro e a ordenação em uma única leitura. A ordem das colunas importa.

Veja também:····
Sargable WHERE
Artigo
-- bad : WHERE EXTRACT(YEAR FROM ts) = 2024
-- good: WHERE ts >= '2024-01-01' AND ts < '2025-01-01'

Não envolva a coluna em uma função — o índice não será usado. Reescreva como um intervalo.

Veja também:····
NOT EXISTS vs NOT IN
Artigo
-- NOT IN collapses to 0 rows on any NULL
WHERE NOT EXISTS (SELECT 1 FROM orders o WHERE o.user_id = u.id);

NOT IN quebra silenciosamente diante de qualquer NULL na subconsulta. NOT EXISTS é seguro com NULL.

Veja também:···
CONCURRENTLYPostgreSQL
Artigo
CREATE INDEX CONCURRENTLY i ON events (user_id, kind);

Constrói um índice em uma tabela quente sem um bloqueio pesado. É proibido dentro de uma transação.

Veja também:····
EXPLAIN
Artigo
EXPLAIN SELECT * FROM orders WHERE user_id = 5;

Mostra o plano da consulta sem executá-la: quais varreduras (Seq Scan / Index Scan), ordem dos joins, linhas estimadas.

Veja também:····
EXPLAIN (ANALYZE, BUFFERS)
Artigo
EXPLAIN (ANALYZE, BUFFERS)
SELECT * FROM orders WHERE user_id = 5;

Executa a consulta e mostra o tempo e as linhas REAIS versus a estimativa. Uma grande diferença estimativa↔real indica estatísticas desatualizadas ou um índice ausente.

Veja também:····
VACUUM / ANALYZEPostgreSQL
VACUUM (ANALYZE) orders;
ANALYZE orders;  -- stats only

VACUUM recupera as versões mortas de linhas deixadas por UPDATE/DELETE; ANALYZE atualiza as estatísticas do planejador. Planos ruins após mudanças em massa são corrigidos exatamente aqui.

Transações8

BEGIN / COMMIT / ROLLBACK
BEGIN;
UPDATE accounts SET balance = balance - 200 WHERE id = 1;
UPDATE accounts SET balance = balance + 200 WHERE id = 2;
COMMIT;   -- or ROLLBACK; to undo everything

Uma transação: tudo entre BEGIN e COMMIT é aplicado como um todo, ou (após ROLLBACK) não é aplicado de forma alguma. Ninguém de fora vê o estado intermediário.

SAVEPOINT
BEGIN;
SAVEPOINT before_bonus;
UPDATE accounts SET balance = balance + 50 WHERE id = 7;
ROLLBACK TO before_bonus;  -- undo just this part
COMMIT;

Um ponto de retorno dentro de uma transação: ROLLBACK TO desfaz apenas os passos após o SAVEPOINT, não a transação inteira.

Isolation levelsPostgreSQL
BEGIN ISOLATION LEVEL REPEATABLE READ;
-- READ COMMITTED (default) | REPEATABLE READ | SERIALIZABLE

O que uma transação enxerga das mudanças concorrentes. READ COMMITTED (padrão) — cada instrução vê commits recentes; REPEATABLE READ — um snapshot para a transação inteira; SERIALIZABLE — como se as transações rodassem uma a uma (prepare-se para repetir erros de serialização).

SELECT … FOR UPDATE
Artigo
BEGIN;
SELECT * FROM accounts WHERE id IN (1, 2) FOR UPDATE;
UPDATE accounts SET balance = balance - 200 WHERE id = 1;
UPDATE accounts SET balance = balance + 200 WHERE id = 2;
COMMIT;

Bloqueia linhas até o fim da transação. Padrão para transferências de dinheiro.

Veja também:··
Conditional UPDATE
Artigo
UPDATE accounts SET balance = balance - 200
WHERE id = 1 AND balance >= 200;

Verificação e atualização em uma única instrução atômica. Se 0 linhas forem atualizadas — exiba «saldo insuficiente».

Veja também:··
CTE + UPDATE … RETURNINGPostgreSQL
Artigo
WITH reserved AS (
  UPDATE inventory SET stock = stock - 1
  WHERE sku = 'WIDGET' AND stock >= 1
  RETURNING id
)
INSERT INTO shipments (sku, qty)
SELECT 'WIDGET', 1 FROM reserved;

Encadeia duas alterações em uma única instrução: a segunda só roda se a primeira de fato tocou uma linha. Se não há o que reservar nada muda, e não há janela de concorrência entre as partes.

FOR UPDATE SKIP LOCKED
Artigo
SELECT id FROM jobs
WHERE status = 'pending'
ORDER BY id
FOR UPDATE SKIP LOCKED LIMIT 1;

Fila de workers: cada worker pega a sua própria tarefa, ignorando as linhas bloqueadas por outros.

Veja também:··
Atomic counter
Artigo
UPDATE counters SET n = n + 1 WHERE id = 1;

Um único UPDATE incrementa o contador de forma segura diante da concorrência. SELECT seguido de UPDATE perde incrementos.

Veja também:··

Controle de acesso (GRANT/REVOKE)4

GRANT
Artigo
GRANT SELECT, INSERT ON orders TO analyst;

Concede privilégios sobre um objeto a um papel/usuário. Liste as ações necessárias (SELECT, INSERT, UPDATE, DELETE).

Veja também:··
REVOKE
Artigo
REVOKE INSERT ON orders FROM analyst;

Revoga privilégios concedidos anteriormente.

Veja também:··
CREATE ROLE
Artigo
CREATE ROLE analyst LOGIN PASSWORD 'secret';

Cria um papel (usuário/grupo). Os privilégios são concedidos ao papel, e os usuários são membros dele.

Veja também:··
Read-only role
Artigo
GRANT USAGE ON SCHEMA public TO readonly;
GRANT SELECT ON ALL TABLES IN SCHEMA public TO readonly;
ALTER DEFAULT PRIVILEGES IN SCHEMA public
  GRANT SELECT ON TABLES TO readonly;

O padrão clássico somente-leitura: acesso ao esquema + SELECT em todas as tabelas + uma regra para tabelas futuras via ALTER DEFAULT PRIVILEGES.

Veja também:··

BigQuery (GoogleSQL)69

COUNTIF
SELECT city, COUNTIF(is_paid) AS paid
FROM events
GROUP BY city;

Conta as linhas em que a condição é verdadeira. Substitui SUM(CASE WHEN … THEN 1 ELSE 0 END).

IF
SELECT IF(revenue > 2000, 'big', 'normal') AS size
FROM events;

Uma escolha ternária em uma função: condição, valor se verdadeiro, valor se falso.

IFNULL / COALESCE
SELECT IFNULL(revenue, 0) AS revenue FROM events;

Substitui NULL por um valor alternativo. IFNULL aceita dois argumentos; COALESCE, quantos precisar.

SAFE_DIVIDE
SELECT SAFE_DIVIDE(COUNTIF(is_paid), COUNT(*)) AS cr
FROM events;

Divisão que retorna NULL quando o denominador é zero, em vez de falhar.

SAFE_CAST
SELECT SAFE_CAST(session_id AS INT64) AS code FROM events;

Uma conversão que devolve NULL em valor inconversível, em vez de quebrar a consulta.

SAFE. prefix
SELECT SAFE.PARSE_DATE("%Y-%m-%d", raw_date) AS d FROM events;

O prefixo SAFE. na maioria das funções escalares transforma um erro de execução em NULL.

LOGICAL_OR / LOGICAL_AND
SELECT platform, LOGICAL_OR(is_paid) AS had_purchase
FROM events GROUP BY platform;

Agregados booleanos: se algum valor foi TRUE e se todos foram TRUE.

QUALIFY
SELECT city, event_id, revenue
FROM events
QUALIFY ROW_NUMBER() OVER (PARTITION BY city ORDER BY revenue DESC) = 1;

Filtra pelo resultado de uma função de janela sem subconsulta nem CTE. Executa após SELECT e WINDOW.

SELECT * EXCEPT
SELECT * EXCEPT(items, session) FROM events;

Todas as colunas exceto as listadas, sem escrever o restante à mão.

SELECT * REPLACE
SELECT * REPLACE(IFNULL(revenue, 0) AS revenue) FROM events;

Troca o valor de uma coluna mantendo o nome e a posição dela.

ARRAY
SELECT ["food", "bed"] AS items;
-- column type: ARRAY<STRING>

Um campo repetido: uma célula guarda uma lista de valores do mesmo tipo.

UNNEST (GoogleSQL)
SELECT event_id, item
FROM events, UNNEST(items) AS item;

Expande um array em linhas, uma por elemento. Um evento com array vazio some.

UNNEST … WITH OFFSET
SELECT item, pos
FROM events, UNNEST(items) AS item WITH OFFSET AS pos;

A mesma expansão mais o índice do elemento no array, começando em zero.

ARRAY_LENGTH
SELECT ARRAY_LENGTH(items) AS basket_size FROM events;

A quantidade de elementos de um array, sem expandi-lo.

ARRAY_AGG (GoogleSQL)
SELECT user_id,
       ARRAY_AGG(event_name ORDER BY event_ts LIMIT 2) AS first_two
FROM events GROUP BY user_id;

Reúne um grupo em um array. Aceita DISTINCT, ORDER BY e LIMIT dentro da chamada.

ARRAY_TO_STRING
SELECT ARRAY_TO_STRING(items, ", ") AS basket FROM events;

Junta um array em uma única string com o separador informado.

IN UNNEST
SELECT event_id FROM events WHERE "food" IN UNNEST(items);

Testa a pertinência a um array sem join nem achatar a tabela inteira.

OFFSET / ORDINAL
SELECT APPROX_QUANTILES(revenue, 2)[OFFSET(1)] AS median FROM events;

Indexação de array: OFFSET conta a partir de zero e ORDINAL a partir de um.

STRUCT
SELECT STRUCT(city AS city, revenue AS amount) AS order_info
FROM events;

Um registro aninhado: vários campos nomeados dentro de uma coluna.

Struct field access
SELECT session.source, session.minutes
FROM events
WHERE session.minutes > 5;

Os campos de um registro são acessados com ponto, em SELECT, WHERE e GROUP BY.

ARRAY_AGG(STRUCT(…))
SELECT user_id,
       ARRAY_AGG(STRUCT(event_name AS name, event_ts AS ts) ORDER BY event_ts) AS events
FROM events GROUP BY user_id;

Um array de registros: todo o histórico de um grupo em uma coluna, sem perder estrutura.

Wildcard table
SELECT user_id, minutes
FROM `sessions_d*`;

Lê como uma só todas as tabelas que casam com o prefixo. Substitui uma cadeia de UNION ALL.

_TABLE_SUFFIX
SELECT _TABLE_SUFFIX AS day, COUNT(*) AS sessions
FROM `sessions_d*`
WHERE _TABLE_SUFFIX BETWEEN '20260314' AND '20260316'
GROUP BY day;

Pseudocoluna com a parte do nome capturada pelo curinga: serve para agrupar e para filtrar shards. O prefixo não entra: para sessions_d* o valor é 20260314, não d20260314. No sandbox da Arena o emulador devolve a cauda do nome completo, por isso lá o dia é obtido com RIGHT(_TABLE_SUFFIX, 8).

_TABLE_SUFFIX (Arena sandbox)
SELECT RIGHT(_TABLE_SUFFIX, 8) AS day, COUNT(*) AS sessions
FROM `sessions_d*`
WHERE RIGHT(_TABLE_SUFFIX, 8) BETWEEN '20260314' AND '20260316'
GROUP BY day;

A forma que roda no treinador: o emulador do sandbox devolve a cauda do nome completo no sufixo, por isso o dia são os últimos 8 caracteres. No BigQuery real não se escreve assim: envolver a pseudocoluna anula o descarte de shards, e a consulta lê (e cobra) todos os dias.

PARTITION BY / CLUSTER BY
CREATE TABLE sessions (d DATE, user_id INT64)
PARTITION BY d
CLUSTER BY user_id;

O particionamento divide a tabela por data e o clustering ordena os dados dentro: juntos reduzem os bytes lidos.

DATE_TRUNC (GoogleSQL)
SELECT DATE_TRUNC(DATE(event_ts), MONTH) AS month FROM events;

Trunca uma data. A ordem é inversa à habitual: primeiro a data, depois a unidade, sem aspas.

TIMESTAMP_TRUNC
SELECT TIMESTAMP_TRUNC(event_ts, DAY) AS day, COUNT(*) AS cnt
FROM events
GROUP BY day;

Trunca um instante para o início do dia, da hora ou da semana. Devolve um TIMESTAMP, não uma data: sai como "2026-03-14 00:00:00 UTC".

DATETIME
SELECT DATETIME(event_ts, "Europe/Moscow") AS local_time FROM events;

Converte um instante em hora de parede de um fuso. TIMESTAMP é um ponto na linha do tempo (sempre UTC); DATETIME é a leitura do relógio, sem fuso.

TIMESTAMP_DIFF / DATE_DIFF
SELECT TIMESTAMP_DIFF(MAX(event_ts), MIN(event_ts), MINUTE) AS minutes
FROM events;

A diferença entre dois instantes na unidade escolhida. Não dá para subtrair timestamps direto.

FORMAT_TIMESTAMP
SELECT FORMAT_TIMESTAMP("%A", event_ts) AS day_name FROM events;

Formata um instante como texto conforme um padrão. Equivale ao TO_CHAR do PostgreSQL.

GENERATE_DATE_ARRAY
SELECT day
FROM UNNEST(GENERATE_DATE_ARRAY(DATE "2026-03-14", DATE "2026-03-16")) AS day;

Uma série de datas sem lacunas: um calendário para juntar aos dados. Equivale a generate_series.

EXTRACT (GoogleSQL)
SELECT EXTRACT(HOUR FROM event_ts) AS hour FROM events;

Extrai uma parte de um instante: HOUR, DAYOFWEEK, WEEK, MONTH, YEAR e outras.

REGEXP_CONTAINS
SELECT * FROM events WHERE REGEXP_CONTAINS(city, r"^M");

Testa um valor contra uma expressão regular. Substitui LIKE quando um padrão com % não basta. O prefixo r desliga o escape.

REGEXP_EXTRACT
SELECT REGEXP_EXTRACT(page_path, r"/([a-z]+)/") AS section FROM events;

Extrai o primeiro grupo de captura da correspondência. Sem correspondência devolve NULL, não string vazia.

JSON_VALUE
SELECT JSON_VALUE(payload, "$.utm.source") AS utm_source FROM events;

Lê um escalar de um JSON por caminho e devolve como texto, já sem aspas.

JSON_QUERY_ARRAY
SELECT item
FROM events, UNNEST(JSON_QUERY_ARRAY(payload, "$.items")) AS item;

Extrai um array JSON como array de valores, pronto para expandir com UNNEST.

APPROX_COUNT_DISTINCT
SELECT platform, APPROX_COUNT_DISTINCT(user_id) AS users
FROM events GROUP BY platform;

Uma contagem distinta aproximada. Mais barata que COUNT(DISTINCT) exato em escala, com pequeno erro.

APPROX_QUANTILES
SELECT APPROX_QUANTILES(revenue, 2)[OFFSET(1)] AS median FROM events;

Quantis aproximados: divide a amostra em N partes e devolve os limites como array.

ANY_VALUE
SELECT user_id, ANY_VALUE(city) AS city, COUNT(*) AS events
FROM events GROUP BY user_id;

Pega um valor qualquer do grupo: útil quando a coluna é constante nele e não cabe no GROUP BY.

PIVOT
SELECT * FROM (SELECT city, platform FROM events)
PIVOT(COUNT(*) FOR platform IN ('ios', 'android', 'web'));

Transforma valores de uma coluna em colunas próprias. A lista de valores é explícita.

UNPIVOT
SELECT * FROM wide_table
UNPIVOT(value FOR metric IN (dau, wau, mau));

A operação inversa: várias colunas viram pares nome/valor.

GROUP BY ROLLUP
SELECT city, platform, COUNT(*) AS cnt
FROM events
GROUP BY ROLLUP(city, platform);

Adiciona subtotais e um total geral: nessas linhas as colunas agrupadas ficam NULL.

STRING_AGG (GoogleSQL)
SELECT platform, STRING_AGG(DISTINCT city, ", " ORDER BY city) AS cities
FROM events GROUP BY platform;

Junta um grupo em uma string. Aceita DISTINCT e ORDER BY dentro da chamada.

ROWS BETWEEN
SELECT event_ts, revenue,
       SUM(revenue) OVER (ORDER BY event_ts
         ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS running_total
FROM events;

O quadro da janela: quais linhas ao redor da atual entram no cálculo. "Do início da janela até a linha atual" é um total acumulado.

ROWS N PRECEDING
AVG(revenue) OVER (ORDER BY event_ts
  ROWS BETWEEN 2 PRECEDING AND CURRENT ROW)

Uma média móvel sobre três linhas: a atual mais as duas anteriores. O limite conta LINHAS, não valores.

ROWS … FOLLOWING
MAX(revenue) OVER (ORDER BY event_ts
  ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING)

Uma vizinhança: a linha anterior, a própria linha e a seguinte. FOLLOWING olha adiante conforme o ORDER BY da janela.

OVER () (whole table)
SELECT sku, qty,
       SAFE_DIVIDE(qty, SUM(qty) OVER ()) AS share
FROM stock;

Um OVER () vazio é uma janela sobre toda a entrada, sem partição nem ordem. Cada linha recebe o total geral ao lado, sem subconsulta.

Default frame (RANGE)
-- these two are NOT the same:
SUM(x) OVER (ORDER BY d)
SUM(x) OVER (ORDER BY d ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)

Com ORDER BY e sem quadro, o padrão é RANGE … CURRENT ROW: as linhas com a MESMA chave de ordenação entram todas. Com datas repetidas o resultado difere de ROWS.

CREATE TEMP FUNCTION
CREATE TEMP FUNCTION with_markup(p NUMERIC) AS (ROUND(p * 1.2, 2));
SELECT sku, price, with_markup(price) AS price_vat
FROM stock;

Uma função SQL temporária: declarada como instrução SEPARADA antes da consulta, vive até o fim dela e não deixa nada no conjunto de dados.

UDF signature
CREATE TEMP FUNCTION pct(part FLOAT64, whole FLOAT64) AS (
  ROUND(100 * SAFE_DIVIDE(part, whole), 1)
);

Os argumentos são declarados com tipos e o corpo é uma única expressão entre parênteses. RETURNS é opcional: o tipo vem do corpo.

UDF in SELECT and WHERE
SELECT sku, qty, shelf_state(qty) AS state
FROM stock
WHERE shelf_state(qty) != 'ok';

A função declarada é chamada pelo nome em qualquer parte da consulta, tanto na saída quanto no filtro. A regra é escrita uma vez, então uma edição não dessincroniza cópias.

FULL JOIN by key
SELECT COALESCE(u.sku, s.sku) AS sku
FROM stock s
FULL OUTER JOIN stock_updates u ON s.sku = u.sku;

Uma junção que não perde linhas: sem par, o outro lado chega como NULL. A chave do resultado é montada com COALESCE dos dois lados.

MERGE (GoogleSQL)
MERGE stock t
USING stock_updates s ON t.sku = s.sku
WHEN MATCHED THEN UPDATE SET qty = s.qty
WHEN NOT MATCHED THEN INSERT ROW;

A fusão nativa do BigQuery: numa passada atualiza as linhas correspondentes e insere as novas. Os conjuntos do treinador são somente leitura, então aqui o resultado é CALCULADO por uma consulta.

MERGE as a SELECT
SELECT COALESCE(u.sku, s.sku) AS sku,
       COALESCE(u.qty, s.qty) AS qty
FROM stock s
FULL OUTER JOIN stock_updates u ON s.sku = u.sku;

FULL OUTER JOIN mais COALESCE é um upsert sem escrever: no encontro vence o lado das atualizações, e os solitários dos dois lados sobrevivem.

MERGE branches as NULL tests
SELECT CASE WHEN s.sku IS NULL THEN 'insert'
            WHEN u.sku IS NULL THEN 'keep'
            ELSE 'update' END AS action
FROM stock s
FULL OUTER JOIN stock_updates u ON s.sku = u.sku;

Depois de uma junção completa as ramificações do MERGE viram testes de NULL: sem linha à esquerda é inserção, sem linha à direita a linha fica como está, com as duas é atualização.

Unrecognized name
-- Unrecognized name: platfrom; Did you mean platform? at [1:8]
SELECT platform, COUNT(*) AS cnt FROM events GROUP BY platform;

O nome não está no escopo. A mensagem dá a posição [linha:coluna] e muitas vezes um candidato após "Did you mean" — corrija o erro em TODOS os lugares de uma vez.

No matching signature
-- No matching signature for aggregate function SUM
--   for argument types: STRING
SELECT city, SUM(revenue) AS total FROM events GROUP BY city;

A função existe, mas recebeu o tipo errado — e a mensagem diz qual recebeu. A cura é outra coluna ou um SAFE_CAST explícito.

Table not found
-- Table not found: event
SELECT event_name, COUNT(*) AS cnt FROM events GROUP BY event_name;

A sintaxe está certa — o endereço não foi encontrado. Compare o nome com a descrição do conjunto de dados; o endereço completo vai entre crases: `project.dataset.table`.

Alias visibility
SELECT city, COUNT(*) AS cnt
FROM events
GROUP BY city
HAVING cnt > 1
ORDER BY cnt DESC;

Um alias do SELECT é visível em GROUP BY, HAVING e ORDER BY, mas NÃO no WHERE: o WHERE roda antes do SELECT. Usá-lo lá gera o mesmo "Unrecognized name".

Bytes scanned
-- SELECT * reads every column;
-- naming columns reads only those.
SELECT city, revenue FROM events;

O BigQuery cobra por bytes lidos, não por linhas: com armazenamento colunar, SELECT * é o mais caro.

Partition pruning
SELECT COUNT(*) FROM sessions
WHERE d BETWEEN DATE "2026-03-14" AND DATE "2026-03-16";

Um filtro na coluna de partição descarta partições antes da leitura: é aí que está a economia.

Half-open range
SELECT visit_id
FROM visits
WHERE logged_ts >= TIMESTAMP '2026-03-14 00:00:00'
  AND logged_ts <  TIMESTAMP '2026-03-16 00:00:00';

Um intervalo de TIMESTAMP se escreve semiaberto: limite esquerdo inclusivo e direito estrito. BETWEEN inclui os dois e pega um instante a mais do dia seguinte.

Function on the partition column
-- prunes:       WHERE DATE(logged_ts) = DATE '2026-03-15'
-- reads it all: WHERE FORMAT_TIMESTAMP('%F', logged_ts) = '2026-03-15'
SELECT visit_id FROM visits WHERE DATE(logged_ts) = DATE '2026-03-15';

De uma data comparada com um literal o planejador deduz a lista de partições antes da leitura. De um timestamp formatado como texto, não: o resultado é o mesmo e a tabela inteira é lida. CAST e concatenação se comportam igual.

Cost checklist
-- 1. name the columns, never SELECT *
-- 2. filter the partition column, unwrapped
-- 3. APPROX_* instead of exact COUNT(DISTINCT)
-- 4. LIMIT trims the OUTPUT, not the bytes scanned

A ordem da economia: nomear as colunas, podar partições com um filtro sem invólucro e usar um agregado aproximado. LIMIT não entra nessa lista: ele corta a saída, mas a conta é pelas colunas lidas.

Clustering key filter
-- CLUSTER BY station
SELECT reading_id, raw_power
FROM readings
WHERE station = 'Reactor-A';

O clustering guarda juntas as linhas com a mesma chave. Uma igualdade nessa chave lê só os blocos delas, e a economia só vale enquanto a coluna aparecer no filtro como está.

IN list on a cluster key
SELECT reading_id, station
FROM readings
WHERE station IN ('Reactor-A', 'Dome-Air');

Vários valores da mesma chave vão numa lista IN: os blocos são escolhidos por ela. Uma cadeia de OR sobre colunas diferentes quebra essa seleção.

STARTS_WITH (GoogleSQL)
SELECT reading_id
FROM readings
WHERE STARTS_WITH(raw_recorded, '14.03.2026');

Verifica se uma string começa com o prefixo dado. O predicado recai sobre a coluna sem envolver a chave de clustering, ao contrário de mudar a caixa ou usar máscara.

Cluster key order
-- CLUSTER BY station, sensor
SELECT reading_id
FROM readings
WHERE station = 'Dock-Bay' AND sensor = 'PRESS';

Um cluster composto é uma ordenação pelas colunas em sequência. O filtro útil repete o PREFIXO dessa ordem: a primeira coluna, ou a primeira e a segunda. Filtrar só pela segunda não descarta blocos.

Function on a cluster key
-- kills block pruning:
--   WHERE LOWER(station) = 'reactor-a'
SELECT reading_id FROM readings WHERE station = 'Reactor-A';

Qualquer invólucro sobre uma coluna de clustering — LOWER, CAST, concatenação — apaga a vantagem: compara-se um valor calculado, então todos os blocos são lidos.

ClickHouse: columnar functions22

countIf
SELECT city, countIf(is_paid) AS paid
FROM events
GROUP BY city;

Conta as linhas em que a condição é verdadeira. Substitui SUM(CASE WHEN … THEN 1 ELSE 0 END).

sumIf / avgIf
SELECT sumIf(amount, status = 'paid') AS revenue,
       avgIf(amount, status = 'paid') AS avg_check
FROM orders;

O sufixo -If funciona com qualquer agregação: apenas as linhas que satisfazem a condição entram.

multiIf
SELECT multiIf(amount > 1000, 'big',
               amount > 100, 'mid',
               'small') AS bucket
FROM orders;

Uma cadeia de condições em uma função em vez de CASE WHEN … ELSE. A primeira verdadeira vence.

uniq / uniqExact
SELECT uniqExact(user_id) AS users_exact,
       uniq(user_id)      AS users_approx
FROM events;

uniqExact é exato; uniq é aproximado, porém bem mais rápido em grandes volumes.

argMax / argMin
SELECT category,
       argMax(product_name, price) AS most_expensive
FROM products
GROUP BY category;

Retorna o primeiro argumento da linha em que o segundo é máximo (mínimo).

quantile / quantiles
SELECT quantile(0.95)(response_ms) AS p95,
       quantiles(0.5, 0.9, 0.99)(response_ms) AS p50_p90_p99
FROM requests;

Percentis em uma chamada: primeiro o nível, depois a coluna.

any / anyLast
SELECT user_id, anyLast(status) AS last_status
FROM events
GROUP BY user_id;

Pega um valor qualquer (any) ou o último visto (anyLast) do grupo.

topK
SELECT topK(3)(country) AS top_countries
FROM sessions;

Os três valores mais frequentes em uma chamada.

groupArray
SELECT user_id, groupArray(product_id) AS bought
FROM orders
GROUP BY user_id;

Reúne os valores do grupo em um array.

arrayJoin
SELECT user_id, arrayJoin(tags) AS tag
FROM users;

Expande um array em linhas: uma linha por elemento.

arrayMap / arrayFilter
SELECT arrayMap(x -> x * 2, prices)      AS doubled,
       arrayFilter(x -> x > 100, prices) AS big
FROM orders;

Transforma e filtra um array com uma lambda, sem expandi-lo em linhas.

arraySum / arrayCount / length
SELECT length(items)                       AS n,
       arraySum(prices)                   AS total,
       arrayCount(x -> x > 0, deltas)     AS positives
FROM carts;

Agregação dentro de um array: soma, contagem condicional, tamanho.

has / indexOf
SELECT * FROM users
WHERE has(tags, 'premium');

Verifica se um elemento está no array e encontra sua posição.

toStartOfDay / toStartOfMonth
SELECT toStartOfDay(created_at) AS day, count()
FROM events
GROUP BY day
ORDER BY day;

Trunca o timestamp para o dia, hora, semana ou mês.

toDate / toDateTime
SELECT toDate(created_at) AS d
FROM events
WHERE created_at >= toDateTime('2026-01-01 00:00:00');

Conversão para data e data-hora; o tipo precisa ser explícito.

toHour / toDayOfWeek / toMonth
SELECT toHour(created_at) AS hour, count()
FROM events
GROUP BY hour;

Extrai uma parte da data com função própria em vez de EXTRACT.

formatDateTime
SELECT formatDateTime(created_at, '%Y-%m') AS month
FROM events;

Formata uma data com um padrão — equivalente a TO_CHAR.

dateDiff
SELECT dateDiff('day', signup_at, first_order_at) AS days_to_order
FROM users;

Diferença entre datas na unidade indicada; a unidade vem primeiro.

LIMIT n BY
SELECT category, product_name, price
FROM products
ORDER BY price DESC
LIMIT 3 BY category;

Três linhas por valor da chave, sem função de janela.

PREWHERE
SELECT user_id, amount
FROM orders
PREWHERE status = 1
WHERE amount > 100;

Filtro aplicado antes de ler as demais colunas: lê-se menos do disco.

FINAL
SELECT * FROM orders FINAL
WHERE user_id = 42;

Aplica o colapso pendente do ReplacingMergeTree na hora; mais caro.

WITH FILL
SELECT toStartOfDay(created_at) AS day, count()
FROM events
GROUP BY day
ORDER BY day WITH FILL STEP INTERVAL 1 DAY;

Preenche as lacunas da série: dias sem eventos aparecem com zero.

MySQL: dialect differences16

IFNULL / NULLIF
SELECT IFNULL(nickname, 'anonymous') AS name
FROM users;

Substitui um valor quando há NULL. COALESCE também funciona e aceita mais argumentos.

GROUP_CONCAT
SELECT user_id,
       GROUP_CONCAT(product_name ORDER BY price DESC SEPARATOR ', ') AS items
FROM orders
GROUP BY user_id;

Junta os valores do grupo em uma string; ordem e separador vão dentro da chamada.

SUBSTRING_INDEX
SELECT SUBSTRING_INDEX(email, '@', -1) AS domain
FROM users;

Pega a parte antes (ou depois) do enésimo delimitador; N negativo conta do fim.

REGEXP / RLIKE
SELECT * FROM users
WHERE email REGEXP '^[a-z]+@example\\.com$';

Correspondência por expressão regular; o MySQL 8 traz REGEXP_REPLACE e REGEXP_SUBSTR.

DATE_FORMAT
SELECT DATE_FORMAT(created_at, '%Y-%m') AS month, COUNT(*)
FROM orders
GROUP BY month;

Formata uma data com um padrão; os padrões são próprios: %Y, %m, %d.

DATEDIFF / TIMESTAMPDIFF
SELECT DATEDIFF(delivered_at, created_at) AS days,
       TIMESTAMPDIFF(HOUR, created_at, delivered_at) AS hours
FROM orders;

DATEDIFF retorna dias e recebe os argumentos invertidos; TIMESTAMPDIFF recebe a unidade primeiro.

CURDATE / NOW
SELECT * FROM orders
WHERE created_at >= CURDATE() - INTERVAL 7 DAY;

CURDATE é a data de hoje sem hora; NOW inclui a hora. O intervalo vai sem aspas.

DATE_ADD / DATE_SUB
SELECT DATE_ADD(created_at, INTERVAL 1 MONTH) AS renew_at
FROM subscriptions;

Desloca uma data por um intervalo; somar um inteiro não funciona aqui.

LIMIT offset, count
SELECT * FROM products
ORDER BY price DESC
LIMIT 10 OFFSET 20;

O MySQL aceita LIMIT 20, 10: primeiro o deslocamento. É mais seguro usar LIMIT … OFFSET ….

Window functions (8.0+)
SELECT category, product_name,
       ROW_NUMBER() OVER (PARTITION BY category ORDER BY price DESC) AS rn
FROM products;

ROW_NUMBER e RANK exigem MySQL 8.0; no 5.7 não existem.

CAST / CONVERT
SELECT CAST(price AS DECIMAL(10, 2)) AS price_2dp
FROM products;

Conversão de tipo; não existe o atalho `::` e a divisão inteira usa DIV.

Backticks / ONLY_FULL_GROUP_BY
SELECT `order`, COUNT(*)
FROM `orders`
GROUP BY `order`;

Identificadores usam crase; ONLY_FULL_GROUP_BY vem ligado por padrão.

ON DUPLICATE KEY UPDATE
INSERT INTO stats (day, hits)
VALUES (CURDATE(), 1)
ON DUPLICATE KEY UPDATE hits = hits + 1;

Insere ou atualiza ao colidir com uma chave única.

INSERT IGNORE / REPLACE
INSERT IGNORE INTO users (email) VALUES (?);

IGNORE ignora linhas conflitantes; REPLACE apaga e insere, perdendo as demais colunas.

UPDATE … JOIN
UPDATE orders o
JOIN users u ON u.id = o.user_id
SET o.is_vip = 1
WHERE u.plan = 2;

A junção fica no próprio UPDATE, antes do SET.

AUTO_INCREMENT
CREATE TABLE users (
  id INT AUTO_INCREMENT PRIMARY KEY,
  email VARCHAR(255) NOT NULL UNIQUE
) ENGINE = InnoDB;

A autonumeração é um atributo da coluna, não um tipo.