Database - Am i doing this right? - sql

I'm trying to make a simple database for a personal project, and I'm not sure whether i'm using Primary Keys properly.
Basically, the database contains users who have votes yes/no on many different items.
Example :
User "JOHN" voted YES on item_1 and item_2, but voted FALSE on item_3.
User "BOB" voted YES on item_1 and item_6.
User "PAUL" votes NO on item_55 and item_76 and item_45.
I want to use the following 3 tables (PK means Primary Key) :
1) table_users, which contains the columns "PK_userID" and "name"
2) table_items, which contains the columns "PK_itemID" and "item_name"
3) table_votes, which contains the columns "PK_userID", "PK_itemID", and "vote"
and the columns with the same name will be linked
Does it look like a proper way to use primary keys ? (so the table_votes will have two Primary Keys, being linked to the two other tables)
Thanks :)

Since user can vote for multiple items and multiple users can vote for a single item, you should not create following two primary keys in third table table_votes. Just create them as fields otherwise it will restrict you add only a userId or itemId only once. Yep, you should make them NOT NULL
"PK_userID", "PK_itemID",

No, that's not correct.
There can only be one primary key per table. You can have other columns with unique indexes that could have been candidate keys, but that's not the primary.
I think you'd have three tables:
create table PERSON (
PERSON_ID int not null identity,
primary key(PERSON_ID)
);
create table ITEM (
ITEM_ID int not null identity,
primary key(ITEM_ID)
);
create table VOTE (
PERSON_ID int,
ITEM_ID int,
primary key(PERSON_ID, ITEM_ID),
foreign key(PERSON_ID) references PERSON(PERSON_ID),
foreign key(ITEM_ID) references ITEM(ITEM_ID)
);
It's a matter of cardinality. A person can vote on many items; an item can be voted on by many persons.
select p.PERSON_ID, i.ITEM_ID, COUNT(*) as vote_count
from PERSON as p
join VOTE as v
on p.PERSON_ID = v.PERSON_ID
join ITEM as i
on i.ITEM_ID = v.PERSON_ID
group by p.PERSON_ID, i.ITEM_ID

This looks reasonable. However, I would not advise you to name your primary keys with a "PK_" prefix. This can be confusing, especially because I advise giving foreign keys and primary keys the same name (the relationship is then obvious). Instead, just name it after the table with Id as a suffix. I would recommend a table structure such as this:
create table Users (
UserId int not null auto_increment primary key,
Name varchar(255) -- Note: you probably want this to be unique
);
create table Items (
ItemId int not null auto_increment primary key,
ItemName varchar(255) -- Note: you probably want this to be unique
);
create table Votes (
UserId int not null references Users(UserId),
ItemId int not null references Items(ItemId),
Votes int,
constraint pk_UserId_ItemId primary key (UserId, ItemId)
);
Actually, I would be inclined to have an auto-incremented primary key in Votes, with UserId, ItemId declared as unique. However, there are good arguments for doing this either way, so that is more a matter of preference.

Related

How to create an SQL column that can have multiple values?

