40 Queries SQL Avançadas

- Published on

Fala, aspirante a Data Analyst! Se você acha que SQL é só decorar SELECT * FROM tabela, a gente precisa conversar. O mercado não quer alguém que decore comandos, quer alguém que resolva problemas reais.
O SQL fica muito mais fácil (e divertido) quando você para de tentar decorar a sintaxe e começa a pensar em como resolver desafios do dia a dia. Pensando nisso, compilei uma lista matadora com 40 queries avançadas que cobrem os principais cenários do trabalho real.
O que você vai aprender:
- Subqueries e Joins: O pão com manteiga de qualquer entrevista.
- GROUP BY e HAVING: Para agregar dados e filtrar grupos.
- Window Functions: O nível sênior do SQL.
- Ranking e Top-N: Achar os "Top 3", "Segundo maior", etc.
- Registros Duplicados: Como limpar a sujeira.
- Análise de Salários e Funcionários: Clássicos de RH.
- Análise de Datas e Meses: Tendências e sazonalidade.
- Análise de Clientes e Pedidos: O coração do e-commerce.
Essas queries são o filtro para vagas de Data Analyst, Data Engineer e Desenvolvedor. Vou destrinchar alguns exemplos práticos para você sentir o gostinho.
Nível 1: O Básico que Diferencia
01. Como encontrar o segundo maior salário?
Lógica: Pegue o maior salário, mas que seja menor que o maior salário geral.
SELECT MAX(salary) AS second_highest_salary
FROM employees
WHERE salary < (SELECT MAX(salary) FROM employees);
Output:
| second_highest_salary |
|---|
| 90000 |
02. Funcionários que entraram no mesmo mês/ano que o gerente
Lógica: Self Join na tabela de funcionários e comparação de datas.
SELECT e.employee_id, e.name
FROM employees e
JOIN employees m ON e.manager_id = m.employee_id
WHERE MONTH(e.join_date) = MONTH(m.join_date)
AND YEAR(e.join_date) = YEAR(m.join_date);
03. Contar nomes que começam e terminam com a mesma letra
Lógica: Use LEFT e RIGHT para comparar a primeira e a última letra.
SELECT COUNT(*) AS total_employees
FROM employees
WHERE LEFT(name, 1) = RIGHT(name, 1);
04. Departamento com o maior salário médio
Lógica: Agrupe por departamento, calcule a média e ordene decrescente.
SELECT department_id, AVG(salary) AS avg_salary
FROM employees
GROUP BY department_id
ORDER BY avg_salary DESC
LIMIT 1;
Output:
| department_id | avg_salary |
|---|---|
| 1 | 85000 |
Nível 2: Subqueries e Window Functions
05. Funcionários que ganham mais que a média do seu departamento
Lógica: Subquery correlacionada. A média é calculada por departamento.
SELECT e.employee_id, e.name, e.salary, e.department_id
FROM employees e
WHERE e.salary > (SELECT AVG(salary)
FROM employees
WHERE department_id = e.department_id);
06. Top 3 funcionários mais bem pagos
Lógica: Ordenação simples e limite.
SELECT employee_id, name, salary
FROM employees
ORDER BY salary DESC
LIMIT 3;
07. Encontrar e-mails duplicados
Lógica: Agrupe por e-mail e conte. Se a contagem for maior que 1, é duplicado.
SELECT email, COUNT(*) AS total_count
FROM employees
GROUP BY email
HAVING COUNT(*) > 1;
08. Salário acumulado ordenado por salário
Lógica: Window Function SUM() OVER().
SELECT employee_id, name, salary,
SUM(salary) OVER (ORDER BY salary) AS total_salary
FROM employees
ORDER BY salary;
Nível 3: Análise de Departamentos e Duplicatas
09. Funcionários que ganham mais que a média do seu departamento
Lógica: Similar à query 05, mas com foco no ID do departamento.
SELECT e.employee_id, e.name, e.salary, e.department_id
FROM employees e
WHERE e.salary > (
SELECT AVG(salary)
FROM employees
WHERE department_id = e.department_id
);
| employee_id | name | salary | department_id |
|---|---|---|---|
| 101 | John | 90000 | 1 |
| 105 | Alice | 75000 | 2 |
| 109 | Robert | 85000 | 1 |
10. Encontrar registros duplicados baseados em uma coluna
Lógica: GROUP BY + HAVING.
SELECT email, COUNT(*) AS count
FROM employees
GROUP BY email
HAVING COUNT(*) > 1;
Output:
| count | |
|---|---|
| john@example.com | 2 |
| alice@example.com | 2 |
11. Top 3 funcionários mais bem pagos por departamento
Lógica: Use ROW_NUMBER() com PARTITION BY.
SELECT employee_id, name, salary, department_id
FROM (
SELECT e.*,
ROW_NUMBER() OVER (PARTITION BY department_id ORDER BY salary DESC) AS rn
FROM employees e
) t
WHERE rn <= 3
ORDER BY department_id, salary DESC;
Output:
| employee_id | name | salary | department_id |
|---|---|---|---|
| 101 | John | 90000 | 1 |
| 109 | Robert | 85000 | 1 |
| 110 | Michael | 70000 | 1 |
| 105 | Alice | 75000 | 2 |
| 107 | Sophia | 65000 | 2 |
| 111 | David | 60000 | 2 |
Linhas 3-5: A Subquery (O código que está dentro dos parênteses)
Esta parte cria uma tabela temporária (que o código apelidou de t). Ela faz o seguinte:
ROW_NUMBER() OVER (...): Esta é um window function. Ela cria uma numeração de linhas (1, 2, 3...) baseada em uma regra.PARTITION BY department_id: Diz para o banco de dados "separar" os funcionários em grupos por departamento. A contagem de linhas vai recomeçar do 1 para cada departamento diferente.ORDER BY salary DESC: Dentro de cada departamento, os funcionários são ordenados do maior para o menor salário.AS rn: Dá o nome dern(abreviação de Row Number) para essa coluna de numeração. O funcionário com o maior salário do departamento ganha orn = 1, o segundo maiorrn = 2, e assim por diante.
A Query Principal (O código de fora)
Agora que cada funcionário tem um "ranking" de salário dentro do seu departamento, a parte de fora filtra o resultado:
WHERE rn <= 3: Filtra para trazer apenas as linhas onde o ranking é menor ou igual a 3. Ou seja, apenas o top 3 de cada departamento.SELECT ...: Esconde a coluna de ranking (rn) e mostra apenas as informações limpas do funcionário: ID, nome, salário e departamento.ORDER BY department_id, salary DESC: Organiza o resultado final na sua tela, agrupando por departamento e mostrando os maiores salários primeiro.
12. Departamentos com mais de 5 funcionários
Lógica: GROUP BY + HAVING.
SELECT department_id, COUNT(*) AS total_employees
FROM employees
GROUP BY department_id
HAVING COUNT(*) > 5;
Output:
| department_id | total_employees |
|---|---|
| 1 | 8 |
| 2 | 6 |
| 3 | 7 |
Nível 4: Joins e Filtros Específicos
13. Funcionários que não pertencem a nenhum departamento
Lógica: LEFT JOIN e checar se o ID do departamento é NULL.
SELECT e.employee_id, e.name
FROM employees e
LEFT JOIN departments d
ON e.department_id = d.department_id
WHERE d.department_id IS NULL;
14. Salário total pago por cada departamento
Lógica: SUM com GROUP BY.
SELECT d.department_id, d.department_name,
SUM(e.salary) AS total_salary
FROM departments d
LEFT JOIN employees e
ON d.department_id = e.department_id
GROUP BY d.department_id, d.department_name;
Output:
| department_id | department_name | total_salary |
|---|---|---|
| 1 | Development | 240000 |
| 2 | HR | 140000 |
| 3 | Sales | 215000 |
| 4 | Support | 125000 |
Linhas 3-5: As tabelas e o relacionamento
departments deemployees e: O código define apelidos (dee) para as tabelas de departamentos e funcionários, facilitando a escrita.LEFT JOIN: Esta é a parte mais importante do relacionamento. OLEFT JOINgarante que todos os departamentos apareçam no resultado, mesmo que não haja nenhum funcionário cadastrado neles. Se um departamento estiver vazio, ele ainda será listado.ON d.department_id = e.department_id: É a regra de conexão. O banco de dados cruza as tabelas onde o código do departamento for igual nas duas.
Linha 6: O agrupamento
- O
GROUP BYjunta as linhas de funcionários que pertencem ao mesmo departamento. Em vez de ver uma lista com cada funcionário individual, o banco de dados "esmaga" os dados para criar uma única linha por departamento.
Linhas 1-2: O cálculo e a exibição
SUM(e.salary) AS total_salary: Como os dados foram agrupados, esta função de agregação (SUM) soma o salário de todos os funcionários daquele grupo. O resultado ganha o nome de total_salary. Se um departamento não tiver funcionários (por causa doLEFT JOIN), o resultado dessa soma seráNULL(ou zero, dependendo do banco).- As outras colunas apenas exibem o ID e o nome do departamento correspondente.
15. Funcionários que ganham mais que a média da empresa
Lógica: Subquery simples.
SELECT employee_id, name, salary
FROM employees
WHERE salary > (SELECT AVG(salary) FROM employees);
Output:
| employee_id | name | salary |
|---|---|---|
| 101 | John | 90000 |
| 103 | Robert | 85000 |
| 105 | Alice | 75000 |
| 106 | Michael | 70000 |
16. Contagem de funcionários por mês de entrada
Lógica: DATE_FORMAT e GROUP BY.
SELECT DATE_FORMAT(join_date, '%Y-%m') AS join_month,
COUNT(*) AS total_employees
FROM employees
GROUP BY join_month
ORDER BY join_month;
Output:
| join_month | total_employees |
|---|---|
| 2018-06 | 2 |
| 2019-04 | 3 |
| 2020-01 | 2 |
| 2020-03 | 1 |
Ou se preferir exibir o nome do mês:
SELECT DATE_FORMAT(join_date, '%M/%Y') AS join_month
Output: "January/2026" (se o banco estiver em inglês)
Nível 5: Análise de Vendas e Clientes
17. Clientes que fizeram pedidos mas nunca devolveram
Lógica: EXISTS e NOT EXISTS.
SELECT c.customer_id, c.customer_name
FROM customers c
WHERE EXISTS (SELECT 1 FROM orders o
WHERE o.customer_id = c.customer_id)
AND NOT EXISTS (SELECT 1 FROM returns r
WHERE r.customer_id = c.customer_id);
Output:
| customer_id | customer_name |
|---|---|
| 101 | John Doe |
| 103 | Michael Johnson |
| 107 | Chris Martin |
| 110 | Olivia Brown |
Linhas 1-2: A consulta principal
- O banco de dados vai buscar o ID e o nome de todos os clientes da tabela
customers(apelidada dec), mas eles precisam passar por dois filtros lógicos noWHERE.
Linhas 3-4: Já comprou?
- O operador
EXISTScheca se a subquery interna retorna alguma linha. - Para cada cliente da tabela principal, o banco olha a tabela de pedidos (
orders o). Se encontrar pelo menos um pedido associado ao ID desse cliente (o.customer_id = c.customer_id), a condição é verdadeira. - Nota: O
SELECT 1é apenas uma convenção de performance. Não importa o dado retornado, apenas se a linha existe ou não.
Linhas 5-6: Nunca devolveu?
- O
NOT EXISTSfunciona ao contrário: ele garante que a subquery interna seja vazia. - O banco olha para a tabela de devoluções (
returns r). Se ele encontrar qualquer registro de devolução ligado ao cliente (r.customer_id = c.customer_id), esse cliente é descartado do resultado.
18. Crescimento mês a mês (MoM) das vendas
Lógica: LAG() para pegar o valor do mês anterior.
SELECT sale_month,
total_sales,
LAG(total_sales) OVER (ORDER BY sale_month) AS prev_month_sales,
ROUND(((total_sales - LAG(total_sales) OVER (ORDER BY sale_month))
/ LAG(total_sales) OVER (ORDER BY sale_month)) * 100, 2) AS growth_pct
FROM monthly_sales;
Output:
| sale_month | total_sales | prev_month_sales | growth_pct |
|---|---|---|---|
| 2024-01 | 120000 | NULL | NULL |
| 2024-02 | 150000 | 120000 | 25.00 |
| 2024-03 | 180000 | 150000 | 20.00 |
| 2024-04 | 210000 | 180000 | 16.67 |
Linha 6: A origem dos dados
- A query busca as informações diretamente de uma tabela chamada
monthly_sales, que já possui o mês (sale_month) e o valor total vendido naquele período (total_sales).
Linha 3: Buscando o valor do mês anterior
LAG(): É um window function utilizada para "olhar para trás". Ela captura o valor de uma coluna da linha anterior.OVER (ORDER BY sale_month): Garante que o banco de dados organize as linhas em ordem cronológica antes de olhar para trás. Assim, oLAGvai pegar exatamente a venda do mês anterior e exibi-la na colunaprev_month_sales.
Linhas 4-5: Calculando a porcentagem de crescimento
Esta fórmula calcula a variação percentual. A lógica matemática pura é:
No SQL, ela foi escrita repetindo a função LAG:
ROUND(((total_sales - LAG(total_sales) OVER (ORDER BY sale_month))
/ LAG(total_sales) OVER (ORDER BY sale_month)) * 100, 2) AS growth_pct
- Parte de cima da fração:
total_sales - LAG(...)calcula a diferença em dinheiro entre os dois meses (se o resultado for positivo, houve lucro; se for negativo, houve queda). - Divisão: Divide essa diferença pelo valor do mês anterior
/ LAG(...)para encontrar a proporção do crescimento. * 100: Transforma esse número decimal em uma porcentagem (ex:0.15vira15.0).ROUND(..., 2): Arredonda o resultado final para exibição com apenas duas casas decimais (ex:15.33).
Detalhe importante
Na primeira linha do resultado (o primeiro mês histórico da tabela), a coluna prev_month_sales e a coluna growth_pct retornarão o valor NULL (vazio), pois não existe um mês anterior no banco de dados para ser comparado.
19. Top 3 produtos por receita em cada categoria
Lógica: ROW_NUMBER() com PARTITION BY.
SELECT * FROM (
SELECT category_id, product_id, product_name, total_revenue,
ROW_NUMBER() OVER (PARTITION BY category_id
ORDER BY total_revenue DESC) AS rn
FROM product_revenue
) t
WHERE rn <= 3
ORDER BY category_id, total_revenue DESC;
Output:
| category_id | product_id | product_name | total_revenue |
|---|---|---|---|
| 1 | 101 | Laptop | 250000 |
| 1 | 105 | Monitor | 180000 |
| 1 | 110 | Keyboard | 120000 |
| 3 | 205 | Shirt | 95000 |
| 3 | 203 | Jeans | 85000 |
| 3 | 207 | Shoes | 70000 |
Linhas 3-4: O "Ranking" Interno
O código dentro dos parênteses cria uma numeração automática para os produtos da tabela product_revenue:
PARTITION BY category_id: O banco "separa" os produtos em grupos por categoria. A contagem recomeça do 1 sempre que muda a categoria.ORDER BY total_revenue DESC: Dentro de cada categoria, os produtos com o maior faturamento ficam no topo da lista.AS rn: O produto mais lucrativo de cada categoria ganharn = 1, o segundo ganharn = 2, e assim por diante.
Linhas 7-8: O Filtro Externo
Do lado de fora, a consulta final utiliza o apelido t para essa tabela temporária e filtra os dados:
WHERE rn <= 3: Garante que apenas os 3 primeiros colocados de cada categoria sobrevivam ao filtro.ORDER BY category_id, total_revenue DESC: Organiza tudo na sua tela para que os dados fiquem agrupados por categoria, mostrando os produtos mais lucrativos primeiro.
20. Funcionários com salário acima da média do seu departamento
Lógica: Subquery correlacionada.
SELECT e.employee_id, e.name, e.salary, e.department_id
FROM employees e
WHERE e.salary > (
SELECT AVG(salary) FROM employees
WHERE department_id = e.department_id
);
Output:
| employee_id | name | salary | department_id |
|---|---|---|---|
| 102 | Alice | 85000 | 1 |
| 104 | David | 90000 | 1 |
| 109 | Sophia | 75000 | 2 |
| 112 | James | 88000 | 3 |
Nível 6: Clientes e Pedidos
21. Clientes que gastaram mais de 1000 no total
SELECT c.customer_id, c.customer_name,
SUM(o.amount) AS total_spent
FROM customers c
JOIN orders o ON c.customer_id = o.customer_id
GROUP BY c.customer_id, c.customer_name
HAVING SUM(o.amount) > 1000;
Output:
| customer_id | customer_name | total_spent |
|---|---|---|
| 101 | John Doe | 1200 |
| 103 | Michael Johnson | 2300 |
| 107 | Chris Martin | 1550 |
| 110 | Olivia Brown | 2250 |
22. Pedidos que ainda não foram enviados
SELECT order_id, customer_id, order_date, status
FROM orders
WHERE status = 'Pending';
Output:
| order_id | customer_id | order_date | status |
|---|---|---|---|
| 1002 | 101 | 2024-05-12 | Pending |
| 1005 | 103 | 2024-05-18 | Pending |
| 1008 | 107 | 2024-05-21 | Pending |
| 1011 | 110 | 2024-05-25 | Pending |
23. Número de pedidos por cliente
SELECT c.customer_id, c.customer_name,
COUNT(o.order_id) AS total_orders
FROM customers c
LEFT JOIN orders o ON c.customer_id = o.customer_id
GROUP BY c.customer_id, c.customer_name
ORDER BY total_orders DESC;
Output:
| customer_id | customer_name | total_orders |
|---|---|---|
| 103 | Michael Johnson | 7 |
| 101 | John Doe | 5 |
| 110 | Olivia Brown | 4 |
| 107 | Chris Martin | 3 |
| 104 | Emily Davis | 2 |
24. Data do último pedido de cada cliente
SELECT c.customer_id, c.customer_name,
MAX(o.order_date) AS latest_order_date
FROM customers c
JOIN orders o ON c.customer_id = o.customer_id
GROUP BY c.customer_id, c.customer_name
ORDER BY latest_order_date DESC;
Output:
| customer_id | customer_name | latest_order_date |
|---|---|---|
| 103 | Michael Johnson | 2024-05-24 |
| 110 | Olivia Brown | 2024-05-25 |
| 101 | John Doe | 2024-05-22 |
| 107 | Chris Martin | 2024-05-21 |
| 104 | Emily Davis | 2024-05-19 |
Nível 7: RH e Departamentos
25. Maior salário em cada departamento
SELECT d.department_id, d.department_name,
MAX(e.salary) AS highest_salary
FROM departments d
JOIN employees e ON d.department_id = e.department_id
GROUP BY d.department_id, d.department_name
ORDER BY highest_salary DESC;
Output:
| department_id | department_name | highest_salary |
|---|---|---|
| 1 | Development | 95000 |
| 3 | Sales | 90000 |
| 2 | HR | 65000 |
| 4 | Support | 45000 |
26. E-mails duplicados na tabela de funcionários
SELECT email, COUNT(*) AS occurrence
FROM employees
GROUP BY email
HAVING COUNT(*) > 1;
Output:
| occurrence | |
|---|---|
| john.doe@gmail.com | 2 |
| alice.work@gmail.com | 3 |
| mike123@gmail.com | 2 |
| emma.dev@gmail.com | 2 |
27. Segundo maior salário da tabela
SELECT MAX(salary) AS second_highest_salary
FROM employees
WHERE salary < (SELECT MAX(salary) FROM employees);
Output:
| second_highest_salary |
|---|
| 90000 |
28. Funcionários que gerenciam mais de 5 pessoas
SELECT m.employee_id, m.name,
COUNT(e.employee_id) AS team_size
FROM employees m
LEFT JOIN employees e ON m.employee_id = e.manager_id
GROUP BY m.employee_id, m.name
HAVING COUNT(e.employee_id) > 5;
Output:
| employee_id | name | team_size |
|---|---|---|
| 101 | John Doe | 8 |
| 103 | Michael Johnson | 6 |
| 107 | Chris Martin | 7 |
| 110 | Olivia Brown | 9 |
Nível 8: Produtos e Vendas
29. Produtos que nunca foram pedidos
SELECT p.product_id, p.product_name
FROM products p
LEFT JOIN orders o
ON p.product_id = o.product_id
WHERE o.order_id IS NULL;
Output:
| product_id | product_name |
|---|---|
| 104 | Tablet |
| 105 | Smartwatch |
| 106 | Headphones |
30. Valor total de vendas por produto
SELECT p.product_name,
SUM(o.amount) AS total_sales
FROM products p
JOIN orders o ON p.product_id = o.product_id
GROUP BY p.product_name
ORDER BY total_sales DESC;
Output:
| product_name | total_sales |
|---|---|
| Laptop | 135000 |
| Smartphone | 95000 |
| Keyboard | 45000 |
| Mouse | 15000 |
31. Clientes que fizeram mais de 3 pedidos
SELECT c.customer_id, c.customer_name,
COUNT(o.order_id) AS total_orders
FROM customers c
JOIN orders o ON c.customer_id = o.customer_id
GROUP BY c.customer_id, c.customer_name
HAVING COUNT(o.order_id) > 3;
Output:
| customer_id | customer_name | total_orders |
|---|---|---|
| 101 | John Doe | 5 |
| 103 | Michael Johnson | 6 |
| 107 | Chris Martin | 4 |
| 110 | Olivia Brown | 7 |
Linhas 3-4: Cruzando Clientes e Pedidos
JOIN(ouINNER JOIN): Junta a tabela de clientes (customers) com a tabela de pedidos (orders). Ao contrário doLEFT JOINque vimos antes, oINNER JOINgarante que apenas clientes que possuem pedidos entrem no resultado. Clientes que nunca compraram nada ficam de fora.
Linha 2 e 5: Agrupando e Contando
- O
GROUP BYjunta todas as linhas de pedidos de um mesmo cliente em uma única linha de resultado. COUNT(o.order_id) AS total_orders(noSELECT): Conta quantos IDs de pedidos existem para cada cliente agrupado, salvando esse número na colunatotal_orders.
Linha 6: O Filtro do Grupo
HAVING: Funciona exatamente como umWHERE, mas com uma diferença crucial: oWHEREfiltra linhas individuais antes do agrupamento, enquanto oHAVINGfiltra o resultado depois que o agrupamento e as contagens já foram feitos.- Neste caso, ele descarta da tela qualquer cliente que tenha feito 1, 2 ou 3 pedidos, deixando apenas quem fez 4 ou mais.
32. Média de valor dos pedidos por cliente
SELECT c.customer_id, c.customer_name,
ROUND(AVG(o.amount), 2) AS avg_order_amount
FROM customers c
JOIN orders o ON c.customer_id = o.customer_id
GROUP BY c.customer_id, c.customer_name
ORDER BY avg_order_amount DESC;
Output:
| customer_id | customer_name | avg_order_amount |
|---|---|---|
| 103 | Michael Johnson | 31500.75 |
| 110 | Olivia Brown | 28500.50 |
| 101 | John Doe | 22600.33 |
| 107 | Chris Martin | 18750.25 |
Nível 9: Desafios de Categoria
33. Categoria com o maior preço médio de produto
SELECT category_id, AVG(price) AS avg_price
FROM products
GROUP BY category_id
ORDER BY avg_price DESC
LIMIT 1;
Output:
| category_id | avg_price |
|---|---|
| 2 | 12500.00 |
34. Clientes que não fizeram nenhum pedido
SELECT c.customer_id, c.customer_name
FROM customers c
LEFT JOIN orders o
ON c.customer_id = o.customer_id
WHERE o.order_id IS NULL;
Output:
| customer_id | customer_name |
|---|---|
| 104 | Emily Davis |
| 111 | Daniel Wilson |
| 115 | Sophia Taylor |
35. Top 2 clientes por valor total gasto
SELECT c.customer_id, c.customer_name,
SUM(o.amount) AS total_spent
FROM customers c
JOIN orders o ON c.customer_id = o.customer_id
GROUP BY c.customer_id, c.customer_name
ORDER BY total_spent DESC
LIMIT 2;
Output:
| customer_id | customer_name | total_spent |
|---|---|---|
| 101 | John Doe | 48500 |
| 103 | Michael Johnson | 36200 |
36. Pedidos com valor maior que a média geral
SELECT order_id, customer_id, order_date, amount
FROM orders
WHERE amount > (SELECT AVG(amount) FROM orders);
Output:
| order_id | customer_id | order_date | amount |
|---|---|---|---|
| 1003 | 101 | 2024-05-10 | 2500 |
| 1005 | 103 | 2024-05-18 | 3200 |
| 1007 | 107 | 2024-05-21 | 4100 |
| 1010 | 110 | 2024-05-25 | 2800 |
Nível 10: Análise Temporal e Avançada
37. Meses com mais de 10 pedidos
SELECT DATE_FORMAT(order_date, '%Y-%m') AS month,
COUNT(*) AS total_orders
FROM orders
GROUP BY month
HAVING COUNT(*) > 10
ORDER BY month;
Output:
| month | total_orders |
|---|---|
| 2024-04 | 12 |
| 2024-05 | 18 |
| 2024-06 | 15 |
| 2024-07 | 11 |
38. Funcionários que ganham mais que a média salarial
SELECT employee_id, name, salary
FROM employees
WHERE salary > (SELECT AVG(salary) FROM employees);
Output:
| employee_id | name | salary |
|---|---|---|
| 101 | John Doe | 75000 |
| 103 | Michael Johnson | 65000 |
| 105 | Olivia Brown | 70000 |
| 107 | Chris Martin | 62000 |
39. Categorias que não têm produtos
SELECT c.category_id, c.category_name
FROM categories c
LEFT JOIN products p
ON c.category_id = p.category_id
WHERE p.product_id IS NULL;
Output:
| category_id | category_name |
|---|---|
| 5 | Toys |
| 6 | Books |
| 7 | Beauty |
40. Total acumulado de vendas por data de pedido
SELECT order_date,
SUM(amount) AS daily_total,
SUM(SUM(amount)) OVER (ORDER BY order_date) AS running_total
FROM orders
GROUP BY order_date
ORDER BY order_date;
Output:
| order_date | daily_total | running_total |
|---|---|---|
| 2024-05-10 | 2500 | 2500 |
| 2024-05-11 | 3200 | 5700 |
| 2024-05-12 | 4100 | 9800 |
| 2024-05-13 | 2800 | 12600 |
Conclusão
Dominar essas 40 queries te coloca em uma posição de destaque. O segredo é praticar. Crie um banco de dados de testes e rode cada uma delas. Entenda a lógica por trás de cada JOIN, GROUP BY e OVER().
A jornada para se tornar um especialista em dados começa com uma base sólida de SQL. Bora codar! 💻
Fique ligado
Seja um Expert em Growth
Receba insights práticos sobre marketing, dados, performance e tecnologia direto no seu email.