/* * File: SmokedTrout_Database.sql * * * * Author: Lam, Cheuk Yu */ /* * Assume a user account 'fsad' with password 'fsad2022' with permission * to create databases already exists. */ /* ********************************************************* * Exercise 1. Create the Smoked Trout database * ************************************************************ */ -- The first time you login to execute this file with \i it may -- be convenient to change the working directory. DROP DATABASE IF EXISTS "SmokedTrout" ; CREATE USER fsad PASSWORD 'fsad2022' CREATEDB; GRANT pg_read_server_files TO fsad; -- In PostgreSQL, folders are identified with '/' -- 1) Create a database called SmokedTrout. CREATE DATABASE "SmokedTrout" WITH OWNER fsad ENCODING = 'UTF8' CONNECTION LIMIT = -1; -- 2) Connect to the database \c "SmokedTrout" fsad /* ********************************************************* * Exercise 2. Implement the given design in the Smoked Trout database * ************************************************************ */ -- 1) Create a new ENUM type called materialState for storing the raw material state CREATE TYPE "materialState" AS ENUM ( 'Solid', 'Liquid', 'Gas', 'Plasma' ); -- 2) Create a new ENUM type called materialComposition for storing whether -- a material is Fundamental or Composite. CREATE TYPE "materialComposition" AS ENUM ( 'Fundamental', 'Composite' ); -- 3) Create the table TradingRoute with the corresponding attributes. CREATE TABLE "TradingRoute" ( "MonitoringKey" INTEGER NOT NULL, "FleetSize" INTEGER, "OperatingCompany" VARCHAR(50), "LastYearRevenue" REAL, PRIMARY KEY ("MonitoringKey") ); -- 4) Create the table Planet with the corresponding attributes. CREATE TABLE "Planet" ( "PlanetID" INTEGER NOT NULL, "StarSystem" VARCHAR(50), "Name" VARCHAR(50), "Population" INTEGER, PRIMARY KEY ("PlanetID") ); -- 5) Create the table SpaceStation with the corresponding attributes. CREATE TABLE "SpaceStation" ( "StationID" INTEGER NOT NULL, "PlanetID" INTEGER, "Name" VARCHAR(20), "Longitude" VARCHAR(20), "Latitude" VARCHAR(20), PRIMARY KEY ("StationID") ); ALTER TABLE "SpaceStation" ADD FOREIGN KEY ("PlanetID") REFERENCES "Planet"("PlanetID") ON UPDATE CASCADE ON DELETE CASCADE NOT VALID; -- 6) Create the parent table Product with the corresponding attributes. CREATE TABLE "Product" ( "ProductID" INTEGER NOT NULL, "Name" VARCHAR(20), "VolumePerTon" REAL, "ValuePerTon" REAL, PRIMARY KEY ("ProductID") ); -- 7) Create the child table RawMaterial with the corresponding attributes. CREATE TABLE "RawMaterial" ( "ProductID" INTEGER, "Name" VARCHAR(20), "FundamentalOrComposite" "materialComposition", "VolumePerTon" REAL, "ValuePerTon" REAL, "State" "materialState", PRIMARY KEY ("ProductID") ) INHERITS ("Product"); -- 8) Create the child table ManufacturedGood. CREATE TABLE "ManufacturedGood" ( "ProductID" INTEGER, "Name" VARCHAR(20), "VolumePerTon" REAL, "ValuePerTon" REAL, PRIMARY KEY ("ProductID") ) INHERITS ("Product"); -- 9) Create the table MadeOf with the corresponding attributes. CREATE TABLE "MadeOf" ( "ManufacturedGoodID" INTEGER, "ProductID" INTEGER ); -- 10) Create the table Batch with the corresponding attributes. CREATE TABLE "Batch" ( "BatchID" INTEGER NOT NULL, "ProductID" INTEGER, "ExtractionOrManufacturingDate" date, "OriginalFrom" INTEGER, PRIMARY KEY ("BatchID") ); ALTER TABLE "Batch" ADD FOREIGN KEY ("OriginalFrom") REFERENCES "Planet"("PlanetID") ON UPDATE CASCADE ON DELETE CASCADE NOT VALID; -- 11) Create the table Sells with the corresponding attributes. CREATE TABLE "Sells" ( "BatchID" INTEGER, "StationID" INTEGER ); ALTER TABLE "Sells" ADD FOREIGN KEY ("BatchID") REFERENCES "Batch"("BatchID") ON UPDATE CASCADE ON DELETE CASCADE NOT VALID; ALTER TABLE "Sells" ADD FOREIGN KEY ("StationID") REFERENCES "SpaceStation"("StationID") ON UPDATE CASCADE ON DELETE CASCADE NOT VALID; -- 12) Create the table Buys with the corresponding attributes. CREATE TABLE "Buys" ( "BatchID" INTEGER, "StationID" INTEGER ); ALTER TABLE "Buys" ADD FOREIGN KEY ("BatchID") REFERENCES "Batch"("BatchID") ON UPDATE CASCADE ON DELETE CASCADE NOT VALID; ALTER TABLE "Buys" ADD FOREIGN KEY ("StationID") REFERENCES "SpaceStation"("StationID") ON UPDATE CASCADE ON DELETE CASCADE NOT VALID; -- 13) Create the table CallsAt with the corresponding attributes. CREATE TABLE "CallsAt" ( "MonitoringKey" INTEGER, "StationID" INTEGER, "VisitOrder" INTEGER ); ALTER TABLE "CallsAt" ADD FOREIGN KEY ("MonitoringKey") REFERENCES "TradingRoute"("MonitoringKey") ON UPDATE CASCADE ON DELETE CASCADE NOT VALID; ALTER TABLE "CallsAt" ADD FOREIGN KEY ("StationID") REFERENCES "SpaceStation"("StationID") ON UPDATE CASCADE ON DELETE CASCADE NOT VALID; -- 14) Create the table Distance with the corresponding attributes. CREATE TABLE "Distance" ( "PlanetOrigin" INTEGER, "PlanetDestination" INTEGER, "AvgDistance" REAL ); ALTER TABLE "Distance" ADD FOREIGN KEY ("PlanetDestination") REFERENCES "Planet"("PlanetID") ON UPDATE CASCADE ON DELETE CASCADE NOT VALID; ALTER TABLE "Distance" ADD FOREIGN KEY ("PlanetOrigin") REFERENCES "Planet"("PlanetID") ON UPDATE CASCADE ON DELETE CASCADE NOT VALID; /* ********************************************************* * Exercise 3. Populate the Smoked Trout database * ************************************************************ */ /* ********************************************************* * NOTE: The copy statement is NOT standard SQL. * The copy statement does NOT permit on-the-fly renaming columns, * hence, whenever necessary, we: * 1) Create a dummy table with the column name as in the file * 2) Copy from the file to the dummy table * 3) Copy from the dummy table to the real table * 4) Drop the dummy table (This is done further below, as I keep * the dummy table also to imporrt the other columns) ************************************************************ */ -- 1) Unzip all the data files in a subfolder called data from where you have your code file -- NO CODE GOES HERE. THIS STEP IS JUST LEFT HERE TO KEEP CONSISTENCY WITH THE ASSIGNMENT STATEMENT -- 2) Populate the table TradingRoute with the data in the file TradeRoutes.csv. CREATE TABLE Dummy ( "MonitoringKey" SERIAL , "FleetSize" INTEGER, "OperatingCompany" VARCHAR (40) , "LastYearRevenue" REAL NOT NULL); \copy Dummy FROM './data/TradeRoutes.csv' WITH (FORMAT CSV, HEADER); INSERT INTO "TradingRoute" ("MonitoringKey", "OperatingCompany", "FleetSize", "LastYearRevenue") SELECT "MonitoringKey", "OperatingCompany", "FleetSize", "LastYearRevenue" FROM Dummy; DROP TABLE Dummy; -- 3) Populate the table Planet with the data in the file Planets.csv. CREATE TABLE Dummy("PlanetID" SERIAL, "StarSystem" VARCHAR(20), "Planet" VARCHAR(20), "Population_inMillions_" INTEGER); \copy Dummy FROM './data/Planets.csv' WITH (FORMAT CSV, HEADER); INSERT INTO "Planet" ("PlanetID", "StarSystem", "Name", "Population") SELECT "PlanetID", "StarSystem", "Planet", "Population_inMillions_" FROM Dummy; DROP TABLE Dummy; -- 4) Populate the table SpaceStation with the data in the file SpaceStations.csv. CREATE TABLE Dummy ("StationID" SERIAL, "PlanetID" INTEGER, "SpaceStations" VARCHAR(20), "Longitude" VARCHAR(20), "Latitude" VARCHAR(20)); \copy Dummy from './data/SpaceStations.csv' WITH (FORMAT CSV , HEADER); INSERT INTO "SpaceStation" ("StationID", "PlanetID", "Name", "Longitude", "Latitude") SELECT "StationID", "PlanetID", "SpaceStations", "Longitude", "Latitude" FROM Dummy; DROP TABLE Dummy; -- 5) Populate the tables RawMaterial and Product with the data in the file Products_Raw.csv. CREATE TABLE Dummy("ProductID" SERIAL, "Product" VARCHAR(20), "Composite" BOOLEAN, "VolumePerTon" REAL, "ValuePerTon" REAL, State "materialState"); \copy Dummy FROM './data/Products_Raw.csv' WITH (FORMAT CSV , HEADER); ALTER TABLE Dummy ALTER COLUMN "Composite" TYPE "materialComposition" USING ( CASE "Composite" WHEN TRUE THEN 'Composite'::"materialComposition" WHEN FALSE THEN 'Fundamental'::"materialComposition" END ); INSERT INTO "RawMaterial" ("ProductID", "Name", "FundamentalOrComposite", "VolumePerTon", "ValuePerTon", "State") SELECT "ProductID", "Product", "Composite", "VolumePerTon", "ValuePerTon", State FROM Dummy; DROP TABLE Dummy; -- 6) Populate the tables ManufacturedGood and Product with the data in the file Products_Manufactured.csv. CREATE TABLE Dummy ("ProductID" SERIAL, "Product" VARCHAR (20), "VolumePerTon" REAL, "ValuePerTon" REAL); \copy Dummy FROM './data/Products_Manufactured.csv' WITH (FORMAT CSV , HEADER); INSERT INTO "ManufacturedGood" ("ProductID", "Name", "VolumePerTon", "ValuePerTon") SELECT "ProductID", "Product", "VolumePerTon", "ValuePerTon" FROM Dummy; DROP TABLE Dummy; -- 7) Populate the table MadeOf with the data in the file MadeOf.csv. CREATE TABLE Dummy ("ManufacturedGoodID" INTEGER, "ProductID" INTEGER); \copy Dummy FROM './data/MadeOf.csv' WITH (FORMAT CSV , HEADER); INSERT INTO "MadeOf" ("ManufacturedGoodID", "ProductID") SELECT "ManufacturedGoodID", "ProductID" FROM Dummy; DROP TABLE Dummy; -- 8) Populate the table Batch with the data in the file Batches.csv. CREATE TABLE Dummy("BatchID" SERIAL, "ProductID" INTEGER, "ExtractionOrManufacturingDate" DATE, "OriginalFrom" INT); \copy Dummy FROM './data/Batches.csv' WITH (FORMAT CSV , HEADER); INSERT INTO "Batch" ("BatchID", "ProductID", "ExtractionOrManufacturingDate", "OriginalFrom") SELECT "BatchID", "ProductID", "ExtractionOrManufacturingDate", "OriginalFrom" FROM Dummy; DROP TABLE Dummy; -- 9) Populate the table Sells with the data in the file Sells.csv. CREATE TABLE Dummy("BatchID" INTEGER, "StationID" INT); \copy Dummy FROM './data/Sells.csv' WITH (FORMAT CSV , HEADER); INSERT INTO "Sells" ("BatchID", "StationID") SELECT "BatchID", "StationID" FROM Dummy; DROP TABLE Dummy; -- 10) Populate the table Buys with the data in the file Buys.csv. CREATE TABLE Dummy("BatchID" INTEGER, "StationID" INT); \copy Dummy FROM './data/Buys.csv' WITH (FORMAT CSV , HEADER); INSERT INTO "Buys" ("BatchID", "StationID") SELECT "BatchID", "StationID" FROM Dummy; DROP TABLE Dummy; -- 11) Populate the table CallsAt with the data in the file CallsAt.csv. CREATE TABLE Dummy("MonitoringKey" INTEGER, "StationID" INTEGER, "VisitOrder" INT); \copy Dummy FROM './data/CallsAt.csv' WITH (FORMAT CSV , HEADER); INSERT INTO "CallsAt" ("MonitoringKey", "StationID", "VisitOrder") SELECT "MonitoringKey", "StationID", "VisitOrder" FROM Dummy; DROP TABLE Dummy; -- 12) Populate the table Distance with the data in the file PlanetDistances.csv. CREATE TABLE Dummy("PlanetOrigin" INTEGER, "PlanetDestination" INTEGER, "Distance" REAL); \copy Dummy FROM './data/PlanetDistances.csv' WITH (FORMAT CSV, HEADER); INSERT INTO "Distance" ("PlanetOrigin", "PlanetDestination", "AvgDistance") SELECT "PlanetOrigin", "PlanetDestination", "Distance" FROM Dummy; DROP TABLE Dummy; /* ********************************************************* * Exercise 4. Query the database * ************************************************************ */ -- 4.1 Report last year taxes per company -- 1) Add an attribute Taxes to table TradingRoute ALTER TABLE "TradingRoute" ADD COLUMN "Taxes" numeric; -- 2) Set the derived attribute taxes as 12% of LastYearRevenue UPDATE "TradingRoute" SET "Taxes" = "LastYearRevenue"*12/100; -- 3) Report the operating company and the sum of its taxes group by company. SELECT "OperatingCompany",SUM(TRUNC("Taxes", 4)) AS "Total taxes" FROM "TradingRoute" GROUP BY "OperatingCompany"; -- 4.2 What's the longest trading route in parsecs? -- 1) Create a dummy table RouteLength to store the trading route and their lengths. CREATE TABLE "RouteLength" ("RouteMonitoringKey" INTEGER,"RouteTotalDistance" NUMERIC); -- 2) Create a view EnrichedCallsAt that brings together trading route, space stations and planets. CREATE VIEW "EnrichedCallsAt" AS SELECT "CallsAt"."MonitoringKey", "CallsAt"."StationID", "SpaceStation"."PlanetID", "CallsAt"."VisitOrder" FROM "CallsAt" INNER JOIN "SpaceStation" ON "CallsAt"."StationID" = "SpaceStation"."StationID"; -- 3) Add the support to execute an anonymous code block as follows; DO $$ DECLARE -- 4) Within the declare section, declare a variable of type real to store a route total distance. "RouteDistance" real := 0.0; -- Trading route total distance -- 5) Within the declare section, declare a variable of type real to store a hop partial distance. "PartialDistance" real; -- 6) Within the declare section, declare a variable of type record to iterate over routes. "route" record; -- 7) Within the declare section, declare a variable of type record to iterate over hops. "hop" record; -- 8) Within the declare section, declare a variable of type text to transiently build dynamic queries. "query" text; -- 9) Within the main body section, loop over routes in TradingRoutes BEGIN FOR route IN SELECT "MonitoringKey" FROM "TradingRoute" LOOP -- 10) Within the loop over routes, get all visited planets (in order) by this trading route. query := 'CREATE VIEW "PortsOfCall" AS ' ||'SELECT "PlanetID" ,"VisitOrder"' ||'FROM "EnrichedCallsAt"' ||'WHERE "MonitoringKey" = ' || route."MonitoringKey" ||'ORDER BY "VisitOrder"' ; -- 11) Within the loop over routes, execute the dynamic view EXECUTE query; -- 12) Within the loop over routes, create a view Hops for storing the hops of that route. CREATE VIEW "Hops" AS SELECT origin."PlanetID" AS origin, destination."PlanetID" AS destination FROM "PortsOfCall" origin INNER JOIN "PortsOfCall" destination ON destination."VisitOrder" = origin."VisitOrder"+1 ORDER BY destination."VisitOrder"; -- 13) Within the loop over routes, initialize the route total distance to 0.0. "RouteDistance" := 0.0; -- 14) Within the loop over routes, create an inner loop over the hops FOR hop IN SELECT origin, destination FROM "Hops" LOOP -- 15) Within the loop over hops, get the partial distances of the hop. query := 'SELECT "AvgDistance" ' || 'FROM "Distance" ' || 'WHERE "Distance"."PlanetOrigin" = $1 ' || 'AND "Distance"."PlanetDestination" = $2 '; -- 16) Within the loop over hops, execute the dynamic view and store the outcome INTO the hop partial distance. EXECUTE query INTO "PartialDistance" USING hop.origin, hop.destination; -- 17) Within the loop over hops, accumulate the hop partial distance to the route total distance. "RouteDistance" := "RouteDistance"+ "PartialDistance"; -- 18) Go back to the routes loop and insert into the dummy table RouteLength the pair (RouteMonitoringKey,RouteTotalDistance). END LOOP; INSERT INTO "RouteLength" ("RouteMonitoringKey", "RouteTotalDistance") VALUES (route."MonitoringKey", "RouteDistance"); -- 19) Within the loop over routes, drop the view for Hops (and cascade to delete dependent objects). DROP VIEW "Hops" CASCADE; -- 20) Within the loop over routes, drop the view for PortsOfCall (and cascade to delete dependent objects). DROP VIEW "PortsOfCall" CASCADE; END LOOP; END; $$; -- 21) Finally, just report the longest route in the dummy table RouteLength. SELECT "RouteMonitoringKey" AS "Longest route monitoringKey", "RouteTotalDistance" AS "Total length in parsecs" FROM "RouteLength" WHERE "RouteTotalDistance" = ( SELECT MAX("RouteTotalDistance") FROM "RouteLength" ); \c postgres postgres