relational model query for simple person-car example - sql

I'd like some insight on a relational model I have created for PostgreSQL. It has to do with person and car relationships.
CREATE TABLE "person" (
"id" serial NOT NULL PRIMARY KEY,
"name" varchar(300) NOT NULL,
"car_id" integer REFERENCES car (id));
CREATE TABLE "car" (
"id" serial NOT NULL PRIMARY KEY,
"type" varchar(50) NOT NULL);
CREATE TABLE "car_person_relations" (
"id" serial NOT NULL PRIMARY KEY,
"car_type" varchar(50) NOT NULL REFERENCES "car" ("type"),
"person_id" integer NOT NULL REFERENCES "person" ("id"));
Ultimately, I'd like to get the most popular car type based on how many people have it,
i.e. how many "person"s it is associated with. What query can I use to achieve this? And is this relational table (car_person_relations) sufficient to achieve it?
Any insight would be much appreciated

If I understand correctly, this is a simple aggregation with some limiting:
select car_type, count(*) as num_persons
from car_person_relations cpr
group by car_type
order by count(*) desc
limit 1;

First off take a look at your data model, it has a couple fundamental problems.
In CAR_PERSONS_RELATIONS table you are attempting to create a FK on car_type. However, to do so car_type would have to be
unique in the car table. From the documentation section 5.4.5. Foreign Keys .
A foreign key constraint specifies that the values in a column (or a group of columns) must match the values
appearing in some row of another table. We say this maintains the
referential integrity between two related tables. ... A foreign key
must reference columns that either are a primary key or form a unique
constraint.
But this would there could only be 1 car for a given type in the
table.
Now look at the PERSON table. This table contains a FK to car_id, thus establishing a M:1 to CAR. Basically this relationship
says "A person can own only 1 car, but a car can be owned by many persons". Your description and table name indicate
this is NOT the relationship you are trying to define.
Correcting requires the following changes:
Add car_id to CAR_PERSON_RELATIONS.
Remove car_id from PERSON.
Remove car_type from CAR_PERSON_RELATIONS.
Redefine CAR_PERSON_RELATIONS PK to car_id, person_id. Alternately
leave the surrogate PK and create UK on car_id,person_id).
Create the FKs from CAR_PERSON_RELATIONS to CAR and PERSON.
Revised Model
create table person (
id serial
,name varchar(300) not null
,constraint person_pk primary key (id)
);
create table car (
id serial
,type varchar(50) not null
,constraint car_pk primary key (id)
);
create table car_person_relations (
car_id integer
,person_id integer
,constraint car_person_relations primary key (car_id, person_id)
,constraint car_person_relations_2_car_fk
foreign key (car_id)
references (car.id)
,constraint car_person_relations_2_person_fk
foreign key (person_id)
references (person.id)
):
And then the necessary query becomes:
select cpr.car_type, count(*) as num_persons
from car_person_relations cpr
join car c
on cpr.car_id = c.id
group by cpr.car_type
order by count(*) desc
limit 1;

Related

Design sql tables with list of foreign keys

I want to create an application for rotating pairs in a team every day. I need to store this in the database. Requirments are:
A team should be assigned to one ore more members.
Each team can have multiple tabs and different members allocate in them.(If team consist of 4 members for the particular tab only 3 should be part of it)
Each tab will have a pair of members or list of pairs per day stored.
I have ended up designing something like the example below:
create table if not exists team (
id serial not null primary key,
name text not null
);
create table if not exists member (
id serial not null primary key,
team_id integer references team(id),
nickname text
);
create table if not exists team_tab (
id bigserial not null primary key,
team_id integer references team(id) on delete cascade,
name text not null,
member_ids integer[],
);
create table if not exists team_tab_pairs (
id bigserial not null primary key,
team_tab_id integer not null references team_tab(id) on delete cascade,
tab_date date not null,
pair_ids integer[][],
);
I need an advice and suggestions how could I achieve this without having a list of references ids stored in the 2 tables below.
You need an extra table to design an M:N relationship. This is the case, for example, between "team tab" and "member". In addition to both main entities:
create table member (
id serial not null primary key,
team_id integer references team(id),
nickname text
);
create table team_tab (
id bigserial not null primary key,
team_id integer references team(id) on delete cascade,
name text not null
);
...you'll need to create a table to represent the M:N relationship, as in:
create table team_tab_member (
team_tab_id bigint not null,
member_id int not null,
primary key (team_tab_id, member_id) -- optional depending on the model
);

