Atenção: por essa edição ser muito antiga não há arquivo PDF para download.
Os artigos dessa edição estão disponíveis somente através do formato HTML.
Clique aqui para ler todos os artigos desta edição
SIMULADO SQL – Respostas e comentários
Na edição anterior propomos um teste sobre a linguagem SQL e um desafio: os dez primeiros que mandassem todas as respostas corretas ganhariam um brinde surpresa e teriam seus nomes publicados no artigo. A iniciativa foi bastante elogiadada e inúmeros emails com respostas foram enviados – os comentários sobre as mensagens recebidas estão no box “Comentários”.
A seguir temos as soluções explicadas para cada questão.
Respostas
1ª Questão:
SELECT T.ANO,T.PERIODO,T.DESCRICAO, A.NOME, D.NO_DISCIPLINA,
SUM(AV.NT_AVALIACAO)/T.NUM_PROVAS MEDIA
FROM ALUNO_TURMA ALT JOIN ALUNO A ON (ALT.CO_ALUNO=A.CO_ALUNO)
JOIN AVALIACAO AV ON (ALT.CO_ALUNO=AV.CO_ALUNO AND
ALT.CO_TURMA=AV.CO_TURMA)
JOIN TURMA T ON (T.CO_TURMA=AV.CO_TURMA)
JOIN DISCIPLINA D ON (AV.CO_DISCIPLINA=D.CO_DISCIPLINA)
WHERE T.ANO=2002 AND ALT.DT_CANCELAMENTO IS NULL
GROUP BY T.ANO,T.PERIODO,T.DESCRICAO,A.CO_ALUNO, A.NOME,D.NO_DISCIPLINA,T.NUM_PROVAS
ORDER BY ANO,PERIODO, NOME
Explicação:
O primeiro passo para resolver esta questão é realizar um join entre as tabelas que possuem as informações solicitadas: aluno_turma, aluno, avaliacao, turma e disciplina. A tabela aluno_turma informa, através do campo dt_cancelamento, se o aluno cancelou ou não a matrícula. Um valor vazio para esse campo significa que a matrícula não foi cancelada.
As notas são recuperadas através da tabela avaliacao. A informação sobre o número de provas, usada no cálculo da média, está armazenada na tabela turma (campo num_provas). Para o cálculo da média, somamos as notas obtidas e dividimos pelo número de provas que a turma em questão deve realizar:
SUM(AV.NT_AVALIACAO)/T.NUM_PROVAS
Repare que o comando Group By faz a quebra pelo campo no_disciplina, permitindo que o cálculo da média seja efetuado para cada disciplina. Se o agrupamento não fosse realizado, a média seria calculada sobre todas as disciplinas. Observe também que os campos Ano, Periodo, Descricao e Nome foram passados como parâmetro do Group By apenas para permitir sua exibição através do comando SELECT:
SELECT T.ANO,T.PERIODO,T.DESCRICAO, A.NOME, D.NO_DISCIPLINA
Outro detalhe é que a presença do campo Num_provas no Group By permite o seu uso no cálculo da média (executado no comando SELECT).
Para filtragem do ano foi utilizada a sentença T.ANO=2002. Neste caso, não é correto utilizar o campo dt_matricula, da tabela aluno_turma, visto que o aluno pode realizar a matrícula antes do início da turma. Por exemplo, o aluno pode ter realizado a matrícula em dezembro de 2001 (ex: dt_matricula = ‘10/12/2001’) e a turma ter sido efetivamente iniciada em janeiro de 2002.
2ª Questão:
SELECT T.PERIODO, T.CO_TURMA,
(SELECT COUNT(*)
FROM ALUNO_TURMA AL
WHERE AL.CO_TURMA = T.CO_TURMA) INICIARAM,
(SELECT COUNT(*)
FROM ALUNO_TURMA AL
WHERE AL.CO_TURMA = T.CO_TURMA AND
DT_CANCELAMENTO IS NOT NULL) CANCELARAM
FROM TURMA T
WHERE T.ANO=2002
ORDER BY T.PERIODO
Explicação:
Este exemplo foge do padrão select atributo from tabela where condição. A técnica utilizada nesta questão é o uso de subconsultas.
Para cada turma do ano de 2002 (T.ANO=2002), realizamos duas subconsultas:
1) (SELECT COUNT(*)
FROM ALUNO_TURMA AL
WHERE AL.CO_TURMA=T.CO_TURMA) INICIARAM
Esse comando calcula a quantidade de alunos da turma retornada pela consulta principal. Observe na sentença WHERE AL.CO_TURMA = T.CO_TURMA que apenas os alunos da turma em questão serão selecionados.
2) (SELECT COUNT(*)
FROM ALUNO_TURMA AL
WHERE AL.CO_TURMA = T.CO_TURMA AND
DT_CANCELAMENTO IS NOT NULL) CANCELARAM
A diferença da segunda subconsulta é a sentença DT_CANCELAMENTO IS NOT NULL, que seleciona apenas os alunos que cancelaram a participação na turma.
3ª Questão:
CREATE VIEW VIEW_MEDIA AS
SELECT T.ANO,T.PERIODO,T.CO_TURMA,T.DESCRICAO, A.CO_ALUNO,A.NOME, D.NO_DISCIPLINA,
SUM(NT_AVALIACAO)/T.NUM_PROVAS MEDIA
FROM ALUNO_TURMA ALT
JOIN ALUNO A ON (ALT.CO_ALUNO=A.CO_ALUNO)
JOIN AVALIACAO AV ON (ALT.CO_ALUNO=AV.CO_ALUNO AND
ALT.CO_TURMA=AV.CO_TURMA)
JOIN TURMA T ON (ALT.CO_TURMA=T.CO_TURMA)
JOIN DISCIPLINA D ON (AV.CO_DISCIPLINA=D.CO_DISCIPLINA)
GROUP BY T.ANO,T.PERIODO,T.CO_TURMA,T.DESCRICAO,A.CO_ALUNO,
A.NOME,D.NO_DISCIPLINA,T.NUM_PROVAS
SELECT VM.DESCRICAO,VM.CO_TURMA, VM.CO_ALUNO, VM.NOME, AVG(MEDIA)MG
FROM VIEW_MEDIA VM
WHERE VM.ANO=2002 AND VM.PERIODO=1
GROUP BY VM.DESCRICAO, VM.CO_TURMA,VM.CO_ALUNO, VM.NOME
HAVING AVG(MEDIA)>
(SELECT SUM(MEDIA)/COUNT(*)
FROM VIEW_MEDIA VM
WHERE VM.CO_TURMA=01)
Explicação:
O conteúdo da view é semelhante ao resultado da questão 1. Ela retorna a média do aluno para cada disciplina cursada em uma determinada turma.
No comando SELECT utilizamos a sentença VM.ANO=2002 para filtrar as turmas de 2002. Observe que existe um agrupamento por aluno, permitindo que a média geral seja calculada através da função AVG(MEDIA). Para exemplificar, observe um retorno imaginário da view em questão:
ANO |
PERIODO |
CO_TURMA |
DESCRICAO |
CO_ALUNO |
NOME |
NO_DISCIPLINA |
MEDIA |
2003 |
1 |
2 |
turma 1/2003 |
1 |
Reinaldo |
Matemática |
8 |
2003 |
1 |
2 |
turma 1/2003 |
1 |
Reinaldo |
História |
6 |
Veja agora o efeito do agrupamento por aluno, num retorno imaginário para o comando SELECT:
DESCRICAO |
CO_TURMA |
CO_ALUNO |
NOME |
MEDIA |
turma 1/2003 |
2 |
1 |
Reinaldo |
7 |
Temos ainda que o aluno deve possuir média geral maior do que a média geral da turma de código 01. Lembrando que a média geral de uma turma é a média de todas as notas obtidas. O comando HAVING realiza esse filtro, fazendo uso também da view Media:
HAVING AVG(MEDIA)>
(SELECT SUM(MEDIA)/COUNT(*)
FROM VIEW_MEDIA VM
WHERE VM.CO_TURMA=01)
4ª Questão:
SELECT A.NOME,
(SELECT COUNT(*)
FROM FREQUENCIA F
WHERE F.CO_ALUNO=A.CO_ALUNO AND F.FREQUENCIA='S') PRESENCAS,
(SELECT COUNT(*)
FROM FREQUENCIA F
WHERE F.CO_ALUNO=A.CO_ALUNO AND F.FREQUENCIA='N') FALTAS
FROM ALUNO A
ORDER BY A.NOME
Explicação:
Essa questão é semelhante a número 2. Note que as subconsultas estão ligadas à SELECT principal através da condição WHERE F.CO_ALUNO=A.CO_ALUNO.
Uma observação: o relatório gerado por essa consulta inclui todos os alunos cadastrados, independente de eles terem realizado alguma matrícula. Se a aplicação possuir muitos alunos que nunca se matricularam, podemos modificar o código da seguinte forma:
SELECT DISTINCT A.NOME,
(SELECT COUNT(*)
FROM FREQUENCIA F
WHERE F.CO_ALUNO=A.CO_ALUNO AND F.FREQUENCIA='S') PRESENCAS,
(SELECT COUNT(*)
FROM FREQUENCIA F
WHERE F.CO_ALUNO=A.CO_ALUNO AND F.FREQUENCIA='N') FALTAS
FROM ALUNO A
JOIN ALUNO_TURMA ALT ON (A.CO_ALUNO = ALT.CO_ALUNO)
ORDER BY A.NOME
Esse novo join gera um custo, que deve ser analisado tendo em vista o cenário da aplicação.
5ª Questão:
SELECT VM.DESCRICAO,VM.CO_TURMA, VM.CO_ALUNO, VM.NOME, AVG(MEDIA) MG
FROM VIEW_MEDIA VM
WHERE VM.ANO=2002
GROUP BY VM.DESCRICAO, VM.CO_TURMA,VM.CO_ALUNO, VM.NOME
Explicação:
Essa consulta é apenas uma simplificação da questão 3. Observe o uso da view Media e o agrupamento por aluno, que permite o cálculo da média geral.
6ª Questão:
SELECT ALT.CO_ALUNO, A.NOME, 'SIM' NOVOALUNO
FROM ALUNO_TURMA ALT
JOIN ALUNO A ON (ALT.CO_ALUNO=A.CO_ALUNO)
JOIN TURMA T ON (ALT.CO_TURMA=T.CO_TURMA)
WHERE ALT.CO_ALUNO NOT IN
(SELECT CO_ALUNO
FROM ALUNO_TURMA AT
JOIN TURMA TU ON (AT.CO_TURMA=TU.CO_TURMA)
WHERE TU.ANO <> 2003 AND AT.CO_ALUNO = A.CO_ALUNO)
AND T.ANO='2003'
UNION
SELECT ALT.CO_ALUNO, A.NOME, 'NÃO' NOVOALUNO
FROM ALUNO_TURMA ALT
JOIN ALUNO A ON (ALT.CO_ALUNO=A.CO_ALUNO)
JOIN TURMA T ON (ALT.CO_TURMA=T.CO_TURMA)
WHERE ALT.CO_ALUNO IN
(SELECT CO_ALUNO FROM ALUNO_TURMA ALT
JOIN TURMA TU ON (ALT.CO_TURMA=TU.CO_TURMA)
WHERE TU.ANO <> 2003 AND ALT.CO_ALUNO = A.CO_ALUNO)
AND T.ANO='2003'
Explicação:
Como temos dois cenários distintos (aluno novo e aluno antigo) o recomendável é utilizar a cláusula UNION. No primeiro SELECT recuperamos os alunos novos, que são aqueles que não possuem matrícula anterior ao ano de 2003. Esse filtro é realizado através de NOT IN:
WHERE ALT.CO_ALUNO NOT IN
(SELECT CO_ALUNO
FROM ALUNO_TURMA AT
JOIN TURMA TU ON (AT.CO_TURMA=TU.CO_TURMA)
WHERE TU.ANO <> 2003 AND AT.CO_ALUNO = A.CO_ALUNO)
No segundo SELECT, os alunos antigos são representados por aqueles que efetuaram matricula em alguma turma anterior a 2003:
WHERE ALT.CO_ALUNO IN
(SELECT CO_ALUNO FROM ALUNO_TURMA ALT
JOIN TURMA TU ON (ALT.CO_TURMA=TU.CO_TURMA)
WHERE TU.ANO <> 2003 AND ALT.CO_ALUNO = A.CO_ALUNO)
7ª Questão:
SELECT A.NOME
FROM ALUNO A
WHERE NOT EXISTS (SELECT CO_ALUNO FROM ALUNO_TURMA WHERE ALUNO_TURMA.CO_ALUNO=A.CO_ALUNO)
Explicação:
A cláusula NOT EXISTS garante que apenas os alunos que não possuem registro equivalente na subconsulta serão exibidos. O filtro na subconsulta é importante para garantir performance, pois retorna apenas as ocorrências do aluno selecionado na consulta principal:
WHERE ALUNO_TURMA.CO_ALUNO=A.CO_ALUNO.
8ª Questão:
SELECT AL.NOME, AL.DT_NASCIMENTO
FROM ALUNO_TURMA ALT
JOIN TURMA T ON (ALT.CO_TURMA=T.CO_TURMA)
JOIN ALUNO AL ON (ALT.CO_ALUNO=AL.CO_ALUNO)
WHERE T.ANO='2003' AND AL.CO_ESTADOCIVIL='S'
UNION
SELECT VM.NOME , AL.DT_NASCIMENTO
FROM VIEW_MEDIA VM
JOIN ALUNO AL ON (VM.CO_ALUNO=AL.CO_ALUNO )
WHERE ANO='2002' AND PERIODO=2 AND AL.CO_ESTADOCIVIL='S' AND
NO_DISCIPLINA='BIOLOGIA' AND MEDIA >8
Explicação:
A cláusula UNION foi utilizada para mesclar as informações de alunos inseridos em dois contextos diferentes. Mesmo que tenhamos algum aluno presente nos dois casos, ele aparecerá apenas uma vez na listagem, visto que UNION elimina as repetições. Uma dica: se você precisar incluir as repetições, utilize UNION ALL.
9ª Questão:
SELECT A.NOME
FROM ALUNO_TURMA ALT
JOIN ALUNO A ON (ALT.CO_ALUNO = A.CO_ALUNO)
JOIN TURMA T ON (ALT.CO_TURMA = T.CO_TURMA)
WHERE A.CO_ESTADOCIVIL='C' AND
T.ANO='2003' AND ALT.DT_CANCELAMENTO IS NULL
AND EXISTS
( SELECT F.CO_ALUNO
FROM FREQUENCIA F
JOIN TURMA T ON (F.CO_TURMA=T.CO_TURMA)
WHERE F.FREQUENCIA='S' AND
F.CO_ALUNO=A.CO_ALUNO AND
T.ANO='2002'
GROUP BY T.CO_TURMA, F.CO_ALUNO
HAVING COUNT(*)>=8)
Explicação:
A sentença A.CO_ESTADOCIVIL='C' AND T.ANO='2003' AND ALT.DT_CANCELAMENTO IS NULL recupera os alunos casados, que estudaram em alguma turma de 2003 e não cancelaram. Já HAVING COUNT(*)>=8 verifica se o aluno obteve pelo menos oito presenças em alguma turma de 2002.
10ª Questão:
SELECT VM.ANO,VM.CO_TURMA,
SUM(MEDIA)/COUNT(*) MEDIATURMA
FROM VIEW_MEDIA VM
WHERE VM.ANO < '2003'
GROUP BY VM.ANO,VM.CO_TURMA
ORDER BY VM.ANO
Explicação:
A view da questão 03 calcula a média do aluno para cada disciplina. Agrupando o resultado da view pelo campo co_turma, podemos obter facilmente a média geral de cada turma.
Conclusão
Este artigo tem como objetivo realizar uma avaliação sobre SQL. No simulado, foram vistos aspectos triviais e não triviais da linguagem. Uma boa maneira de compreender as soluções é executá-las – para isso, o script completo do banco de dados, construído no SQL Server, está disponível para download no site deste artigo.
Continuo aberto a comentários, dúvidas ou sugestões sobre o simulado. Se você conhece uma maneira mais eficaz de resolver alguma questão, entre em contato para que possamos compartilhar a técnica com os demais leitores. Agradeço a todos que participaram e desejo bons estudos!
[BOX] Comentários
Recebi inúmeros emails, contendo respostas, elogios e sugestões em relação ao artigo. No entanto, ninguém acertou todas as questões. Vejamos alguns comentários sobre as mensagens recebidas:
Elogios:
- Aplicação do simulado como exercício prático na disciplina de banco de dados em algumas universidades, incentivando o aprendizado dos alunos;
- Possibilidade do leitor avaliar seus conhecimentos sobre SQL.
Críticas:
- Não disponibilização de um script para criação do banco de dados, bem como para a inserção de alguns dados de teste.
Resposta do autor: a idéia foi proposital, para forçar o leitor a se abstrair ao máximo.
Respostas:
Curiosamente, alguns erros se repetiram em diversas respostas:
- O campo dt_matricula da tabela aluno_turma foi utilizado para filtrar o ano da turma (vide questão 1).
- Na questão 03 alguns leitores, ao invés de usarem o campo período para a ordenação do resultado, usaram os campos dt_inicial e dt_final.
- Foi pedido que a view da questão 3 retornasse o campo matrícula do aluno. Algumas respostas utilizaram o campo dt_matricula, ao invés do campo co_aluno – que foi especificado como matrícula no dicionário de dados.
- A questão 08 pede uma listagem essencialmente de alunos solteiros, em todas as situações. Algumas respostas realizaram o filtro de estado civil apenas sobre os alunos que realizaram matrícula em alguma turma de 2003.
Algumas respostas interessantes:
- Na questão 01 realizamos o cálculo da média no SELECT principal. A resposta abaixo calcula a média dentro de um outro SELECT:
select TUR.ANO,
TUR.PERIODO,
TUR.DESCRICAO as DESCRICAOTURMA,
ALU.NOME,
DIS.NO_DISCIPLINA,
(select sum(NT_AVALIACAO) / TUR.NUM_PROVAS
from AVALIACAO
where CO_ALUNO = ALU.CO_ALUNO
and CO_DISCIPLINA = DIS.CO_DISCIPLINA
and CO_TURMA = TUR.CO_TURMA) as MEDIAFINAL
from AVALIACAO AVA
left join ALUNO ALU
on AVA.CO_ALUNO = ALU.CO_ALUNO
left join DISCIPLINA DIS
on AVA.CO_DISCIPLINA = DIS.CO_DISCIPLINA
left join TURMA TUR
on AVA.CO_TURMA = TUR.CO_TURMA
where TUR.ANO = 2002
order by TUR.ANO, TUR.PERIODO, ALU.NOME
No entanto, a consulta apresenta falhas. Ela não verifica se o aluno foi ou não cancelado, exigência da questão. Além disso, o comando traz resultado repetidos.
- Na questão 06 foi pedido um campo boolean informando se o aluno era novo ou não. Para resolver, usamos o operador UNION. Observe a solução de um leitor:
Select distinct ATU.CO_ALUNO,
ALU.NOME,
case
when exists(select CO_ALUNO
from ALUNO_TURMA
where CO_ALUNO = ATU.CO_ALUNO
and CO_ALUNO not in (select CO_ALUNO
from ALUNO_TURMA
where year(DT_MATRICULA) < 2003)) then
'Sim'
else
'Não'
end as ALUNONOVO
from ALUNO_TURMA ATU
left join ALUNO ALU
on ATU.CO_ALUNO = ALU.CO_ALUNO
where year(DT_MATRICULA) = 2003
Um problema nesta resposta é o filtro para as turmas de 2003 – como visto anteriormente, o campo dt_matricula não deve ser utilizado para esse fim.
- Na questão 09, um leitor apresentou uma alternativa que utiliza duas vezes o exists. Veja abaixo:
Select a.nome
From Aluno a
Where a.co_estadocivil = 'C' and
exists
(Select 1
From Aluno_turma at, Turma t
Where t.ano = 2003 and t.co_turma = at.co_turma and
at.co_aluno = a.co_aluno and
at.dt_cancelamento is null)
and
exists
(Select co_turma,count(*) as Presenca
From Frequencia
Where co_aluno = a.co_aluno and frequencia = 'S'
Group by co_turma
Having count(*) >= 8)