SQL · 45 min

Consulte um Banco de Filmes Real

Carregue um dataset real de 4.968 filmes no MySQL e responda perguntas reais com ele: filtre, ordene, junte pessoas a filmes, agregue notas e use subqueries. O complemento prático do curso SQL Essentials.

Problema

Ler sobre `SELECT` não é o mesmo que consultar um banco que revida — com NULLs, milhões de linhas em tabelas relacionadas e perguntas que não cabem num único comando. Neste lab você vai trabalhar com o **dataset de filmes**: quase 5.000 filmes reais, 8.000 pessoas, suas reviews (notas do IMDb, votos) e papéis (quem dirigiu e atuou em quê). Você vai subir o schema, importar os CSVs e então responder seis perguntas reais — do tipo que um time de produto ou de dados de fato faria — usando exatamente as habilidades do curso SQL Essentials: `WHERE`, `ORDER BY`, `JOIN`, `GROUP BY`, `HAVING` e subqueries. No fim você terá escrito queries contra um dataset real e bagunçado e produzido um `queries.sql` que serve de prova de que você sabe transformar uma pergunta em SQL.

Objetivos

Pré-requisitos

O que você vai construir

Você não vai construir um app — vai construir fluência. Partindo de CSVs crus, você vai subir
um banco relacional real e então escrever seis queries que respondem perguntas reais sobre filmes:
os filmes mais bem avaliados, os diretores mais prolíficos, a nota média por país e mais. Cada
passo empilha mais uma habilidade de SQL sobre a anterior.

O dataset em uma imagem

Quatro tabelas, conectadas por IDs:

Tabela Linhas O que guarda Colunas-chave
films 4.968 uma linha por filme id, title, release_year, country, language, duration, gross, budget
people 8.397 diretores e atores id, name, birthdate
reviews 4.968 uma linha com as notas de cada filme film_id → films, imdb_score, num_votes
roles 19.791 quem fez o quê num filme film_id → films, person_id → people, role (director ou actor)

Os relacionamentos são todo o ponto de um banco relacional:

Para responder "qual diretor tem a maior nota média no IMDb" você precisa viajar people → roles → films → reviews. Esse caminho é para o que servem os joins.

Um aviso sobre dados reais: NULLs

Isso é dado real, então é incompleto. gross, budget, language e birthdate estão faltando
(NULL) em muitas linhas. Não é um bug — é o estado normal de dados de produção, e tratá-lo
corretamente (com IS NOT NULL, e sabendo que AVG ignora NULLs) é exatamente a habilidade que
este lab treina.

Como trabalhar neste lab

Rode cada query você mesmo contra sua própria cópia do banco e leia o resultado real. Salve cada
query final num arquivo chamado queries.sql à medida que avança — esse arquivo é seu deliverable
no Passo 6.

