程序代写案例-COMP3311
时间:2022-05-03
2022/5/3 17:24 COMP3311 Sample Solutions
https://www.cse.unsw.edu.au/~cs3311/22T1/exams/sample/soln.html 1/10
COMP3311 22T1 COMP3311 22T1 Exam
Sample
Sample Solutions
Database Systems
These solutions are simply suggestions. In most cases many alternatives
exist which would be equally correct and also worth full marks. Note that
the order of tuples does not matter one bit in the SQL questions. The
test scripts set the order themselves.
Q1
-- COMP3311 22T1 Exam Sample
-- Q1: view of teams and #matches
create or replace view Q1(team, nmatches)
as
select t.country, count(*)
from Teams t join Involves m on (m.team=t.id)
group by t.country
Q2
-- COMP3311 22T1 Exam Sample
-- Q2: view of players scoring several amazing goals
create or replace view Q2(player,ngoals)
as
select p.name as player, count(g.id) as goals
from Players p join Goals g on (g.scoredBy = p.id)
where g.rating='amazing'
group by p.name
having count(g.id) > 1 ;
Q3
-- COMP3311 22T1 Exam Sample
-- Q3: team(s) with most players who have never scored a goal
create or replace view PlayersAndGoals (player,team,ngoals)
as
select p.name, t.country, count(g.id)
from Teams t
join Players p on (p.memberof = t.id)
left outer join Goals g on (p.id = g.scoredby)
group by p.name, t.country ;
create or replace view CountryAndGoalless(team,nplayers)
as
select team, count(*) as players
from PlayersAndGoals
where ngoals = 0
group by team ;
create or replace view Q3(team,nplayers)
as
select team, players
2022/5/3 17:24 COMP3311 Sample Solutions
https://www.cse.unsw.edu.au/~cs3311/22T1/exams/sample/soln.html 2/10
from CountryAndGoalless
where players = (select max(players) from CountryAndGoalless) ;
Q4
-- COMP3311 22T1 Exam Sample
-- Q4: function that takes two team names and
-- returns #matches they've played against each other
create or replace function
MatchesFor(text) returns setof integer
as $$
select m.id
from Matches m
join Involves i on (m.id = i.match)
join Teams t on (i.team = t.id)
where t.country = $1
$$ language sql;
create or replace function
Q4(_team1 text, _team2 text) returns integer
as $$
declare
nmatches integer;
begin
perform * from Teams where country = _team1;
if (not found) then return NULL; end if;
perform * from Teams where country = _team2;
if (not found) then return NULL; end if;
select count(*) into nmatches
from ((select * from MatchesFor(_team1))
intersect
(select * from MatchesFor(_team2))
) as X;
return nmatches;
end;
$$ language plpgsql;
Q5
-- COMP3311 22T1 Exam Sample
-- Q5: show "cards" awarded against a given team
-- should have parameterised these views via an SQL function :-(
create or replace view RedCardsFor(team,ncards)
as
select t.country, count(c.id)
from Players p
join Teams t on (p.memberof = t.id)
join Cards c on (c.givento = p.id)
where c.cardtype='red'
group by t.country ;
create or replace view RedCards(team,ncards)
as
select t.country, coalesce(c.ncards,0)
from Teams t left outer join RedCardsFor c on (t.country=c.team) ;
2022/5/3 17:24 COMP3311 Sample Solutions
https://www.cse.unsw.edu.au/~cs3311/22T1/exams/sample/soln.html 3/10
create or replace view YellowCardsFor(team,ncards)
as
select t.country, count(c.id)
from Players p
join Teams t on (p.memberof = t.id)
join Cards c on (c.givento = p.id)
where c.cardtype='yellow'
group by t.country ;
create or replace view YellowCards(team,ncards)
as
select t.country, coalesce(c.ncards,0)
from Teams t left outer join YellowCardsFor c on (t.country=c.team) ;
drop function if exists q5(text);
drop type if exists RedYellow;
create type RedYellow as (nreds integer, nyellows integer);
create or replace function
Q5(_team text) returns RedYellow
as $$
declare
reds integer;
yellows integer;
result RedYellow;
begin
select r.ncards, y.ncards into reds, yellows
from RedCards r
join YellowCards y on (r.team = y.team)
where r.team = _team;
if (not found) then
result.nreds := NULL;
result.nyellows := NULL;
else
result.nreds := reds;
result.nyellows := yellows;
end if;
return result;
end;
$$ language plpgsql
;
Q6
-- q6.sql
drop view if exists Q6;
drop view if exists MatchScores;
drop view if exists TeamScores;
drop view if exists TeamsInMatches;
drop view if exists GoalsByTeamInMatch;
create view GoalsByTeamInMatch
as
select g.scoredIn as match, p.memberOf as team, count(*) as goals
from Goals g join Players p on (p.id = g.scoredBy)
group by g.scoredIn, p.memberOf;
;
2022/5/3 17:24 COMP3311 Sample Solutions
https://www.cse.unsw.edu.au/~cs3311/22T1/exams/sample/soln.html 4/10
create view TeamsInMatches
as
select i.match as match, i.team as team, t.country as country
from Involves i join Teams t on (i.team = t.id)
;
create view TeamScores
as
select tim.match, tim.country, coalesce(gtm.goals, 0) as goals
from TeamsInMatches tim left join GoalsByTeamInMatch gtm
on (tim.team = gtm.team and tim.match = gtm.match)
;
create view MatchScores
as
select t1.match,
t1.country as team1, t1.goals as goals1,
t2.country as team2, t2.goals as goals2
from TeamScores t1 join TeamScores t2
on (t1.match = t2.match and t1.country < t2.country)
;
create view Q6
as
select m.city as location, m.playedOn as date,
ms.team1, ms.goals1, ms.team2, ms.goals2
from Matches m join MatchScores ms on (m.id = ms.match)
;
#!/usr/bin/python3
# COMP3311 22T1 Exam Sample
# Q6: print match reports for a specified team in a given year
import sys
import psycopg2
def getResult(g1,g2):
if g1 > g2:
result = "won"
elif g1 < g2:
result = "lost"
else:
result = "drew"
return result
db = None
cur = None
if len(sys.argv) < 3:
print(f"Usage: {sys.argv[0]} TeamName Year")
exit(1)
team = sys.argv[1]
year = sys.argv[2]
if not year.isnumeric:
print(f"Invalid year {year}")
start_year = f"{year}-01-01"
end_year = f"{year}-12-31"
qT = "select count(*) from Teams where country = %s"
q6 = """
select *
2022/5/3 17:24 COMP3311 Sample Solutions
https://www.cse.unsw.edu.au/~cs3311/22T1/exams/sample/soln.html 5/10
from q6
where (team1 = %s or team2 = %s) and date between %s and %s
order by date
"""
try:
db = psycopg2.connect("dbname=footy")
cur = db.cursor();
cur.execute(qT, [team])
tup = cur.fetchone()
if not tup:
print(f"No team '{team}'")
exit(1)
cur.execute(q6, [team,team,start_year,end_year])
res = cur.fetchall()
if len(res) == 0:
print("No matches")
exit(1)
for tup in res:
where,date,t1,g1,t2,g2 = tup
if t1 == team:
result = getResult(g1,g2)
goals = f"{g1}-{g2}"
opponent = t2
else:
result = getResult(g2,g1)
goals = f"{g2}-{g1}"
opponent = t1
print(f"played {opponent} in {where} on {date} and {result} {goals}")
except psycopg2.Error as err:
print("DB error: ", err)
finally:
if db:
db.close()
if cur:
cur.close()
Q7
#!/usr/bin/python3
# COMP3311 22T1 Exam Sample
# Q7: print a specified player's career performance
# and, yes, John was naughty using a query inside a for loop ...
import sys
import psycopg2
db = None
cur = None
if len(sys.argv) < 2:
print(f"Usage: {sys.argv[0]} PlayerName")
exit(1)
player = sys.argv[1]
qPlayer = "select id,name from Players where name = %s";
qGames = """
select m.id, m.city, m.playedOn
2022/5/3 17:24 COMP3311 Sample Solutions
https://www.cse.unsw.edu.au/~cs3311/22T1/exams/sample/soln.html 6/10
from Teams t join Involves i on (i.team=t.id)
join Matches m on (m.id=i.match)
join Players p on (t.id=p.memberof)
where p.id = %s
order by m.playedOn
"""
qGoals = "select count(*) from Goals where scoredIn = %s and scoredBy = %s"
qTeam = """
select t.country
from Teams t join Players p on (t.id = p.memberof)
where p.id = %s
"""
totMatches = 0
totGoals = 0
try:
db = psycopg2.connect("dbname=footy")
cur = db.cursor();
cur.execute(qPlayer,[player])
res = cur.fetchone()
if not res:
print("No such player")
exit(1)
pid,name = res
cur.execute(qGames, [pid])
for g in cur.fetchall():
totMatches = totMatches + 1
mid,city,date = g
cur.execute(qGoals, [mid,pid])
ngoals = cur.fetchone()[0];
totGoals = totGoals + ngoals
if ngoals == 0:
continue
elif ngoals == 1:
goals = " and scored 1 goal"
else:
goals = f" and scored {ngoals} goals"
print(f"played in {city} on {date}{goals}")
cur.execute(qTeam, [pid])
team = cur.fetchone()[0]
print(f"Summary: played for {team}, {totMatches} matches, {totGoals} goals")
except psycopg2.Error as err:
print("DB error: ", err)
finally:
if cur:
cur.close()
if db:
db.close()
Q8
a. ER-style mapping for subclasses:
create table Employee (
id integer,
name text,
position text,
primary key (id)
2022/5/3 17:24 COMP3311 Sample Solutions
https://www.cse.unsw.edu.au/~cs3311/22T1/exams/sample/soln.html 7/10
);
create table PartTime (
id integer references Employee(id),
fraction float check (0.0 < fraction and fraction < 1.0),
primary key (id)
);
create table Casual (
id integer references Employee(id),
primary key (id)
);
create table HoursWorked (
id integer references Casual(id),
onDate date,
starting time,
ending time,
primary key (id,onDate),
constraint timing check (starting < ending)
);
We cannot enforce the total participation constraint (an employee may have no associated
subclass tuples). We cannot enforce the disjoint subclasses constraint (an employee may have
several associated subclass tuples).
b. Single-table mapping for subclasses:
create table Employee (
id integer,
name text,
position text,
etype text not null check (etype in ('part-time','casual')),
fraction float check (0.0 < fraction and fraction < 1.0),
primary key (id),
constraint CheckValidTypeData
check ((etype = 'part-time' and fraction is not null)
or (etype = 'casual' and fraction is null))
);
create table HoursWorked (
id integer references Employee(id),
onDate date,
starting time,
ending time,
primary key (id,onDate),
constraint timing check (starting < ending)
);
With an appropriate CheckValidTypeData constraint we can enforce the disjoint subclass
constraint. With the not null requirement on etype, we can enforce the total participation
constraint. The etype field could be replaced by a boolean which checks isCasual.
It is also feasible to omit the etype field and simply assume that fraction being not null means
that the employee is part-time.
In neither case can we enforce that part-time employees do not have hours-worked associated
with them.
Q9
a. Trigger to handle adding a new CourseEnrolments tuple:
create function fixCoursesOnAddCourseEnrolment() returns trigger
as $$
2022/5/3 17:24 COMP3311 Sample Solutions
https://www.cse.unsw.edu.au/~cs3311/22T1/exams/sample/soln.html 8/10
declare
_nS integer; _nE integer; _sum integer; _avg float;
begin
select nS,nE,avgEval into _nS,_nE,_avg
from Courses where id=new.course;
-- add one more student
_ns := _nS + 1;
if (new.stuEval is not null) then
-- got another evaluation
_nE := _nE + 1;
if (_nS ≤ 10 or (3*_nE) ≤ _nS) then
-- added a new student, but still not enough for valid eval
_avg := null;
else
-- compute new evaluation
select sum(stuEval) into _sum
from CourseEnrolments where course=new.course;
_sum := _sum + new.stuEval;
_avg := _sum::float / _nE;
end if;
end if;
-- update Course record
update Courses set ns = _nS, nE = _nE, avgEval = _avg
where id=new.course;
-- since "after" trigger, return value irrelevant
return new;
end;
$$
language plpgsql;
b. Trigger to handle dropping a CourseEnrolments tuple:
create function fixCoursesOnDropCourseEnrolment() returns trigger
as $$
declare
_nS integer; _nE integer; _sum integer; _avg float;
begin
select nS,nE,avgEval into _nS,_nE,_avg
from Courses where id=old.course;
-- we always add one more student
_nS := _nS - 1;
if (old.stuEval is not null) then
-- lost an evaluation
_nE := _nE - 1;
if (_nS ≤ 10 or (3*_nE) ≤ _nS) then
-- no longer enough for valid eval
_avg := null;
else
-- compute new evaluation
select sum(stuEval) into _sum
from CourseEnrolments
where course=old.course and student<>old.student;
_avg := _sum::float / _nE;
end if;
end if;
-- update Course record
update Courses set nS = _nS, nE = _nE, avgEval = _avg
where id=old.course;
-- since "after" trigger, return value irrelevant
return old;
end;
2022/5/3 17:24 COMP3311 Sample Solutions
https://www.cse.unsw.edu.au/~cs3311/22T1/exams/sample/soln.html 9/10
$$
language plpgsql;
c. Trigger to handle updating a CourseEnrolments tuple:
create function fixCoursesOnModCourseEnrolment() returns trigger
as $$
declare
_newEval integer; _oldEval integer;
_nE integer; _nS integer; _sum integer; _avg float;
begin
select nS,nE,avgEval into _nS,_nE,_avg
from Courses where id=old.course;
if (old.stuEval is null and new.stuEval is not null) then
-- update involves adding evaluation
_nE := _nE + 1;
end if;
-- treat NULL as zero for arithmetic
_oldEval := coalesce(old.stuEval,0);
_newEval := coalesce(new.stuEval,0);
if (_oldEval <> _newEval) then
-- compute new evaluation
select sum(stuEval) into _sum
from CourseEnrolments where course=old.course;
_avg := (_sum - _oldEval + _newEval)::float / _nE;
end if;
-- update Course record
update Courses set nS = _nS, nE = _nE, avgEval = _avg
where id=old.course;
-- since "after" trigger, return value irrelevant
return new;
end;
$$
language plpgsql;
Q10
a. The code prints a list of teams and the number of matches they have played in each city.
b. The outer query (teams) is executed once, and returns 100 tuples (assumption). For each of
these, one (inner) query (count) is executed. Total calls to execute() = 101.
c. Python code to achieve the same effect with a single query:
q = """
select t.country, m.city, count(*)
from Teams t
join Involves i on (i.team = t.id)
join Matches m on (i.match = m.id)
group by t.country, m.city
order by t.country, m.city
"""
db = psycopg2.connect("dbname=footy")
cur = db.cursor()
cur.execute(q)
results = cur.fetchall()
for tuple in results:
team, city, nmatches = tuple
print(f"{t} {c} {n}")
2022/5/3 17:24 COMP3311 Sample Solutions
https://www.cse.unsw.edu.au/~cs3311/22T1/exams/sample/soln.html 10/10
Q11
a. FDs: A→BC, DE→F, ADE→G (also accept A→B, A→C instead of A→BC)
b. Step Attrs FDs Key Notes
1 ABCDEFG A→BC, DE→F,ADE→G ADE
A→BC violates BCNF, LHS is partial key,
so partition
2a ABC A→BC A No FDs violate BCNF, so ABC is part ofsolution
2b ADEFG DE→F, ADE→G ADE DE→F violates BCNF, LHS is partial key,so partition
3a DEF DE→F DE No FDs violate BCNF, so DEF is part ofsolution
3b ADEG ADE→G ADE No FDs violate BCNF, so ADEG is partof solution
Solution: three tables: ABC, DEF, ADEG (i.e. Student, Assessment, Mark)
Q12
a. Which employees earn more than $20 per hour (give their employee id and name)
Tmp1 = Sel[payRate>20]Employees
Res = Proj[eno,ename]Tmp1
b. Who are the department managers (give just their name)
Tmp1 = Employees Join Departments (on eno)
Res = Proj[ename]Tmp1
c. Which employees worked on every day during the last week (give just their name)
Tmp1 = Proj[day]Timesheet
Tmp2 = Proj[eno,day]Timesheet
Tmp3 = Tmp2 / Tmp1
Tmp4 = Employees Join Tmp3 (on eno)
Res = Proj[ename]Tmp4
Would expect to see division used ... if not, but still correct, ok, e.g.
Tmp1 = Proj[eno](Sel[day='Mon']Timesheet)
Tmp2 = Proj[eno](Sel[day='Tue']Timesheet)
...
Tmp7 = Proj[eno](Sel[day='Sun']Timesheet)
Tmp8 = Tmp1 Intersect Tmp2 Intersect ... Tmp7
Tmp9 = Employees Join Tmp8
Res = Proj[ename]Tmp9