can anyone explain me how to connect these tables?
I have to be able to list population in a country, list population in a city and list number of employees in a company for each city it is present in! The only limitation is that city name is unique and company can be present only once in one city but can have many branches in one country.
I have made country, company and city tables but I cannot understand how to connect them properly and get this functionality. I watched some youtube tutorials and read many posts but I still can't seem to make it work. If someone can help I would appreciate it. Thanks a lot guys!
One entity that is missing from your list is branch (of the company). The table definitions are then:
CREATE TABLE country (
id integer NOT NULL PRIMARY KEY,
name varchar NOT NULL,
population integer NOT NULL
);
CREATE TABLE city (
id integer NOT NULL PRIMARY KEY,
name varchar NOT NULL,
population integer NOT NULL,
country integer NOT NULL REFERENCES country(id)
);
Simple. You have a list of countries and a list of cities. The city records have a column country that references the proper record in the country table. City name is not set UNIQUE here because different countries can have a city by the same name (English, Dutch, Portuguese and Spanish empires created new cities with old names which were sometimes distinguishable (New York) but not always (such as Melbourne)). Append the UNIQUE clause to the name column if you really want the city name to be unique.
CREATE TABLE company (
id integer NOT NULL PRIMARY KEY,
name varchar NOT NULL
);
CREATE TABLE branch (
company integer NOT NULL REFERENCES company(id),
city integer NOT NULL REFERENCES city(id),
name varchar,
employees integer NOT NULL,
PRIMARY KEY (company, city)
);
The trick here is that the PK for the company branch is defined on the company identifier and the city identifier combined. A PK is always unique in the table.
Assuming that you want population counts and employees in a single query, this would do the trick:
SELECT
company.name AS company,
branch.name AS branch,
city.name AS city,
branch.employees,
city.population AS city_pop,
country.population AS country_pop
FROM company
JOIN branch ON branch.company = company.id
JOIN city ON city.id = branch.city
JOIN country ON country.id = city.country
ORDER BY company.name, branch.name;
Related
I need to create a function in PostgreSQL using the pl/pgSQL language. I have the following four tables:
CREATE TABLE person (
id serial PRIMARY KEY,
name text NOT NULL,
town_id integer NOT NULL,
country_id integer NOT NULL,
FOREIGN KEY (town_id) references towns (id),
FOREIGN KEY (country_id) references countries (id)
);
CREATE TABLE towns (
id serial PRIMARY KEY,
name text UNIQUE NOT NULL
);
CREATE TABLE countries (
id serial PRIMARY KEY,
name text UNIQUE NOT NULL
);
Now I want to create a function that receives a person record and normalizes this into these tables. The example record is:
name: Jakob
town: Los Angeles
country: USA
In other words, I want to store unique town names in the table towns, and I want to store unique county names in the table countries. I would thus like to implement a "get-or-create" that checks whether a value already exists, and if yes, fetch the primary key. Otherwise, I want to insert the town/country based on the provided name, and fetch the id. Then insert this in the person table.
How would this work as a function in the pl/pgSQL language?
I need to create unique index for table with two fields (email, alternative_email). It means one email address can be mentioned only one time in two columns. Also alternative email can be empty.
CREATE TABLE customers (
id serial PRIMARY KEY,
email VARCHAR (255) NOT NULL,
alternative_email VARCHAR (255) NOT NULL default ''
);
Data:
insert into customers (email, alternative_email)
values ('test#example.com', ''); - ok
insert into customers (email, alternative_email)
values ('test1#example.com', 'test#example.com');
Second row should not be inserted because alternative_email = 'test#example.com' already mentioned as email in first row.
How to create index to do this ?
You cannot create an index across two columns like that. What you need to do is change your design such that emails are all in one column in a different table.
So you could have a table called emails like:
CREATE TABLE emails (
id serial PRIMARY KEY,
email VARCHAR (255) NOT NULL UNIQUE,
);
Then have a many-to-many table that maps the customers to emails:
CREATE TABLE customer_emails (
customer_id INTEGER NOT NULL REFERENCES customers(id),
email_id INTEGER NOT NULL UNIQUE REFERENCES emails(id),
alternative boolean NOT NULL DEFAULT FALSE,
PRIMARY KEY (customer_id, email_id)
);
CREATE UNIQUE INDEX customer_email_idx ON customer_emails(customer_id, alternative);
This enforces that 2 different customers cannot reuse the same email address, that a customer can have at most one primary and one alternative email address, and the same email address cannot be used for both primary and alternative email addresses of the same customer.
I'm creating a database of assessments for courses using PostgreSQL.
I'd like assessment names to be unique within the course, but two courses can have assessments with the same name.
-- assessment contains the different assignments & labs that
-- students may submit their code to.
CREATE TABLE assessment (
id SERIAL PRIMARY KEY,
name VARCHAR(255) UNIQUE NOT NULL,
comments TEXT NOT NULL,
type ASSESSMENT_TYPE NOT NULL,
course_id SERIAL NOT NULL,
FOREIGN KEY (course_id) REFERENCES courses(id)
);
-- courses contains the information about a course. Since
-- the same course can run multiple times, a single course
-- is uniquely identified by (course_code, year, period)
CREATE TABLE courses (
id SERIAL PRIMARY KEY,
name VARCHAR(255) UNIQUE NOT NULL, -- Unique within all courses. Wrong!
course_code VARCHAR(20) NOT NULL,
period PERIOD NOT NULL,
year INTEGER NOT NULL
);
Two main points:
Can I do this without changing the schema?
If so, is there a more idiomatic solution that may include schema changes?
1. Can I do this without changing the schema?
No, since you have multiple issues here.
Your assessments are globally unique by name and not within a course.
assessment.course_id has its own sequence which is useless (SERIAL is just INTEGER + SEQUENCE)
Table courses defines a column data type that does not exist: PERIOD (at least not up to version 11)
2. If so, is there a more idiomatic solution that may include schema changes?
A modified schema that should do what you want would look like this following:
CREATE TABLE courses (
id SERIAL PRIMARY KEY,
name VARCHAR(255) NOT NULL,
course_code VARCHAR(20) NOT NULL,
period tstzrange NOT NULL
);
-- the following is required to build the proper unique constraint...
CREATE EXTENSION IF NOT EXISTS btree_gist;
-- the unique constraint: no two courses with same name at any point in time
ALTER TABLE courses
ADD CONSTRAINT idx_unique_courses
EXCLUDE USING GIST (name WITH =, period WITH &&);
CREATE TABLE assessment (
id SERIAL PRIMARY KEY,
name VARCHAR(255) NOT NULL,
comments TEXT NOT NULL,
type ASSESSMENT_TYPE NOT NULL,
course_id INTEGER NOT NULL REFERENCES courses(id),
UNIQUE (course_id, name)
);
I have this table containing these columns and I want to make it satisfy the 3NF. What I did was move DOB, City, Province, Postcode to another table called 2ndCus. However, I am still not sure if I did it correctly or not. What do you think?
CustomerID
LastName
FirstName
DateofBirth
Address
City
Province
PostCode
Email
Phone#
Well, without knowing your total requirement, I can't be sure, but a reasonable guess look something like this:
CUSTOMERS
---------
CUSTOMER_ID
LAST_NAME
FIRST_NAME
DOB
ADDRESSES
---------
ADDRESS_ID
ADDRESS_TYPE
ADDRESS
CITY
PROVINCE
POSTCODE
EMAIL_ADDRESSES
---------------
EMAIL_ID
EMAIL_TYPE
EMAIL_ADDDRESS
PHONE_NUMBERS
-------------
PHONE_NUMBER_ID
PHONE_NUMBER_TYPE
COUNTRY_CODE
AREA_CODE
PHONE_NUMBER
And then you can have intersection tables for the many-to-many relationships, such as:
CUSTOMER_ADDRESSES
------------------
CUSTOMER_ID
ADDRESS_ID
CUSTOMER_EMAIL_ADDRESSES
------------------------
CUSTOMER_ID
EMAIL_ID
CUSTOMER_PHONE_NUMBERS
----------------------
CUSTOMER_ID
PHONE_NUMBER_ID
This is just one example, it can get much more involved than this.
One other thought: When it comes to address types, email types, phone number types, etc, those could be implemented via a check constraints or valid tables, depending on the amount of "churn" you have in add/removing types.
Hope that helps.
I'm struggling with postgreSQL, as I don't know how to link one instance of type A to a set of instances of type B. I'll give a brief example:
Let's say we want to set up a DB containing music albums and people, each having a list of their favorite albums. We could define the types like that:
CREATE TYPE album_t AS (
Artist VARCHAR(50),
Title VARCHAR(50)
);
CREATE TYPE person_t AS (
FirstName VARCHAR(50),
LastName VARCHAR(50),
FavAlbums album_t ARRAY[5]
);
Now we want to create tables of those types:
CREATE TABLE Person of person_t WITH OIDS;
CREATE TABLE Album of album_t WITH OIDS;
Now as I want to make my DB as object-realational as it gets, I don't want to nest album "objects" in the row FavAlbums of the table Person, but I want to "point" to the entries in the table Album, so that n Person records can refer to the same Album record without duplicating it over and over.
I read the manual, but it seems that it lacks some vital examples as object-relational features aren't being used that often. I'm also familiar with the realational model, but I want to use extra tables for the relations.
Why you create a new type in postgresql to do what you need?
Why you don't use tables directly?
With n-n relation:
CREATE TABLE album (
idalbum integer primary key,
Artist VARCHAR(50),
Title VARCHAR(50)
);
CREATE TABLE person (
idperson integer primary key,
FirstName VARCHAR(50),
LastName VARCHAR(50)
);
CREATE TABLE person_album (
person_id integer,
album_id integer,
primary key (person_id, album_id),
FOREIGN KEY (person_id)
REFERENCES person (idperson),
FOREIGN KEY (album_id)
REFERENCES album (idalbum));
Or with a "pure" 1-n relation:
CREATE TABLE person (
idperson integer primary key,
FirstName VARCHAR(50),
LastName VARCHAR(50)
);
CREATE TABLE album (
idalbum integer primary key,
Artist VARCHAR(50),
Title VARCHAR(50),
person_id integer,
FOREIGN KEY (person_id)
REFERENCES person (idperson)
);
I hope that I help you.
Now as I want to make my DB as object-realational as it gets, I don't want to nest album "objects" in the row FavAlbums of the table Person, but I want to "point" to the entries in the table Album, so that n Person records can refer to the same Album record without duplicating it over and over.
Drop the array column, add an id primary key column (serial type) to each table, drop the oids (note that the manual recommends against using them). And add a FavoriteAlbum table with two columns (PersonId, AlbumId), the latter of which are a primary key. (Your relation is n-n, not 1-n.)
Sorry for answering my own question, but I just wanted to give some pieces of information I gained by toying around with that example.
ARRAY Type
I found out that the ARRAY Type in PostgreSQL is useful if you want to associate a variable number of values with one attribute, but only if you can live with duplicate entries. So that technique is not suitable for referencing "objects" by their identity.
References to Objects/Records by identity
So if you want to, as in my example, create a table of albums and want to be able to reference one album by more than one person, you should use a separate table to establish these relationships (Maybe by using the OIDs as keys).
Another crazy thing one could do is referencing albums by using an ARRAY of OIDs in the person table. But that is very awkward and really does not improve on the classic relational style.