multiple foreign keys as primary key postgres, should I do it?

This is one of those: why I should or why I should not.
So my books app has reviews, but one user must not review the same book more than one time. In my point of view makes sense to create a table for the reviews and make the user_id and the book_id (ISBN) as the PRIMARY KEY for the reviews table. But, could it be a problem at some point if the application gets too many reviews, for example, could that decision slow the queries?
I am using postgres and I am not sure if the following code is correct:
CREATE TABLE users(
user_id PRIMARY KEY SERIAL,
user_name VARCHAR NOT NULL UNIQUE,
pass_hash VARCHAR NOT NULL,
email VARCHAR NOT NULL UNIQUE,
);
CREATE TABLE books(
book_id PRIMARY KEY BIGINT,
author VARCHAR NOT NULL,
title VARCHAR NOT NULL,
year INT NOT NULL CHECK (year > 1484),
review_count INT DEFAULT 0 NOT NULL,
avrg_score FLOAT,
);
CREATE TABLE reviews(
user_id INT FOREIGN KEY REFERENCES users(user_id) NOT NULL
book_id INT FOREIGN KEY REFERENCES books(book_id) NOT NULL
score INT NOT NULL CHECK (score > 0, score < 11)
PRIMARY KEY (book_id, user_id)
);
This is a perfectly valid design choice.
You have a many-to-many relationship between books and users, which is represented by the reviews table. Having a compound primary key based on two foreign keys lets you enforce referential integrity (a given tuple may only appear once), and at the same time provide a primary key for the table.
Another option would be to have a surrogate primary key for the bridge table. This could make things easier if you need to reference the reviews from another table, but you would still need a unique constraint on both foreign key columns for integrity, so this would actually result in extra space being used.
When it comes to your code, it has a few issues:
the primary key keyword goes after the datatype
the check constraint is incorrectly formed
missing or additional commas here and there
Consider:
CREATE TABLE users(
user_id SERIAL PRIMARY KEY ,
user_name VARCHAR NOT NULL UNIQUE,
pass_hash VARCHAR NOT NULL,
email VARCHAR NOT NULL UNIQUE
);
CREATE TABLE books(
book_id BIGINT PRIMARY KEY,
author VARCHAR NOT NULL,
title VARCHAR NOT NULL,
year INT NOT NULL CHECK (year > 1484),
review_count INT DEFAULT 0 NOT NULL,
avrg_score FLOAT
);
CREATE TABLE reviews(
user_id INT REFERENCES users(user_id) NOT NULL,
book_id INT REFERENCES books(book_id) NOT NULL,
score INT NOT NULL CHECK (score > 0 and score < 11),
PRIMARY KEY (book_id, user_id)
);
I the above is good but I would drop columns review_count and avrg_score from books. These are derivable when needed. If needed for your application then instead of storing these create a view to derive them. This avoids the always complicated process of maintaining running values:
create view books_vw as
select b.book_id
, b.author
, b.title
, b.year
, count(r.*) review_count
, avg(r.score) avrg_score
from books b
left join reviews r
on r.book_id = b.book_id
group by
b.book_id
, b.author
, b.title
, b.year
;

SQL Server: why are two foreign keys allowed on a column referencing to single column?

Why does SQL Server allow this?
create table dbo.tab1
(
id int primary key
)
create table dbo.tab2
(
id int constraint first_name references tab1(id),
constraint second_name foreign key (id) references tab1(id)
)
Here's an example that might clarify this a bit.
I have a person table which defines all of the people in the system. I then have a marriage table. A marriage is defined as a pairing of 1 Husband and 1 Wife, and both of these need to be valid entries in the Person table.
create table dbo.Person
(
id int primary key
)
create table dbo.Marriage
(
id int constraint Husband references tab1(id),
id2 int constraint Wife foreign key (id) references tab1(id)
)
Example was for clarity. Please forgive me for any political or cultural assumptions that may have gone into it.
Yes. This is quite interesting functionality wise. See if you are using parent child concept then it's helpful.
Example: If you have customer and dbo.Customer table. The customer distribute the commission after some recharge. So when you relate Customer to its parent chain you need the same column of customer table as foreign key twice.

Foreign key with many-to-many containment constraint

