| COMP3311 23T3 |
Prac Exercise 05 SQL Queries, Views, and Aggregates (ii) |
Database Systems |
In lectures, we used a simple database about beers, bars and drinkers to illustrate aspects of querying in SQL. The database was designed to simplify queries by using symbolic primary keys (e.g., Beers.name). In practice, we don't use symbolic primary keys because: (a) they typically occupy more space than a numeric key, (b) symbolic names have an annoying tendency to change over time (e.g., you might change your email, and having email as a primary key creates a multitude of update problems). Therefore, we have designed a (slightly) more realistic schema for representing the same information:
The SQL schema will obviously be different to the schema used in lectures, and is available in the file:
/home/cs3311/web/23T3/pracs/05/schema.sql
This schema is written in portable SQL and so should load into both PostgreSQL and SQLite without errors.
If you're working on your laptop (and not via putty), you can grab copies of all files used in this Prac in the ZIP archive:
/home/cs3311/web/23T3/pracs/05/prac.zip
Login to a machine with a PostgreSQL server running. If you already have a beer2 database and you want to replace it, you will, of course, need to drop it first:
$ dropdb beer2
Then do the following:
$ createdb beer2... make a new empty database ... $ psql beer2 -f /home/cs3311/web/23T3/pracs/05/schema.sql... load the schema ... produces CREATE TABLE, etc. messages ... $ psql beer2 -f /home/cs3311/web/23T3/pracs/05/data.sql... load the tuples ... poduces INSERT messages ...
You now have a database that you can use via:
$ psql beer2... run SQL commands ...
Login to a machine with SQLite installed, change to the directory containing the schema.sql and data.sql files mentioned above, and do the following:
% sqlite3 beer2.db SQLite version 3.8.7.1 2014-10-29 13:59:56 Enter ".help" for usage hints. sqlite> .read schema.sql sqlite> .read data.sql sqlite> .quit %
This will create a file called beer2.db in the current directory. You now have a database that you can use via:
$ sqlite3 beer2.db... run SQL commands ...
Use the two databases you created above to do the exercises below. The same view definitions should work in both databases. Perhaps you could alternate between developing and testing the view first in PostgreSQL and then testing it in SQLite, and vice versa. The aim is to get practice in building queries in both databases.
In the questions below, you are required to produce SQL queries
to solve a range of data retrieval problems on this schema.
For each problem, create a view called Qn
which holds the top-level
SQL statement that produces
the answer (this SQL statement may make use of any views defined
earlier in the Prac Exercise).
In producing a solution for each problem, you may define as many
auxiliary views as you like.
To simplify the process of producing these views, a template file (queries.sql) is available. While you're developing your views, you might find it convenient to edit the views in one window (i.e. edit the queries.sql file containing the views) and copy-and-paste the view definitions into another window running psql or sqlite3.
Note that the order of tuples in the results does not matter. As long as you have the same set of tuples, your view is correct. Remember that, in theory, the output from an SQL query is a set. Some test queries use an explicit ordering, but that should not be included in the view definition.
Note also that the sample outputs typically use column names that are different to the column names in the table. You should use the column names given in the sample output; treat them as part of description of the question.
Once you have completed each of the view definitions, you can test it simply by typing:
beer2=# select * from Qn;
and observing whether the result matches the expected result given below. Note that I'll give all the results in PostgreSQL format; the SQLite tuples don't look precisely the same, but it should be clear enough that it is the same set of tuples.
Write an SQL view to answer each of the following queries. Note that none of your queries should contain internal id values; all references to entities in queries should be via their name.
What beers are made by Toohey's?
In PostgreSQL, the results should look like:
beer2=# select * from Q1;
beer
-------------
New
Old
Red
Sheaf Stout
(4 rows)
Show beers with headings "Beer", "Brewer".
In PostgreSQL, the results should look like:
beer2=# select * from Q2;
Beer | Brewer
--------------------------+---------------------
80/- | Caledonian
Amber Ale | James Squire
Bigfoot Barley Wine | Sierra Nevada
Burragorang Bock | George IV Inn
Chestnut Lager | Bridge Road Brewers
Crown Lager | Carlton
Fosters Lager | Carlton
India Pale Ale | James Squire
Invalid Stout | Carlton
Melbourne Bitter | Carlton
New | Toohey's
Nirvana Pale Ale | Murray's
Old | Toohey's
Old Admiral | Lord Nelson
Pale Ale | Sierra Nevada
Pilsener | James Squire
Porter | James Squire
Premium Lager | Cascade
Red | Toohey's
Sink the Bismarck | Brew Dog
Sheaf Stout | Toohey's
Sparkling Ale | Cooper's
Stout | Cooper's
Tactical Nuclear Penguin | Brew Dog
Three Sheets | Lord Nelson
Victoria Bitter | Carlton
(26 rows)
Find the brewers whose beers John likes.
In PostgreSQL, the results should look like:
beer2=# select * from Q3;
brewer
---------------
Brew Dog
James Squire
Lord Nelson
Sierra Nevada
Caledonian
(5 rows)
How many different beers are there?
In PostgreSQL, the results should look like:
beer2=# select * from Q4;
#beers
--------
26
(1 row)
How many different brewers are there?
In PostgreSQL, the results should look like:
beer2=# select * from Q5;
#brewers
----------
12
(1 row)
Find pairs of beers by the same manufacturer (but no (a,b) and (b,a) pairs, or (a,a))
In PostgreSQL, the results should look like:
beer2=# select * from Q6;
beer1 | beer2
---------------------+--------------------------
Amber Ale | Porter
Amber Ale | Pilsener
Amber Ale | India Pale Ale
Bigfoot Barley Wine | Pale Ale
Crown Lager | Victoria Bitter
Crown Lager | Melbourne Bitter
Crown Lager | Invalid Stout
Crown Lager | Fosters Lager
Fosters Lager | Victoria Bitter
Fosters Lager | Melbourne Bitter
Fosters Lager | Invalid Stout
India Pale Ale | Porter
India Pale Ale | Pilsener
Invalid Stout | Victoria Bitter
Invalid Stout | Melbourne Bitter
Melbourne Bitter | Victoria Bitter
New | Sheaf Stout
New | Red
New | Old
Old | Sheaf Stout
Old | Red
Old Admiral | Three Sheets
Pilsener | Porter
Red | Sheaf Stout
Sink the Bismarck | Tactical Nuclear Penguin
Sparkling Ale | Stout
(26 rows)
How many beers does each brewer make?
In PostgreSQL, the results should look like:
beer2=# select * from Q7 order by brewer;
brewer | nbeers
---------------------+--------
Brew Dog | 2
Bridge Road Brewers | 1
Caledonian | 1
Carlton | 5
Cascade | 1
Cooper's | 2
George IV Inn | 1
James Squire | 4
Lord Nelson | 2
Murray's | 1
Sierra Nevada | 2
Toohey's | 4
(12 rows)
Which brewer makes the most beers?
In PostgreSQL, the results should look like:
beer2=# select * from Q8; brewer --------- Carlton (1 row)
Beers that are the only one by their brewer.
In PostgreSQL, the results should look like:
beer2=# select * from Q9;
beer
------------------
80/-
Burragorang Bock
Chestnut Lager
Nirvana Pale Ale
Premium Lager
(5 rows)
Beers sold at bars where John drinks.
In PostgreSQL, the results should look like:
beer2=# select * from Q10 order by beer;
beer
-------------------
Burragorang Bock
New
Old
Old Admiral
Pale Ale
Sink the Bismarck
Sparkling Ale
Three Sheets
Victoria Bitter
(9 rows)
You might like to consider a variation on this query to find just the beers that John likes that are sold in the bars where he drinks. The solution is given in the solutions file.
Bars where either Gernot or John drink.
In PostgreSQL, the results should look like:
beer2=# select * from Q11 order by bar;
bar
------------------
Australia Hotel
Coogee Bay Hotel
Local Taphouse
Lord Nelson
Royal Hotel
(5 rows)
Bars where both Gernot and John drink.
In PostgreSQL, the results should look like:
beer2=# select * from Q12;
bar
-------------
Lord Nelson
(1 row)
Bars where John drinks but Gernot doesn't
In PostgreSQL, the results should look like:
beer2=# select * from Q13;
bar
------------------
Australia Hotel
Local Taphouse
Coogee Bay Hotel
(3 rows)
What is the most expensive beer?
In PostgreSQL, the results should look like:
beer2=# select * from Q14;
beer
-------------------
Sink the Bismarck
(1 row)
Find bars that serve New at the same price as the Coogee Bay Hotel charges for VB.
In PostgreSQL, the results should look like:
beer2=# select * from Q15;
bar
-------------
Royal Hotel
(1 row)
Find the average price of common beers (where "common" = served in more than two hotels)
In PostgreSQL, the results should look like:
beer2=# select * from Q16;
beer | AvgPrice
-----------------+----------
Victoria Bitter | 2.40
New | 2.59
Old | 2.68
(3 rows)
Which bar sells 'New' cheapest?
In PostgreSQL, the results should look like:
beer2=# select * from Q17;
bar
--------------
Regent Hotel
(1 row)
Which bar is most popular? (Most drinkers)
In PostgreSQL, the results should look like:
beer2=# select * from Q18;
bar
------------------
Coogee Bay Hotel
Lord Nelson
(2 rows)
Which bar is least popular? (May have no drinkers)
In PostgreSQL, the results should look like:
beer2=# select * from Q19;
bar
-----------------
Local Taphouse
Marble Bar
Regent Hotel
Australia Hotel
Royal Hotel
(5 rows)
Which bar is most expensive? (Highest average price)
In PostgreSQL, the results should look like:
beer2=# select * from Q20;
bar
----------------
Local Taphouse
(1 row)
Which beers are sold at all bars?
In PostgreSQL, the results should look like:
beer2=# select * from Q21; beer ------ (0 rows)
i.e. no beers are sold at all bars.
Price of cheapest beer at each bar?
In PostgreSQL, the results should look like:
beer2=# select * from Q22;
bar | min_price
------------------+-----------
Coogee Bay Hotel | 2.25
Local Taphouse | 7.50
Royal Hotel | 2.30
Australia Hotel | 3.00
Regent Hotel | 2.20
Marble Bar | 2.80
Lord Nelson | 3.00
(7 rows)
Name of cheapest beer at each bar?
In PostgreSQL, the results should look like:
beer2=# select * from Q23;
bar | beer
------------------+-----------------
Australia Hotel | New
Coogee Bay Hotel | New
Lord Nelson | New
Marble Bar | New
Marble Bar | Victoria Bitter
Regent Hotel | New
Regent Hotel | Victoria Bitter
Royal Hotel | Victoria Bitter
Royal Hotel | New
Local Taphouse | Pale Ale
(10 rows)
How many drinkers are in each suburb?
In PostgreSQL, the results should look like:
beer2=# select * from Q24; addr | count ----------+------- Randwick | 1 Mosman | 1 Newtown | 1 Clovelly | 1 (4 rows)
How many bars in suburbs where drinkers live? (must include suburbs with no bars)
In PostgreSQL, the results should look like:
beer2=# select * from Q25; addr | #bars ----------+------- Randwick | 1 Mosman | 0 Newtown | 0 Clovelly | 0 (4 rows)
You should attempt the above exercises before looking at the PostgreSQL sample solutions.
Now that you've attempted the above exercises in PostgreSQL, let's consider how things would work in SQLite. This provides an interesting exercise in SQL portability, since both databases support "standard SQL".
Make a copy of your queries.sql file and start testing the views that you created for PostgreSQL for SQLite. If you want to make the query results from SQLite look more like those from PostgreSQL, run the following commands at the start of your SQLite session:
$ sqlite3 beer2.db SQLite version 3.8.7.1 2014-10-29 13:59:56 Enter ".help" for usage hints. sqlite> .headers on sqlite> .mode column sqlite> .width 20 20 20 20 sqlite>... continue with your SQL queries ...
Note that SQLite is not quite as smart as PostgreSQL when it comes to choosing column widths. All columns will be 20 characters wide if you use the above settings. Note that any values that are wider than 20 characters will be truncated. If you can't be bothered typing these commands each time, put them in a file called .sqliterc in your home directory and they'll be executed each time you run sqlite3.
As we noticed in the previous Prac Exercise, SQLite doesn't support the definition of views via:
create or replace view ViewName as ...
The fix for this is simple enough. Change all of the view definitions to something like:
drop view if exists ViewName; create view ViewName as ...
Note that this approach would cause problems in PostgreSQL, which records which views depend on which other views. Inevitably, you would try to drop a view defined early in the SQL file that is used by view later in the file. In PostgreSQL, you can add the keyword cascade to ensure that you not only drop the view you asked to drop, but also all the views that make use of it. SQLite doesn't do this dependency checking, and will allow you to drop a view, even if other views use it. You won't notice until you try to use one of the remaining views and will then be told that the view you dropped is undefined. If you redefine the view straight away (as in the above), then the dependency problem never arises.
If you're a vim user, the following command will convert all of the current create view statements into an appropriate form. If you're not a vim user, read and weep ... or post the equivalent for your editor of choice.
:s/^create or replace view \([^ ]*\)/drop view if exists \1;^Mcreate view \1/
Note: the ^M is achieved by typing the two-character sequence control-V control-M. Also, if you attempt this in vim and mess it up, you can undo the effects simply by typing the character u.
To avoid the fact that SQLite doesn't support view definitions of the form:
create view ViewName(attr1, attr2, ...) as select expr1, expr2, ...
simply use the equivalent form:
create view ViewName as select expr1 as attr1, expr2 as attr2, ...
Once you've made the above changes, many of the views will work correctly in both PostgreSQL and SQLite.
One "problem" is that SQLite doesn't have quite the same formatting options for result values. PostgreSQL allows you to cast to a numeric value to control the number of decimal places in real number values; unfrotunately, SQLite doesn't quite allow the same. Don't worry if your real results don't look the same in SQLite.
You should attempt the above exercises before looking at the SQLite sample solutions.