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

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/

Related

Two postgresql tables referencing each other

Question may be basic, I don't have any experience with databases.
I have a postgres db with some tables. Two of them are dates and accounts.
The date table has an account_id field referencing an id table in an account table and a balance field that represents the balance that account had at that date. So, many date entities may reference one account entity, many-to-one, okay.
But an account table also has an actual_date field, that must reference the date entity, with actual balance this account has. One account entity may reference one actual date entuty, but date entity can have one or zero account entities referncing it. And if it does have an account referencing it with it's actual_date, it will always be the same account, date itself referencing with account_id.
What kind of relathinship is this? Is it even possible to implement? And if it is, how do I do it?
I came up with this piece of code, but I have no clue if it does what I think it does.
CREATE TABLE accounts (
id SERIAL PRIMARY KEY,
user_id INT REFERENCES users,
actual_date_id DATE UNIQUE REFERENCES dates
);
CREATE TABLE dates (
id SERIAL PRIMARY KEY,
account_id INT REFERENCES accounts,
date DATE,
balance INT,
unconfirmed_balance INT
);
P.S. I create tables with init.sql but work with them with sqlalchemy and it would be greate if someone could also show how to define such model with it.
As written the SQL script would never work for two reasons:
a foreign key can only reference the primary key of a table, not any arbitrary column in it. So actual_date_id should be an integer in order to be able to reference the primary key of the dates table.
you can't reference a table that hasn't been created yet, so the foreign key between accounts and dates must be created after both tables are created.
With circular foreign keys it's usually easier to define at least one of them as deferrable, so that you can insert them without the need of e.g. an intermediate NULL value.
So something along the lines (assuming that users already exists)
CREATE TABLE accounts (
id SERIAL PRIMARY KEY,
user_id INT REFERENCES users,
actual_date_id integer UNIQUE -- note the data type
);
CREATE TABLE dates (
id SERIAL PRIMARY KEY,
account_id INT REFERENCES accounts,
date DATE,
balance INT,
unconfirmed_balance INT
);
-- now we can add the foreign key from accounts to dates
alter table accounts
add foreign key (actual_date_id)
references dates (id)
deferrable initially deferred;
It might be better to avoid the circular reference to begin with. As you want to make sure that only one "current balance" exists for each account, this could be achieved by adding a flag in the dates table and getting rid of the actual_date_id in the accounts table.
CREATE TABLE accounts (
id SERIAL PRIMARY KEY,
user_id INT REFERENCES users
);
CREATE TABLE dates (
id SERIAL PRIMARY KEY,
account_id INT REFERENCES accounts,
is_current_balance boolean not null default false,
date DATE,
balance INT,
unconfirmed_balance INT
);
-- this ensures that there is exactly one row with "is_current_balance = true"
-- for each account
create unique index only_one_current_balance
on dates (account_id)
where is_current_balance;
Before you change a row in dates to be the "current one", you need to reset the existing one to false.
Unrelated, but:
With modern Postgres versions it's recommended to use identity columns instead of serial

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.

How do you store business activities in a SQL database?