I created a table in SQL using PostgreSQL called "tenants". Below is the code for the tenants:
create table tentants (
id bigserial not null primary key,
tenant_name varchar(1000) not null,
offices int not null,
number int not null,
email varchar(1000)
I want to include the ability to add multiple values to "office" in case a tenant rents more than one office. I don't want to use JSON for this. I tried creating a related table called "offices" but That could only allow me to add one office per tenant.
What is the best approach for this?
You can use text, that works for me with ids separated by commas like this
4,3,67,2
Anyway the proper approach would be another table and name it tenant_offices
tenant_offices
columns >
tenant_id
office_id (well ofcourse you should have atleast an office table)
You can create an "tenant_offices" table (like you did before), having as structure :
id, tenant_id, office_id,... where id is the primary key of the "tenant_offices" table and tenant_id and office_id are foreign keys.
tenant_id which refers to your tenants table and office_id which refers to your offices table.
Here, the tenant can therefore rent several offices.
Hoping to have enlightened you, or helped !
I assume that the relationship is one-to-many tenant to office (that is, office can be rented only by one tenant)
Then you have to create table offices with a foreign key that points to the tenant:
CREATE TABLE offices (
id bigserial not null primary key,
tenant_id bigserial foreign key references tenants(id))
additional columns if needed
note that in this version you will not retain history of rents (you will have to run update on offices to change tenant_id)
EDIT: In case of a many-to-many relationship (that will also allow us to retain history of rents) we need to create a relationship table:
CREATE TABLE TenantsOffices (
id bigserial not null primary key
tenant_id bigserial foreign key references tenants(id),
office_id bigserial foreign key references offices(id),
start_date datetime,
end_date datetime)
Useful information: https://www.sqlshack.com/learn-sql-types-of-relations/

Modeling a 1:Many relationship with an attribute

I've come across a table design that immediately struck me as odd, but now that I've thought through it I can't seem to come up with a design that I'm really happy about.
The existing design (simplified) is:
CREATE TABLE Accounts (
account_id INT NOT NULL,
account_name VARCHAR(50) NOT NULL,
CONSTRAINT PK_Accounts PRIMARY KEY CLUSTERED (account_id)
)
CREATE TABLE Groups (
group_id INT NOT NULL,
group_name VARCHAR(50) NOT NULL,
CONSTRAINT PK_Groups PRIMARY KEY CLUSTERED (group_id)
)
CREATE TABLE Group_Accounts (
group_id INT NOT NULL,
account_id INT NOT NULL,
is_primary BIT NOT NULL,
CONSTRAINT PK_Group_Accounts PRIMARY KEY CLUSTERED (group_id, account_id)
)
While it looks like a standard many:many relationship, an account never actually belongs to more than one group. I immediately thought, "Ok, we could put the group_id into the Accounts table and that should work." But then what would I do with the is_primary attribute?
I could put an account_id into the Groups table as primary_account_id and then I believe that I could enforce RI with a foreign key on the primary_account_id, group_id to account_id, group_id.
Alternatively, I could move the "is_primary" flag into the Accounts table. Maybe that's the best solution?
Any thoughts on pros/cons for each approach? Am I missing any potential issues? Is there some other alternative that I've missed?
Is there any way to enforce a single primary account within a group in any of these situations outside of triggers (so primarily declarative RI)?
Thanks!
Relationship Cardinality
Judging by your description, you need 1:N relationship, which means you do not need the junction table Group_Accounts. Just a simple FK from Accounts to Groups should do.
Special Row
The next question is how you pick one row at the N side (Accounts) to be "special". You can either:
use the Accounts.is_primary flag and enforce its uniqueness (per group) through a filtered unique index (if your DBMS supports it),
or you could have a FK in Groups pointing to the primary account. In the latter case, though, you have to be careful to pick a primary account which actually belongs to the group.
The second approach can be modeled similar to this:
Groups.FK1 denotes:
FOREIGN KEY (group_id, primary_account_no) REFERENCES Accounts (group_id, account_no)
The presence of group_id in the FK above is what enforces primary account to belong to the group it is the primary account of.
Just be careful how you generate account_no when creating new accounts. You'll need to do something like this to avoid race conditions in concurrent environment (the actual code will varry by DBMS, of course).
Pick the first approach if your DBMS supports filtered indexes and there is no specific reason to pick the second approach.
Pick the second if:
you DBMS doesn't support filtered indexes,
or your DBMS supports deferred constraints and you need to enforce the presence of primary account at all times (just make primary_account_no NOT NULL),
or you don't actually need account_id, so you can have potentially one index less (depending on how strictly your DBMS requires indexes on FKs, and your actual workload, you may be able to avoid index on primary_account_no, as opposed to index that must be present on is_primary).
It is definitely possible to get rid of Group_Accounts.
From your description, it seems each group has many accounts, but each account only has one group. So you would put the group_id into the Accounts table as you suggest, and then put primary_account_id as a field in Groups.
It is possible to change the m:n intersection table, Group_Accounts, to a 1:n table by changing the PK to just the account id instead of both account and group. However, you would still be stuck with the additional overhead of enforcing the constraint that one and only one account is primary for any group.
However, if you move the group FK to the account record, where it really should be for 1:n cardinality, you can create a Primary_Accounts table kinda like the Group_Accounts table except the PK would be the group id. So each group could have one only one entry and that would be the one primary account. It would look like this:
create table Groups (
Id int not null,
Name varchar( 50 ) not null,
constraint PK_Groups primary key( Id )
);
create table Accounts (
Id int not null,
Name varchar( 50 ) not null,
GroupID int not null,
constraint PK_Accounts primary key( Id ),
constraint FK_AccountGroup foreign key( GroupID )
references Groups( ID )
);
create table PrimaryAccounts (
GroupID int not null,
AccountID int not null,
constraint PK_PrimaryAccounts primary key( GroupId ),
constraint FK_PrimaryGroup foreign key( GroupID )
references Groups( ID ),
constraint FK_PrimaryAccount foreign key( AccountID )
references Accounts( ID )
);
Now you have the 1:n cardinality design properly and you have the ability to designate one and only one account per group as the primary account.
However, there is one flaw. The PrimaryAccounts table must refer to an existing group and an existing account, but there is nothing that enforces the implicit requirement that the account be associated with the group.
Fortunately, this is easily fixed. Just add a constraint to the Accounts table:
constraint UQ_AccountGroup unique( GroupID, ID ),
Then, instead of creating two FKs in the PrimaryAccounts table, you need only one:
constraint FK_PrimaryGroupAccount foreign key( GroupID, AccountID )
references Accounts( GroupID, ID )
Now there can be only one primary account for each group and that account must be associated with the group.

Need help understanding this SQL (generated by doctrine)

i am actually reading Doctrine Reference: One to Many, Unidirectional with Join table. but this will probably be more of a SQL quesiton. basically, this is supposed to model a one to many, unidirectional relationship. i guess from the PHP code (in that link), its such that 1 user have many phonenumbers.
the question is from the SQL, it seems like 1 user can have many phonenumbers. and 1 phonenumber can only belong to 1 user. am i right?
CREATE TABLE User (
id INT AUTO_INCREMENT NOT NULL,
PRIMARY KEY(id)
) ENGINE = InnoDB;
CREATE TABLE users_phonenumbers (
user_id INT NOT NULL,
phonenumber_id INT NOT NULL,
UNIQUE INDEX users_phonenumbers_phonenumber_id_uniq (phonenumber_id),
PRIMARY KEY(user_id,
phonenumber_id)
) ENGINE = InnoDB;
CREATE TABLE Phonenumber (
id INT AUTO_INCREMENT NOT NULL,
PRIMARY KEY(id)
) ENGINE = InnoDB;
ALTER TABLE users_phonenumbers ADD FOREIGN KEY (user_id) REFERENCES User(id);
ALTER TABLE users_phonenumbers ADD FOREIGN KEY (phonenumber_id) REFERENCES Phonenumber(id);
can't i just simplify the database to ... below ... no need for join tables and what not?
Users (id, name)
Phonenumbers (id, user [FK], number)
Correct, these are two valid approaches to the same problem. And yes, the unique index on users_phonenumbers means that each phone number can belong to only one user.
The design is actually suboptimal.
The idea must have been that there are telephone numbers, users, and that they can be linked many-to-many. Because of the unique index on phonenumberid hoever, each number can only be assigned to one user.
Then the whole users_phonenumbers has become redundant, because they could just have added a userid column on the phonenumbers table and save themselves a join.
BAd table design if you ask me.

MySQL Lookup table and id/keys

Hoping someone can shed some light on this: Do lookup tables need their own ID?
For example, say I have:
Table users: user_id, username
Table categories: category_id, category_name
Table users_categories: user_id, category_id
Would each row in "users_categories" need an additional ID field? What would the primary key of said table be? Thanks.
You have a choice. The primary key can be either:
A new, otherwise meaningless INTEGER column.
A key made up of both user_id and category_id.
I prefer the first solution but I think you'll find a majority of programmers here prefer the second.
You could create a composite key that uses the both keys
Normally if there is no suitable key to be found in a table you want to create a either a composite key, made up of 2 or more fields,
ex:
Code below found here
CREATE TABLE topic_replies (
topic_id int unsigned not null,
id int unsigned not null auto_increment,
user_id int unsigned not null,
message text not null,
PRIMARY KEY(topic_id, id));
therefor in your case you could add code that does the following:
ALTER TABLE users_categories ADD PRIMARY KEY (user_id, category_id);
therefor once you want to reference a certain field all you would need is to pass the two PKs from your other table, however to link them they need to each be coded as a foreign key.
ALTER TABLE users_categories ADD CONSTRAINT fk_1 FOREIGN KEY (category_id) REFERENCES categories (category_id);
but if you want to create a new primary key in your users_categories table that is an option. Just know that its not always neccessary.
If your users_categories table has a unique primary key over (user_id, category_id), then - no, not necessarily.
Only if you
want to refer to single rows of that table from someplace else easily
have more than one equal user_id, category_id combination
you could benefit from a separate ID field.
Every table needs a primary key and unique ID in SQL no matter what. Just make it users_categories_id, you technically never have to use it but it has to be there.

MS SQL Bridge Table Constraints

Greetings -
I have a table of Articles and a table of Categories.
An Article can be used in many Categories, so I have created a table of ArticleCategories like this:
BridgeID int (PK)
ArticleID int
CategoryID int
Now, I want to create constraints/relationships such that the ArticleID-CategoryID combinations are unique AND that the IDs must exist in the respective primary key tables (Articles and Categories).
I have tried using both VS2008 Server Explorer and Enterprise Manager (SQL-2005) to create the FK relationships, but the results always prevent Duplicate ArticleIDs in the bridge table, even though the CategoryID is different.
I am pretty sure I am doing something obviously wrong, but I appear to have a mental block at this point.
Can anyone tell me please how should this be done?
Greaty appreciated!
Don't use a BridgeId column.
Make a composite primary key (aka compound key) from your ArticleId and CateogryId, that will ensure each combination is unique.
Make each column a foreign key to the corresponding table, and that completes your set of constraints.
Ok first you do a unique index on ArticleID, CategoryID.
Then you set up a foreign key constraint on articleID linking it back to the Article table. and then do the same for CategoryID and Catgory table.
Your description sounds like you are creating the PK on the Bridge table and the FK on the other table which is why it wouldn't work.
Expanding on HLGEM's solution you would have something like:
Create Table ArticleCategories
(
Id int not null Primary Key Clustered
, ArticleId int not null
, CategoryId int not null
, Constraint UK_ArticleCategories_Unique ( ArticleId, CategoryId )
, Constraint FK_ArticleCategories_Articles
Foreign Key ( ArticleId )
References Articles( Id )
, Constraint FK_ArticleCategories_Categories
Foreign Key ( CategoryId )
References Categories( Id )
)