Quick question that I'm having trouble putting into search terms:
Suppose I have a many-to-many relationship between players and teams:
CREATE TABLE players (
id bigserial primary key,
name text not null
);
CREATE TABLE teams (
id bigserial primary key,
name text not null,
team_captain_id bigint not null references players(id)
);
CREATE TABLE team_players (
id bigserial primary key,
player_id bigint not null,
team_id bigint not null
);
ALTER TABLE team_players ADD CONSTRAINT uq_team_players UNIQUE (player_id,team_id);
Now, each team is required to have a team captain, who is also a player. But I want to enforce that the team captain is also a member of that team (or, semantically equivalent, that the team captain is not redundantly in the join table)
Is there a standard way to model this? I can think of several ways that would actually get the job done, but I'm wondering if there's a standard, elegant way of doing it.
Thanks!
EDIT: Although it would be nice to have the captain a required field, I would also be content with the following condition: If the team has at least 1 member, then a captain is defined for it.
EDIT 2: OK, here's an attempt for clarification. Pardon the unnecessary "id" columns.
CREATE TABLE players (
id bigserial primary key,
name text not null
);
CREATE TABLE teams (
id bigserial primary key,
name text not null
);
CREATE TABLE leaderships (
id bigserial primary key,
team_id bigint not null references teams(id),
captain_id bigint not null references players(id),
-- Make a key.
UNIQUE (team_id,captain_id),
-- Only one leadership per team.
UNIQUE (team_id)
);
CREATE TABLE team_players (
id bigserial primary key,
team_id bigint not null,
captain_id bigint not null,
player_id bigint not null,
-- One entry per player.
UNIQUE (team_id,captain_id,player_id),
-- Valid reference to a leadership.
FOREIGN KEY (team_id,captain_id) references leaderships(team_id,captain_id),
-- Not the captain.
CHECK (player_id <> captain_id)
);
You need to learn about database design.
Find fill-in-the-(named-)blank statements that describe your application. Each statement gets a table. A table holds the rows that make a true statement.
// [player_id] is a player
player(player_id)
// [team_id] is a team
team(team_id)
// player [player_id] plays for team [team_id]
team_players(team_id,player_id)
Turns out you don't need a player_team_id. The team_players (player_id,team_id) pairs are 1:1 with them so you can use those instead. On the other hand team-player contracts are 1:1 with them so they might have a role.
Every team_players player_id is a player player_id (since every team player is a player). We say that via a FOREIGN KEY delaration (and the DBMS enforces it):
FOREIGN KEY (team_id) REFERENCES team (team_id)
FOREIGN KEY (player_id) REFERENCES player (player_id)
It's true that team_players (player_id,team_id) is unique. But more than that is true. No contained subrow is unique. This matters to database design.
A unique subrow is a "superkey". A unique subrow containing no smaller unique subrow is a "key". Use KEY for that. Any superset of key columns is unique. But SQL requires that the target of a FOREIGN KEY be explictly declared so. Use UNIQUE for that. Traditionally in SQL you pick one key as PRIMARY KEY. This matters to some SQL functionality. (Technically, in SQL KEY means UNIQUE and PRIMARY KEY means UNIQUE NON NULL. Ie SQL does not enforce no-smaller-contained-unique-subrow.)
KEY (team_id,player_id)
(If you also had a team_player_id in team_players it too would be a KEY, typically the PK.)
Some players are captains. It's 1:1. So both team_id and player_id are unique.
// [player_id] captains [team_id]
team_captains(team_id,player_id)
FOREIGN KEY (team_id) REFERENCES team (team_id)
FOREIGN KEY (player_id) REFERENCES player (player_id)
KEY (team_id)
KEY (player_id)
A team-captain pair must appear as a team-player pair.
FOREIGN KEY (team_id,player_id) REFERENCES team_players (team_id,player_id)
Your thoughts on redundancy re captains is admirable. It is true that there is a sense in which it is redundant to have the database record that a person is a team's captain and that they are on a given team.
-- instead of having team_players(team_id,player_id)
-- team_players team_players FK now to here
// player [player_id] is a non-captain on team [team_id]
team_non_captains(team_id,player_id)
FOREIGN KEY (team_id) REFERENCES team (team_id)
FOREIGN KEY (player_id) REFERENCES player (player_id)
KEY (team_id,player_id)
However, every time you wanted the players on a team you'd have to say:
-- now team_player =
// player [player_id] is a non-captain on team [team_id]
// OR player [player_id] is captain of team [teamm_id]
select * from team_non_captains UNION select * from team_captains
It turns out it is probably worse to have one "redundant" row per captain than it is to have one "redundant" union operation (and "redundant" human parsing of a sub-expression) per query involving a whole team. Just make the most straightforward statements.
(Avoid nulls in an initial design. They complicate table meanings and query meanings. Especially query meanings because SQL does not evaluate expressions involving nulls in a way that means means anything in particular in terms of the meanings of tables in a query, let alone "not known" or "not applicable". One uses them as an engineering tradeoff which you must learn to judge.)
The simplest possible design, a real-world solution could possible involve more complexity.
a player is part of exactly one team so player.team_id is functionally dependent on player.id, and points to teams.id
a team has one captain which should be present in the players table
The captain should be part of the team, which requires an additional constraint teams.{team_id,captain_id} -> players.{team_id,id}
CREATE TABLE players
( id bigserial primary key
, team_id INTEGER NOT NULL
, name text not null
, UNIQUE (team_id, id) -- we need this because the FK requires it
);
CREATE TABLE teams
( id bigserial primary key
, captain_id bigint not null references players(id)
, name text not null
);
ALTER TABLE players
ADD FOREIGN KEY (team_id )
REFERENCES teams(id)
;
-- captain must be part of the team
ALTER TABLE teams
ADD FOREIGN KEY (id, captain_id )
REFERENCES players( team_id, id)
;
UPDATE it appears a player can take part of more than one team, so a N:M junction-table will be needed:
CREATE TABLE players
( player_id bigserial PRIMARY KEY
, name text not null
);
CREATE TABLE teams
( team_id bigserial PRIMARY KEY
, captain_id bigint
, name text not null
);
CREATE TABLE players_teams
(player_id INTEGER NOT NULL REFERENCES players(player_id)
, team_id INTEGER NOT NULL REFERENCES teams(team_id)
, PRIMARY KEY (player_id,team_id)
);
ALTER TABLE teams
ADD FOREIGN KEY (team_id,captain_id)
REFERENCES players_teams(team_id,player_id)
;