The goal is to store activities such as inserting, updating, and deleting business records.
One solution I'm considering is to use one table per record to be tracked. Here is a simplified example:
CREATE TABLE ActivityTypes
(
TypeId int IDENTITY(1,1) NOT NULL,
TypeName nvarchar(50) NOT NULL,
CONSTRAINT PK_ActivityTypes PRIMARY KEY (TypeId),
CONSTRAINT UK_ActivityTypes UNIQUE (TypeName)
)
INSERT INTO ActivityTypes (TypeName) VALUES ('WidgetRotated');
INSERT INTO ActivityTypes (TypeName) VALUES ('WidgetFlipped');
INSERT INTO ActivityTypes (TypeName) VALUES ('DingBatPushed');
INSERT INTO ActivityTypes (TypeName) VALUES ('ButtonAddedToDingBat');
CREATE TABLE Activities
(
ActivityId int IDENTITY(1,1) NOT NULL,
TypeId int NOT NULL,
AccountId int NOT NULL,
TimeStamp datetime NOT NULL,
CONSTRAINT PK_Activities PRIMARY KEY (ActivityId),
CONSTRAINT FK_Activities_ActivityTypes FOREIGN KEY (TypeId)
REFERENCES ActivityTypes (TypeId),
CONSTRAINT FK_Activities_Accounts FOREIGN KEY (AccountId)
REFERENCES Accounts (AccountId)
)
CREATE TABLE WidgetActivities
(
ActivityId int NOT NULL,
WidgetId int NOT NULL,
CONSTRAINT PK_WidgetActivities PRIMARY KEY (ActivityId),
CONSTRAINT FK_WidgetActivities_Activities FOREIGN KEY (ActivityId)
REFERENCES Activities (ActivityId),
CONSTRAINT FK_WidgetActivities_Widgets FOREIGN KEY (WidgetId)
REFERENCES Widgets (WidgetId)
)
CREATE TABLE DingBatActivities
(
ActivityId int NOT NULL,
DingBatId int NOT NULL,
ButtonId int,
CONSTRAINT PK_DingBatActivities PRIMARY KEY (ActivityId),
CONSTRAINT FK_DingBatActivities_Activities FOREIGN KEY (ActivityId)
REFERENCES Activities (ActivityId),
CONSTRAINT FK_DingBatActivities_DingBats FOREIGN KEY (DingBatId)
REFERENCES DingBats (DingBatId)
CONSTRAINT FK_DingBatActivities_Buttons FOREIGN KEY (ButtonId)
REFERENCES Buttons (ButtonId)
)
This solution seems good for fetching all activities given a widget or dingbat record id, however it doesn't seem so good for fetching all activities and then trying to determine to which record they refer.
That is, in this example, all the account names and timestamps are stored in a separate table, so it's easy to create reports focused on users and focused on time intervals without the need to know what the activity is in particular.
However, if you did want to report on the activities by type in particular, this solution would require determining to which type of activity the general activity table refers.
I could put all my activity types in one table, however the ID's would not be able to be constrained by a foreign key, instead the table name might be used as an id, which would lead me to use dynamic queries.
Note in the example that a DingBatActivity has an optional button Id. If the button name were to have been edited after being added to the dingbat, the activity would be able to refer to the button and know its name, so if a report listed all activities by dingbat and by button by name, the button name change would automatically be reflected in the activity description.
Looking for some other ideas and how those ideas compromise between programming effort, data integrity, performance, and reporting flexibility.
The way that I usually architect a solution to this problem is similar to inheritance in objects. If you have "activities" that are taking place on certain entities and you want to track those activities then the entities involved almost certainly have something in common. There's your base table. From there you can create subtables off of the base table to track things specific to that subtype. For example, you might have:
CREATE TABLE Objects -- Bad table name, should be more specific
(
object_id INT NOT NULL,
name VARCHAR(20) NOT NULL,
CONSTRAINT PK_Application_Objects PRIMARY KEY CLUSTERED (application_id)
)
CREATE TABLE Widgets
(
object_id INT NOT NULL,
height DECIMAL(5, 2) NOT NULL,
width DECIMAL(5, 2) NOT NULL,
CONSTRAINT PK_Widgets PRIMARY KEY CLUSTERED (object_id),
CONSTRAINT FK_Widgets_Objects
FOREIGN KEY (object_id) REFERENCES Objects (object_id)
)
CREATE TABLE Dingbats
(
object_id INT NOT NULL,
label VARCHAR(50) NOT NULL,
CONSTRAINT PK_Dingbats PRIMARY KEY CLUSTERED (object_id),
CONSTRAINT FK_Dingbats_Objects
FOREIGN KEY (object_id) REFERENCES Objects (object_id)
)
Now for your activities:
CREATE TABLE Object_Activities
(
activity_id INT NOT NULL,
object_id INT NOT NULL,
activity_type INT NOT NULL,
activity_time DATETIME NOT NULL,
account_id INT NOT NULL,
CONSTRAINT PK_Object_Activities PRIMARY KEY CLUSTERED (activity_id),
CONSTRAINT FK_Object_Activities_Objects
FOREIGN KEY (object_id) REFERENCES Objects (object_id),
CONSTRAINT FK_Object_Activities_Activity_Types
FOREIGN KEY (activity_type) REFERENCES Activity_Types (activity_type),
)
CREATE TABLE Dingbat_Activities
(
activity_id INT NOT NULL,
button_id INT NOT NULL,
CONSTRAINT PK_Dingbat_Activities PRIMARY KEY CLUSTERED (activity_id),
CONSTRAINT FK_Dingbat_Activities_Object_Activities
FOREIGN KEY (activity_id) REFERENCES Object_Activities (activity_id),
CONSTRAINT FK_Dingbat_Activities_Buttons
FOREIGN KEY (button_id) REFERENCES Object_Activities (button_id),
)
You can add a type code to the base activity if you want to for the type of object which it is affecting or you can just determine that by looking for existence in a subtable.
Here's the big caveat though: Make sure that the objects/activities really do have something in common which relates them and requires you to go down this path. You don't want to store disjointed, unrelated data in the same table. For example, you could use this method to create a table that holds both bank account transactions and celestial events, but that wouldn't be a good idea. At the base level they need to have something in common.
Also, I assumed that all of your activities were related to an account, which is why it's in the base table. Anything in common to ALL activities goes in the base table. Things relevant to only a subtype go in those tables. You could even go multiple levels deep, but don't get carried away. The same goes for the objects (again, bad name here, but I'm not sure what you're actually dealing with). If all of your objects have a color then you can put it in the Objects table. If not, then it would go into sub tables.
I'm going to go out on a limb and take a few wild guesses about what you're really trying to accomplish.
You say you're trying to track 'store activities' I'm going to assume you have the following activities:
Purchase new item
Sell item
Write off item
Hire employee
Pay employee
Fire employee
Update employee record
Ok, for these activities, you need a few different tables: one for inventory, one for departments, and one for employees
The inventory table could have the following information:
inventory:
item_id (pk)
description (varchar)
number_in_stock (number)
cost_wholesale (number)
retail_price (number)
dept_id (fk)
department:
dept_id (pk)
description (varchar)
employee
emp_id (pk)
first_name (varchar)
last_name (varchar)
salary (number)
hire_date (date)
fire_date (date)
So, when you buy new items, you will either update the number_in_stock in inventory table, or create a new row if it is an item you've never had before. When you sell an item, you decriment the number_in_stock for that item (also for when you write off an item).
When you hire a new employee, you add a record from them to the employees table. When you pay them, you grab their salary from the salary column. When you fire them, you fill in that column for their record (and stop paying them).
In all of this, the doing is not done by the database. SQL should be used for keeping track of information. It's fine to write procedures for doing these updates (a new invoice procedure that updates all the items from an invoice record). But you don't need a table to do stuff. In fact, a table can't do anything.
When designing a database, the question you need to ask is not "what do I need to do?" it is "What information do I need to keep track of?"
New answer, based on an different interpretation of the question.
Are you just trying to keep a list of what has happened? If you just need a ordered list of past events, you just need 1 table for it:
action_list
action_list_id (pk)
action_desc (varchar)
event_log:
event_log_id (pk)
event_time (timestamp)
action_list_id (fk)
new_action_added (fk)
action_details_or_description (varchar)
In this, the action_list would be something like:
1 'WidgetRotated'
2 'WidgetFlipped'
3 'DingBatPushed'
4 'AddNewAction'
5 'DeleteExistingAction'
The event_log would be a list of what activities happened, and when. One of your actions would be "add new action" and would require the 'new_action_added' column to be filled in on the event table anytime the action taken is "add new action".
You can create actions for update, remove, add, etc.
EDIT:
I added the action_details_or_description column to event. In this way, you can give further information about an action. For example, if you have a "product changes color" action, the description could be "Red" for the new color.
More broadly, you'll want to think through and map out all the different types of actions you'll be taking ahead of time, so you can set up your table(s) in a way that can accurately contain the data you want to put into them.
How about the SQL logs?
The last time I needed a database transaction logger I used an Instead Of trigger in the database so that it would instead of just updating the record, the database would insert a new record into the log table. This technique meant that I needed an additional table to hold the log for each table in my database and the log table had an additional column with a time stamp. Using this technique you can even store the pre and post update state of the record if you want to.