Passos

  1. Suba o banco e importe os CSVs

    Antes de consultar qualquer coisa, você precisa dos dados no MySQL.

    Crie o schema

    Descompacte o films-dataset.zip e rode o schema.sql incluído. Pela CLI mysql:

    mysql -u root -p < schema.sql
    

    Isso cria o banco films e as quatro tabelas com suas chaves primárias e estrangeiras. As
    foreign keys fazem a ordem de importação importar: uma linha de roles referencia uma de
    films e uma de people, então essas precisam existir antes. Importe nesta ordem:
    films → people → reviews → roles.

    Importe os dados

    Escolha um método:

    Opção A — LOAD DATA (rápido, CLI). Habilite o local infile e carregue cada arquivo:

    USE films;
    LOAD DATA LOCAL INFILE 'films.csv'   INTO TABLE films
      FIELDS TERMINATED BY ',' ENCLOSED BY '"' LINES TERMINATED BY '\n' IGNORE 1 LINES;
    LOAD DATA LOCAL INFILE 'people.csv'  INTO TABLE people
      FIELDS TERMINATED BY ',' ENCLOSED BY '"' LINES TERMINATED BY '\n' IGNORE 1 LINES;
    LOAD DATA LOCAL INFILE 'reviews.csv' INTO TABLE reviews
      FIELDS TERMINATED BY ',' ENCLOSED BY '"' LINES TERMINATED BY '\n' IGNORE 1 LINES;
    LOAD DATA LOCAL INFILE 'roles.csv'   INTO TABLE roles
      FIELDS TERMINATED BY ',' ENCLOSED BY '"' LINES TERMINATED BY '\n' IGNORE 1 LINES;
    

    (Se der ERROR 3948, inicie o cliente com mysql --local-infile=1 e rode
    SET GLOBAL local_infile = 1;.)

    Opção B — assistente do Workbench. Botão direito em cada tabela → Table Data Import Wizard
    → escolha o CSV correspondente. Faça na ordem films → people → reviews → roles.

    Verifique a carga

    Nunca confie numa importação que você não conferiu. Confirme as contagens:

    SELECT
      (SELECT COUNT(*) FROM films)   AS films,
      (SELECT COUNT(*) FROM people)  AS people,
      (SELECT COUNT(*) FROM reviews) AS reviews,
      (SELECT COUNT(*) FROM roles)   AS roles;
    

    Você deve ver 4968, 8397, 4968, 19791. Se alguma contagem estiver errada, reimporte aquela tabela.

    Entregável deste passo: o resultado das contagens provando que as quatro tabelas carregaram.

  2. Filtre e ordene: WHERE e ORDER BY

    Comece com uma única tabela, films, e responda perguntas estreitando e ordenando linhas.

    Pergunta 1 — os filmes recentes mais longos

    Liste os 10 filmes mais longos lançados de 2000 em diante, do mais longo primeiro, mostrando título, ano e duração.

    SELECT title, release_year, duration
    FROM films
    WHERE release_year >= 2000
      AND duration IS NOT NULL
    ORDER BY duration DESC
    LIMIT 10;
    

    Leia o que cada cláusula faz:

    • WHERE release_year >= 2000 mantém só filmes recentes.
    • AND duration IS NOT NULL descarta linhas sem duração — crítico, porque senão o
      ORDER BY ordenaria os NULLs junto e te enganaria. NULL é "desconhecido", não zero.
    • ORDER BY duration DESC põe o mais longo primeiro; LIMIT 10 pega a fatia do topo.

    Experimente as variações

    Pratique os outros operadores de filtro do curso:

    -- IN: filmes de um conjunto de países
    SELECT title, country FROM films
    WHERE country IN ('Brazil', 'France', 'Japan')
    ORDER BY country, title;
    
    -- BETWEEN: filmes dos anos 1990
    SELECT title, release_year FROM films
    WHERE release_year BETWEEN 1990 AND 1999
    ORDER BY release_year;
    
    -- LIKE: filmes cujo título começa com "The Lord"
    SELECT title FROM films
    WHERE title LIKE 'The Lord%';
    

    Entregável deste passo: salve a query da Pergunta 1 e seu resultado top-10 no queries.sql.

  3. Conecte tabelas: INNER JOIN

    A nota de um filme mora em reviews, não em films. Para usar as duas, você junta (join) elas pela chave compartilhada.

    Pergunta 2 — os filmes mais bem avaliados

    Mostre os 10 filmes mais bem avaliados (título, ano, nota IMDb), mas só os com peso real de
    audiência — ao menos 50.000 votos — para que um único voto 10/10 não lidere a lista.

    SELECT f.title, f.release_year, r.imdb_score, r.num_votes
    FROM films AS f
    INNER JOIN reviews AS r ON r.film_id = f.id
    WHERE r.num_votes >= 50000
    ORDER BY r.imdb_score DESC, r.num_votes DESC
    LIMIT 10;
    

    As ideias-chave:

    • INNER JOIN reviews AS r ON r.film_id = f.id cola cada filme à sua linha de review pela foreign key.
    • Aliases de tabela (f, r) encurtam a query e deixam f.title vs r.imdb_score sem ambiguidade.
    • INNER significa que só aparecem linhas que casam dos dois lados — filmes sem linha de review somem.
    • O filtro num_votes >= 50000 é um instinto real de analista: proteja-se de ruído de amostra pequena.

    Junte três tabelas — quem dirigiu

    Para trazer o nome do diretor, viaje films → roles → people:

    SELECT f.title, p.name AS director, r.imdb_score
    FROM films AS f
    INNER JOIN reviews AS r ON r.film_id = f.id
    INNER JOIN roles   AS rl ON rl.film_id = f.id AND rl.role = 'director'
    INNER JOIN people  AS p  ON p.id = rl.person_id
    WHERE r.num_votes >= 50000
    ORDER BY r.imdb_score DESC
    LIMIT 10;
    

    Repare na condição de join rl.role = 'director' — sem ela você casaria também todo ator,
    multiplicando as linhas. Filtrar dentro do join mantém o grão em "uma linha por filme".

    Entregável deste passo: salve a query da Pergunta 2 e seu resultado no queries.sql.

  4. Resuma com GROUP BY e agregações

    Agregação colapsa muitas linhas numa linha-resumo por grupo. É aqui que o SQL responde
    "quantos", "em média", "o maior".

    Pergunta 3 — nota média por país

    Para cada país, mostre quantos filmes avaliados ele tem e sua nota IMDb média — só países
    com ao menos 30 filmes avaliados, melhor média primeiro.

    SELECT
      f.country,
      COUNT(*)                 AS rated_films,
      ROUND(AVG(r.imdb_score), 2) AS avg_score
    FROM films AS f
    INNER JOIN reviews AS r ON r.film_id = f.id
    WHERE f.country IS NOT NULL
    GROUP BY f.country
    HAVING COUNT(*) >= 30
    ORDER BY avg_score DESC;
    

    A mecânica que confunde:

    • GROUP BY f.country faz uma linha de saída por país distinto; as agregações descrevem cada grupo.
    • Toda coluna não-agregada no SELECT precisa aparecer no GROUP BY (aqui, f.country).
    • AVG(r.imdb_score) ignora notas NULL automaticamente; ROUND(..., 2) deixa legível.
    • HAVING COUNT(*) >= 30 filtra grupos — você não pode usar WHERE para isso, porque o
      WHERE roda antes do agrupamento. WHERE filtra linhas; HAVING filtra grupos. Essa
      distinção é uma pergunta clássica de entrevista.

    Pergunta 4 — os diretores mais prolíficos

    Quem dirigiu mais filmes? Mostre os 10 diretores com mais filmes.

    SELECT p.name AS director, COUNT(*) AS films_directed
    FROM roles AS rl
    INNER JOIN people AS p ON p.id = rl.person_id
    WHERE rl.role = 'director'
    GROUP BY p.id, p.name
    ORDER BY films_directed DESC
    LIMIT 10;
    

    Agrupar por p.id (não só p.name) é o hábito seguro — duas pessoas diferentes podem ter o
    mesmo nome, e o id as mantém distintas.

    Entregável deste passo: salve as Perguntas 3 e 4 com seus resultados no queries.sql.

  5. Responda perguntas de comparação com subqueries

    Algumas perguntas comparam cada linha a um valor que você precisa calcular antes. Esse cálculo
    interno é uma subquery.

    Pergunta 5 — filmes acima da média geral

    Quantos filmes têm nota acima da nota IMDb média do dataset inteiro?

    SELECT COUNT(*) AS above_average_films
    FROM reviews
    WHERE imdb_score > (SELECT AVG(imdb_score) FROM reviews);
    

    O (SELECT AVG(imdb_score) FROM reviews) entre parênteses roda primeiro e retorna um único
    número — a média global. A query externa então compara a nota de cada filme com ele. Você não
    poderia escrever WHERE imdb_score > AVG(imdb_score) direto, porque um agregado não pode ficar
    num WHERE sobre as mesmas linhas — a subquery é como você contorna isso.

    Pergunta 6 — diretores que superam a média

    Liste diretores cuja nota média de filmes está acima da média geral, com sua média e
    contagem de filmes — mais filmes primeiro. (Ao menos 5 filmes, para ter significado.)

    SELECT
      p.name AS director,
      COUNT(*)                       AS films,
      ROUND(AVG(r.imdb_score), 2)    AS avg_score
    FROM roles AS rl
    INNER JOIN people  AS p ON p.id = rl.person_id
    INNER JOIN reviews AS r ON r.film_id = rl.film_id
    WHERE rl.role = 'director'
    GROUP BY p.id, p.name
    HAVING COUNT(*) >= 5
       AND AVG(r.imdb_score) > (SELECT AVG(imdb_score) FROM reviews)
    ORDER BY films DESC, avg_score DESC;
    

    Esta única query usa tudo: um join de três tabelas, GROUP BY, dois agregados, um HAVING que
    combina um filtro de contagem com uma subquery, e um ORDER BY de múltiplas chaves. Se você
    consegue lê-la e explicar cada cláusula, você domina o núcleo do SQL.

    Entregável deste passo: salve as Perguntas 5 e 6 com seus resultados no queries.sql.

  6. Entregue: seu queries.sql

    Empacote as seis queries num único arquivo reproduzível e entregue.

    Monte sua entrega

    Crie um arquivo queries.sql com suas seis respostas, cada uma sob um comentário de cabeçalho,
    com o resultado colado abaixo como comentário. Estrutura:

    -- Lab do Dataset de Filmes — <seu nome>
    
    -- Q1: 10 filmes mais longos lançados de 2000 em diante
    SELECT title, release_year, duration
    FROM films
    WHERE release_year >= 2000 AND duration IS NOT NULL
    ORDER BY duration DESC
    LIMIT 10;
    /* Resultado (top 3):
       title | release_year | duration
       ...
    */
    
    -- Q2: top 10 filmes mais bem avaliados com >= 50000 votos
    -- ... query + resultado ...
    
    -- Q3: nota IMDb média por país (>= 30 filmes avaliados)
    -- Q4: top 10 diretores mais prolíficos
    -- Q5: quantos filmes têm nota acima da média geral
    -- Q6: diretores cuja média supera a média geral (>= 5 filmes)
    

    Entregue

    git init
    git add queries.sql
    git commit -m "Lab de SQL — queries do dataset de filmes"
    # faça push para um repo público ou gist, depois entregue essa URL
    

    Entregue a URL do repositório ou gist como seu deliverable do lab.

    Critério de submissão (autoverificação)

    • A checagem de contagem do Passo 1 mostra 4968 / 8397 / 4968 / 19791
    • As seis queries rodam sem erro numa importação nova do dataset
    • Q2 e Q6 usam JOIN entre pelo menos três tabelas corretamente
    • Q3 usa GROUP BY + HAVING (não WHERE) para filtrar grupos
    • Q5 e Q6 usam uma subquery para a comparação com a média
    • Cada query tem seu resultado real colado abaixo (não inventado)

    O que vem a seguir

    Você consultou um banco relacional real de ponta a ponta. No Backend Developer Path, isso
    vira a camada de dados de uma API que você constrói e faz deploy — as mesmas tabelas films,
    agora atrás de endpoints HTTP que você projeta.