O BigQuery usa o GoogleSQL (anteriormente chamado de Standard SQL), um dialeto que é majoritariamente compatível com ANSI SQL mas com extensões importantes para lidar com dados semiestruturados, escala e produtividade.
Diferenças sintáticas do ANSI SQL
Referência a tabelas
Tabelas são identificadas por três partes separadas por ponto. Quando o nome contém hífens ou caracteres especiais, usar crases:
-- Forma padrão
SELECT * FROM projeto.dataset.tabela
-- Com crases (obrigatório se o project_id tiver hífens)
SELECT * FROM `meu-projeto.dataset.tabela`
-- Wildcard para múltiplas tabelas com sufixo (ex: tabelas por ano)
SELECT * FROM `projeto.dataset.eventos_*`
WHERE _TABLE_SUFFIX BETWEEN '2025' AND '2026'EXCEPT e REPLACE em SELECT *
-- Selecionar todas as colunas exceto algumas
SELECT * EXCEPT (coluna_sensivelidade, coluna_interna)
FROM tabela
-- Substituir o valor de uma coluna mantendo as demais
SELECT * REPLACE (UPPER(nome) AS nome, preco * 1.1 AS preco)
FROM tabelaQUALIFY: filtrar sobre window functions
Equivalente ao HAVING mas para funções de janela. Evita a necessidade de uma subquery:
-- Sem QUALIFY (subquery necessária)
SELECT * FROM (
SELECT *, ROW_NUMBER() OVER (PARTITION BY usuario_id ORDER BY ts DESC) AS rn
FROM eventos
) WHERE rn = 1
-- Com QUALIFY (mais limpo)
SELECT *
FROM eventos
QUALIFY ROW_NUMBER() OVER (PARTITION BY usuario_id ORDER BY ts DESC) = 1TABLESAMPLE
Amostra aleatória de uma tabela sem ler todos os dados, útil para exploração barata:
SELECT * FROM tabela_grande TABLESAMPLE SYSTEM (1 PERCENT)FOR SYSTEM_TIME AS OF (time travel)
Consulta o estado histórico de uma tabela em um momento passado (até 7 dias):
-- Como a tabela estava ontem
SELECT * FROM meu_dataset.pedidos
FOR SYSTEM_TIME AS OF TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 1 DAY)
-- Recuperar linhas deletadas acidentalmente
INSERT INTO meu_dataset.pedidos
SELECT * FROM meu_dataset.pedidos
FOR SYSTEM_TIME AS OF '2026-05-04 10:00:00 UTC'
WHERE id NOT IN (SELECT id FROM meu_dataset.pedidos)Tipos ARRAY e STRUCT
São os tipos mais distintos do BigQuery em relação a bancos relacionais tradicionais.
ARRAY
Coluna que contém uma lista de valores do mesmo tipo:
-- Criar array literal
SELECT [1, 2, 3] AS numeros
-- Array de structs (comum em dados de eventos)
SELECT
usuario_id,
ARRAY_AGG(STRUCT(produto_id, quantidade, preco) ORDER BY preco DESC) AS itens
FROM pedidos
GROUP BY usuario_id
-- Funções de array
SELECT
ARRAY_LENGTH(itens) AS qtd_itens,
(SELECT SUM(i.preco) FROM UNNEST(itens) i) AS total
FROM pedidos_com_itensSTRUCT
Registro aninhado (equivale a um objeto JSON ou uma linha dentro de uma coluna):
-- Criar struct literal
SELECT STRUCT('Ana' AS nome, 30 AS idade) AS pessoa
-- Acessar campo do struct
SELECT pessoa.nome, endereco.cidade
FROM clientes
-- Struct com array (padrão comum em dados desnormalizados)
SELECT
pedido_id,
cliente.nome,
cliente.endereco.cidade,
item.produto_id
FROM pedidos,
UNNEST(itens) AS item -- expande o array de itensUNNEST
Transforma um ARRAY em linhas. Essencial para trabalhar com dados aninhados:
-- Cada elemento do array vira uma linha
SELECT
p.pedido_id,
item.produto_id,
item.quantidade
FROM pedidos AS p,
UNNEST(p.itens) AS item
-- Com índice (OFFSET)
SELECT
p.pedido_id,
item,
posicao
FROM pedidos AS p,
UNNEST(p.tags) AS item WITH OFFSET AS posicaoFunções exclusivas ou notáveis
Agregação condicional
SELECT
COUNTIF(status = 'ativo') AS ativos,
COUNTIF(status = 'inativo') AS inativos,
AVG(IF(status = 'ativo', valor, NULL)) AS ticket_medio_ativos,
STRING_AGG(nome, ', ' ORDER BY nome LIMIT 5) AS primeiros_5
FROM clientesDatas e timestamps
SELECT
DATE_TRUNC(data_criacao, MONTH) AS mes,
DATE_DIFF(data_entrega, data_criacao, DAY) AS dias_entrega,
EXTRACT(DAYOFWEEK FROM data_criacao) AS dia_semana,
FORMAT_DATE('%Y-%m', data_criacao) AS ano_mes,
TIMESTAMP_MICROS(ts_unix_micros) AS ts,
DATETIME(ts, 'America/Sao_Paulo') AS ts_local
FROM pedidosJSON
-- Extrair campo de coluna JSON (string)
SELECT
JSON_VALUE(payload, '$.usuario.id') AS usuario_id,
JSON_VALUE(payload, '$.evento.tipo') AS tipo_evento,
CAST(JSON_VALUE(payload, '$.valor') AS FLOAT64) AS valor
FROM eventos_raw
-- Parsear JSON para tipo nativo
SELECT JSON_QUERY(payload, '$.itens') AS itens_json
FROM pedidos_rawStrings
SELECT
REGEXP_EXTRACT(email, r'@(.+)$') AS dominio,
REGEXP_REPLACE(texto, r'\s+', ' ') AS texto_normalizado,
SPLIT(categorias, ',') AS array_categorias,
TO_BASE64(dados_binarios) AS base64
FROM tabelaSQL Procedural (scripting)
O BigQuery suporta scripts multi-statement com variáveis, condicionais e loops:
DECLARE total INT64;
DECLARE data_inicio DATE DEFAULT DATE_SUB(CURRENT_DATE(), INTERVAL 30 DAY);
-- Contar registros
SET total = (
SELECT COUNT(*) FROM meu_dataset.pedidos
WHERE DATE(criado_em) >= data_inicio
);
-- Condicional
IF total > 10000 THEN
SELECT FORMAT("Alto volume: %d pedidos", total);
ELSE
SELECT FORMAT("Volume normal: %d pedidos", total);
END IF;
-- Loop com WHILE
DECLARE i INT64 DEFAULT 1;
WHILE i <= 5 DO
INSERT INTO meu_dataset.log VALUES (i, CURRENT_TIMESTAMP());
SET i = i + 1;
END WHILE;Stored Procedures
CREATE OR REPLACE PROCEDURE meu_dataset.processar_pedidos(
IN data_ref DATE,
OUT total_processados INT64
)
BEGIN
DELETE FROM meu_dataset.staging WHERE DATE(ts) = data_ref;
INSERT INTO meu_dataset.pedidos_processados
SELECT * FROM meu_dataset.staging_raw WHERE DATE(ts) = data_ref;
SET total_processados = @@ROW_COUNT;
END;
-- Chamar a procedure
DECLARE resultado INT64;
CALL meu_dataset.processar_pedidos(CURRENT_DATE(), resultado);
SELECT resultado;PIVOT e UNPIVOT
-- PIVOT: linhas para colunas
SELECT *
FROM (
SELECT mes, categoria, receita FROM vendas
)
PIVOT (
SUM(receita)
FOR categoria IN ('eletronicos', 'roupas', 'alimentos')
)
-- UNPIVOT: colunas para linhas
SELECT mes, categoria, receita
FROM vendas_wide
UNPIVOT (
receita FOR categoria IN (eletronicos, roupas, alimentos)
)WITH RECURSIVE
-- Expandir hierarquia de categorias
WITH RECURSIVE hierarquia AS (
SELECT id, nome, parent_id, 0 AS nivel
FROM categorias
WHERE parent_id IS NULL
UNION ALL
SELECT c.id, c.nome, c.parent_id, h.nivel + 1
FROM categorias c
JOIN hierarquia h ON c.parent_id = h.id
)
SELECT * FROM hierarquia ORDER BY nivel, nomeTable-Valued Functions (TVFs)
Funções que retornam uma tabela, reutilizáveis em queries:
CREATE OR REPLACE TABLE FUNCTION meu_dataset.pedidos_por_periodo(
inicio DATE,
fim DATE
)
AS (
SELECT * FROM meu_dataset.pedidos
WHERE DATE(criado_em) BETWEEN inicio AND fim
);
-- Usar a TVF
SELECT * FROM meu_dataset.pedidos_por_periodo('2026-01-01', '2026-03-31')Ver também: gcp-bigquery | gcp-bigquery-otimizacao | gcp-bigquery-cobranca | gcp-bigquery-dataform