implementing UNIQUE across linked tables in MySQL

a USER is a PERSON and a PERSON has a COMPANY - user -> person is one-to-one, person -> company is many-to-one.
person_id is FK in USER table.
company_id is FK in PERSON table.
A PERSON may not be a USER, but a USER is always a PERSON.
If company_id was in user table, I could create a unique key based on username and company_id, but it isn't, and would be a duplication of data if it was.
Currently, I'm implementing the unique username/company ID rule in the RoseDB manager wrapper code, but it feels wrong. I'd like to define the unique rule at the DB level if I can, but I'm not sure excactly how to approach it. I tried something like this:
alter table user add unique(used_id,person.company_id);
but that doesn't work.
By reading through the documentation, I can't find an example that does anything even remotely similar. Am I trying to add functionality that doesn't exist, or am I missing something here?
Well, there's nothing simple that does what you want. You can probably enforce the constraint you need using BEFORE INSERT and BEFORE UPDATE triggers, though. See this SO question about raising MySQL errors for how to handle making the triggers fail.
Are there more attributes to your PERSON table? Reason I ask is that what you want to implement is a typical corollary table:
USERS table:
user_id (pk)
USER_COMPANY_XREF (nee PERSON) table:
user_id (pk, fk)
company_id (pk, fk)
EFFECTIVE_DATE (not null)
EXPIRY_DATE (not null)
COMPANIES table:
company_id (pk)
The primary key of the USER_COMPANY_XREF table being a composite key of USERS.user_id and COMPANIES.company_id would allow you to associate a user with more than one company while not duplicating data in the USERS table, and provide referencial integrity.
You could define the UNIQUE constraint in the Person table:
CREATE TABLE Company (
company_id SERIAL PRIMARY KEY
) ENGINE=InnoDB;
CREATE TABLE Person (
person_id SERIAL PRIMARY KEY,
company_id BIGINT UNSIGNED,
UNIQUE KEY (person_id, company_id),
FOREIGN KEY (company_id) REFERENCES Company (company_id)
) ENGINE=InnoDB;
CREATE TABLE User (
person_id BIGINT UNSIGNED PRIMARY KEY,
FOREIGN KEY (person_id) REFERENCES Person (person_id)
) ENGINE=InnoDB;
But actually you don't need the unique constraint even in the Person table, because person_id is already unique on its own. There's no way a given person_id could reference two companies.
So I'm not sure what problem you're trying to solve.
Re your comment:
That doesn't solve the issue of allowing the same username to exist in different companies.
So you want a given username to be unique within one company, but usable in different companies? That was not clear to me from your original question.
So if you don't have many other attributes specific to users, I'd combine User with Person and add an "is_user" column. Or just rely on it being implicitly true that a Person with a non-null cryptpass is by definition a User.
Then your problem with cross-table UNIQUE constraints goes away.