violated - parent key not found error

I have the following error appearing:
INSERT INTO GroupMembers VALUES ('Goldfrat', 'Simon Palm')
*
ERROR at line 1:
ORA-02291: integrity constraint (SHAHA1.IAM_IS_GROUP_FK) violated - parent key
not found
The constraint in the GroupMembers table is:
CONSTRAINT iam_is_group_fk FOREIGN KEY(is_group) REFERENCES Members(group_name)
The Members Table looks like this:
CREATE TABLE Members (
group_name VARCHAR2(40),
CONSTRAINT g_id_pk PRIMARY KEY(group_name),
CONSTRAINT m_group_name_fk FOREIGN KEY(group_name) REFERENCES Artist(artistic_name));
All of the tables are created fine until it comes to creating the GroupMembers table. Anyone have any ideas? I've been scratching for quite a while.
The problem is that
CONSTRAINT iam_is_group_fk FOREIGN KEY(is_group) REFERENCES Members(group_name); references the table Members on the group_name field.
This means that the field is_group on the table GroupMembers must have an identical value on the table Members and group_name field.
In my opinion, this is bad practice.
First of all, you should have a primary key field on the table GroupMembers. You should not store the names of the group members in the table GroupMembers, but their corresponding id from the table Members.
Also, the table Members should look something like this:
CREATE TABLE Members (
member_id NUMBER PRIMARY KEY
member_name VARCHAR2(40),
CONSTRAINT g_id_pk PRIMARY KEY(member_id),
CONSTRAINT m_group_name_fk FOREIGN KEY(group_name) REFERENCES Artist(artistic_name));
Then on the table GroupMembers, I suppose you want to associate some members to their set of groups and back, so you should do something like this:
CREATE TABLE GroupMembers (
member_id NUMBER,
group_id NUMBER
)
CONSTRAINT iam_is_member_fk FOREIGN KEY(member_id) REFERENCES Members(member_id);
CONSTRAINT iam_is_member_fk FOREIGN KEY(group_id) REFERENCES Groups(group_id);
Supposing that you have a table Groups containing all the group details, with the primary key stored as number , and name group_id.
Always remember that each table must have a primary key. It is good practice for this key to be a number.
So by having member_id in Members, group_id in Groups, you can create a many-to-many relationship in GroupMembers. Also, put a unique index on this table so you don't have duplicates ( the same member associated with the same id several times ).
Look at this example linking users to roles. It is the same case:
error is
you have to use same column name which is defined in reference table
i.e
CONSTRAINT m_group_name_fk FOREIGN KEY(group_name) REFERENCES Artist(group_name)
That group_name should be define as primary key in Artist Table.
Order in which you are insert is the reason for the error.
Members(group_name) does not contain Goldfrat. If this is not true then it's not there in the table Artists.