3NF - Did I do it right? - database-normalization

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.

Related

PostgreSQL: How to store money with multiple currencies?

I have an application that handles products and sales in different currencies. So every row in the same table in the database can store prices in different currencies. How to correctly do that?
The most straightforward way is to define a numeric price_amount column and varchar price_currency oolumn, but I feel that two technically independent columns for essentially single value (price) is wrong. Like physical measurements are meaningless numbers without units, amount of money is also meaningless without their units–currency.
In my opinion money should be a single value containing both amount and currency within itself.
I started searching and got surprised a bit that there are no ready solutions or good articles in search results. There is pg-currency extension that does what I want, but it was abandoned almost 10 years ago.
I created the following composite datatype as a starting point:
CREATE TYPE true_money AS (
currency varchar,
amount numeric
);
And then starting to write supporting things for it: validations, arithmetic, aggregates… And realized that this rabbit hole is truly deep.
All my current (partial) results over this composite type can be found here for reference: https://gist.github.com/Envek/780b917e72a86c123776ee763b8dd986?fbclid=IwAR2GxGUVPg5FtN3SSPhQv2uFA7oPNNjbZeTYWRix-ZijYaJFRec15chWLA8#file-true_money-sql
And now I can do following things:
INSERT INTO "products" ("title", "price") VALUES ('Гравицапа', ('RUB',100500));
INSERT INTO "products" ("title", "price") VALUES ('Эцих с гвоздями', ('RUB',12100.42));
INSERT INTO "products" ("title", "price") VALUES ('Gravizapa', ('USD',19999.99));
-- You can access its parts if you need to extract them or do filtering or grouping
SELECT SUM(price) FROM test_monetaries WHERE (price).currency = 'RUB';
-- (RUB,112600.42)
-- And if you forget filtering/grouping then custom types can save you from nonsense results
SELECT SUM(price) FROM test_monetaries;
ERROR: (USD,19999.99) can not be added to (RUB,112600.42) - currencies do not match
Is it a correct approach? How to do it right?
A bit of context: in our app, users (sellers) can manage their stock (products) in any currency they want (e.g., USD, EUR, JPY, RUB, whatever). The app will convert currencies and publish products in local sites (like British or Australian). Buyers will also buy these goods in their local currency (GBP, AUD, etc.) that will eventually be converted to seller currency and paid to them (except fees). So in many places in the application, almost any supported currency can appear. And finally, the seller should be able to change their currency and all products should be converted to new currency in batches (single update within transaction can't be used by some reasons). So we can't say “keep only numeric values in the products table and JOIN with the sellers table to get currency” (which is anti-pattern per se, I believe).
Yes, creating your own type is quite a lot of work if you want to integrate it seamlessly with PostgreSQL.
If an item can be sold in different countries and has a different price everywhere, you should model the data accordingly. Having an exchange rate is not good enough, because the same item might be more expensive in Japan than in China.
If you are only interested in the current price, that could look like this:
CREATE TABLE currency (
currency_id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
denomination text CHECK (length(denomination) = 3) NOT NULL
);
CREATE TABLE exchange (
from_curr_id bigint REFERENCES currency NOT NULL,
to_curr_id bigint REFERENCES currency NOT NULL,
rate numeric(10,5) NOT NULL
);
CREATE TABLE country (
country_id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
name text UNIQUE NOT NULL,
currency_id bigint REFERENCES currency NOT NULL
);
CREATE TABLE product (
product_id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
title text NOT NULL,
);
CRATE TABLE price (
country_id bigint REFERENCES country NOT NULL,
product_id bigint REFERENCES product NOT NULL,
amount numeric(10,2) NOT NULL,
PRIMARY KEY (product_id, country_id)
);
CREATE INDEX ON price (country_id); -- for the foreign key
This way, each product can have a certain price in each country, and the price is associated with a currency via the country.
Of course, the real world might be still more complicated:
you could have more than one currency per country
you might want to keep historical price information
The main thing is that you can always follow a chain of foreign keys that leads you to the desired amount and currency unambiguously.
For converting between currencies

Model `select by partition key in Cassandra` in BigTable

What would the equivalent of modeling a select by partition key in Cassandra be in BigTable?
For example; if I had a Cassandra table
CREATE TABLE emp (
empID int,
deptID int,
first_name varchar,
last_name varchar,
PRIMARY KEY (empID, deptID));
I can query
SELECT deptid FROM emp WHERE empid = 104;
In BigTable; I think this is equivalent to adding columns to a Row?
If so is that a relatively standard design pattern?
Or if not; is there another pattern that can be used?
Thanks
Brent
This is mostly addressed in the comments. Bigtable does not have separate partition key and primary key concepts and only has a single index.
Your example you would probably want to make both your employee ID and department ID part of your row key. Keys are stored lexicographically and you can use prefixes to do more efficient subscans, so you would need to determine whether to concatenate either employee ID followed by department ID, or vice versa.
This is somewhat akin to the reverse domain name pattern and you may want to review the guidance suggested here:
https://cloud.google.com/bigtable/docs/schema-design#types_of_row_keys

Foreign key from multiple tables

I am thinking of three tables;
Employee with emp_id an, emp_name and other related data.
and
Person table with person_id, person_name and other related data.
and
GroupList with grp_list_id, member_id (either emp_id or person_id) and is_employee (true for employee and false for person) and other related data.
This is the first time I am trying to do a table whose foreign key can come from two different tables. Can someone please suggest the best way to achieve it?

primary and foreign keys in postgresql - total beginner

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;

Get set of all existing values in a column in PostgreSQL

Have a table with about 500,000 rows. One of the columns is a string field.
Is there a way to get the set of all existing values of that string in PostgreSQL without having to request each row out of the database and add the values to a set manually?
Example:
first_name last_name
will i.am
will smith
britney spears
The set of all existing values for "first_name" would be ['will', 'britney'].
SELECT DISTINCT first_name FROM people;
or
SELECT first_name FROM people GROUP BY first_name;