DB_HOST
DB_USER
DB_PWD
DB_NAME
DB_SOCKET
client
folder and run npm i
server
and run npm i && npm run dev
We chose to create a "Quality of life comparer" where the user can see, for example, living costs and housing prices of many different cities and compare against another.
The main reason for this application is to find out the cost of living and housing for a certain city or country.
Example of questions you would like the answer to:
In using the "Quality of life" application the user gets the answer to these questions and can prepare travel or move to a city of their choice.
The data is collected from Teleport.org.
We chose to separate the data of the cities into two entities (housing and livingCost) in order for the city table to be as independent as possible since housing and livingCost might be added later on. We separated countries from cities in order to be able to get details from multiple cities in one country, which we are using in the application when the user wants to compare the average costs between the countries. LivingCost and housing are separated since we thought that they are different categories and therefore it would be easier to add another table to link a city to, for example if we would like to add education statistics.
Each country in countries
can hold 0 or many cities and each city in cities
can only have a relation to one and only one country. Each city can have 0 or one housing and livingCost, while both housing and livingCost can only have one, and only one city related to it.
For this table, we used country_id as a primary key to hold each country’s name that we put into our database. The id is auto-increment and we used not null and unique for each country name.
CREATE TABLE countries (
country_id INT AUTO_INCREMENT,
country VARCHAR(50) NOT NULL UNIQUE,
PRIMARY KEY (country_id)
);
For this table, we also included a city_id
as a primary key and on auto-increment to have a relation between the other tables. We store the city
- attribute as the name of the city and information gathered from the API. The foreign key is the country_id
which has a relation to the countries
entity.
CREATE TABLE cities (
city_id INT PRIMARY KEY AUTO_INCREMENT,
city VARCHAR(50) NOT NULL UNIQUE,
information TEXT,
country_id INT NOT NULL,
FOREIGN KEY (country_id) REFERENCES countries(country_id)
);
For this table, we use the city_id
as the primary key to hold each tuple since it’s dependent on a specific city. The table has the foreign key of city_id
to create a relation between livingcost and cities entity and to link each livingcost to the correct city. The rest of the attributes are floats, gathered from the API we used which collects prices of each. We used ON DELETE CASCADE
to delete a tuple when deleting a city.
CREATE TABLE livingcost (
city_id INT NOT NULL,
taxi FLOAT,
cappuchino FLOAT,
beer FLOAT,
gym_membership FLOAT,
monthly_public_transport FLOAT,
lunch FLOAT,
PRIMARY KEY (city_id),
FOREIGN KEY (city_id) REFERENCES cities(city_id)
ON DELETE CASCADE
);
For this table, we also used the city_id
as primary key and foreign key since there can be only one city related to each tuple. The rest of the attributes are floats, gathered from the API we used which collects prices of each. We used ON DELETE CASCADE
to delete a tuple when deleting a city.
CREATE TABLE housing (
city_id INT NOT NULL,
large_appt INT,
medium_appt INT,
small_appt INT,
rent_index FLOAT,
PRIMARY KEY (city_id),
FOREIGN KEY (city_id) REFERENCES cities(city_id)
ON DELETE CASCADE
);
Note: All red values in the queries are variables/objects
INSERT IGNORE INTO countries (country) VALUES (?), country.name;
We loop through each country provided from the API and insert each country.name
into the table countries
on the attribute country
. If the API provides ut with duplicates, we ignore those using the INSERT IGNORE
statement.
SELECT country_id FROM countries WHERE country="country.name";
When inserting cities to the database, we first select the country_id
related to that city which is needed while inserting the city to the database since it’s related to a country_id
.
INSERT IGNORE INTO cities (city, country_id) VALUES(?,?), city.name, result.country_id;
We loop through all cities from the API and perform a query for each city where we get city.name
from the API and result.country_id
is gained from the query above. If the API provides ut with duplicates, we ignore those using the INSERT IGNORE
statement.
INSERT INTO livingcost SET ? obj;
We create an object (obj) that contains the needed values for creating a tuple in livingcost
which we insert to livingcost
. The SET
method inserts each value in the object to the correct position.
INSERT INTO housing SET ? obj;
Same procedure as the above query, but for housing.
UPDATE cities SET information=description WHERE city=data.city;
We struggled with inserting the city description at the same time as inserting a city and therefore we chose to perform a query to update the tuples in cities
after. data.city
is the description provided from the API.
SELECT city, city_id FROM cities;
This is a help-function that is needed when inserting city descriptions, livingcost, and housing since they are all related to a city.
SELECT city, city_id, country_id FROM cities ORDER BY city;
This query is needed to display the dropdown fields with all cities listed in them, in order for the user to pick two cities to compare. We query the country_id
in order to know what country the city belongs to, which is later used if the user wants to compare the average costs between countries in the cities selected.
SELECT cities.city, livingcost.*, housing.*, cities.information
FROM livingcost
INNER JOIN housing ON livingcost.city_id = housing.city_id
INNER JOIN cities ON livingcost.city_id = cities.city_id
WHERE housing.city_id IN (body.first, body.second);
This is where we get all the necessary information for a specific city, such as city name, living costs, housing costs, and city description. body.first
is the city selected from the first dropdown field and body.second
is the other city selected from the second dropdown field.
SELECT country
FROM countries
WHERE country_id IN (
SELECT country_id
FROM cities
WHERE city_id = city);
This query is performed when the user wants to compare the average costs in the countries that the cities belong to, where we first select the country_id
for the city and then selecting the country
(country name) from the countries table.
CREATE OR REPLACE VIEW CountryAverage AS
SELECT
TRUNCATE(AVG(livingcost.taxi), 2) AS 'taxi',
TRUNCATE(AVG(livingcost.cappuchino), 2) AS 'cappuchino',
TRUNCATE(AVG(livingcost.beer), 2) AS 'beer',
TRUNCATE(AVG(livingcost.gym_membership), 2) AS 'gym',
TRUNCATE(AVG(livingcost.monthly_public_transport), 2) AS 'public_transport',
TRUNCATE(AVG(livingcost.lunch), 2) AS 'lunch',
TRUNCATE(AVG(housing.small_appt), 2) AS 'small_appt',
TRUNCATE(AVG(housing.medium_appt), 2) AS 'medium_appt',
TRUNCATE(AVG(housing.large_appt), 2) AS 'large_appt',
TRUNCATE(AVG(housing.rent_index), 2) AS 'rent_index'
FROM livingcost
JOIN housing ON livingcost.city_id = housing.city_id
WHERE livingcost.city_id IN (
SELECT city_id FROM cities
WHERE country_id IN (
SELECT country_id
FROM countries
WHERE country_id IN (
SELECT country_id
FROM cities
WHERE city_id = city )));
We create a view for this long query that is used to provide the average costs for all details that a city has. We use TRUNCATE
in order to limit the number of decimals to 2 since we otherwise would get too many decimals with the results. AVG
is used to get the average. We chose to rename the columns by giving them aliases because we weren’t able to target the columns when they had parenthesis.
SELECT * FROM CountryAverage;
Here we select the results from the view created above.
The implementation can be found here.
life-quality-comparer
cd server
npm install && npm run server
cd ..
cd client
npm install && npm run dev
server
directory.
Change the variables to fit your environment.DB_HOST = localhost;
DB_USER = root;
DB_PWD = root;
DB_NAME = life - quality - comparer;
DB_SOCKET = 'optional socket port path';
Visit localhost:5000 in your web browser.