I have a mysql database table called Vehicles.
This table has many rows: car, helicopter, plane, etc.
I want each of these rows to have their own table, so I can have different data for each vehicle type.
My question is, how can I make this table so that each row references not another row on another table, but the table itself. I thought of just using the table name, but it feels a bit hackish.
If I'm reading this correctly, you are thinking of a Base or Master entity table to hold all attributes that the vehicles have in common, then a derived table for all the attributes associated with each vehicle.
Something like this maintains data integrity and is scalable:
create table Vehicles(
ID int not null auto_generated primary key,
VType char( 1 ) not null check( VType in( 'C', 'H', 'P' )),
... <other common attributes>,
constraint UQ_ID_VType unique( ID, VType )
);
create table cars(
ID int not null primary key,
VType char( 1 ) not null check( VType = 'C' ),
... <other car-only attributes>,
constraint FK_Car_Vehicle foreign key( ID, VType )
references Vehicles( ID, VType )
);
create table Helicopters(
ID int not null primary key,
VType char( 1 ) not null check( VType = 'H' ),
... <other helicopter-only attributes>,
constraint FK_Helicopter_Vehicle foreign key( ID, VType )
references Vehicles( ID, VType )
);
Etc. The Vehicles table can contain only defined vehicle types and each sub-table can only contain their particular vehicle type and each ID/Type combination must exist in Vehicles first. Beyond that, each one can define any attributes it needs to describe its particular vehicle type. To add Motorcycle, add 'M' to the check constraint of VType field of Vehicles table and create a Motorcycles table with 'M' as the only acceptable value of its VType field.
If all you have is the vehicle ID value, you locate that in the Vehicles table to find out the type -- car, helicopter, etc. -- and that tells you which sub-table to access to get the rest of the information.
As an alternate, and it helps with the scalability factor, have the VType field of the Vehicles table be a FK to a VehicleTypes table. Then you don't have to redefine the table itself to add a new type of vehicle.
Related
Say I have a bunch of people with multiple phone numbers. In a MySQL database I'd have a Person table and a Phone Number table with a many to one relationship.
Now I want to make one of those numbers the primary phone number and only allow one primary number per person. How would I model this?
Try the schema below. It will prevent entries that try to assign more than one primary number per person.
CREATE TABLE person (
`id` INT(11) UNSIGNED NOT NULL AUTO_INCREMENT,
`first_name` VARCHAR(50) NOT NULL,
`last_name` VARCHAR(50) NOT NULL,
PRIMARY KEY(`id`)
);
CREATE TABLE phonenumber (
`id` INT(11) UNSIGNED NOT NULL AUTO_INCREMENT,
`phonenumber` VARCHAR(10) NOT NULL,
`person_id` INT(11) UNSIGNED NOT NULL,
`is_primary` ENUM('1'),
PRIMARY KEY(`id`),
UNIQUE KEY idx_person_primary (`person_id`, `is_primary`),
UNIQUE KEY idx_person_phone (`phonenumber`, `person_id`)
);
INSERT INTO person (first_name, last_name) VALUES ('Michael', 'Jones');
INSERT INTO phonenumber (phonenumber, person_id, is_primary) VALUES ('9876543210', 1, 1);
INSERT INTO phonenumber (phonenumber, person_id, is_primary) VALUES ('1234567890', 1, NULL);
INSERT INTO phonenumber (phonenumber, person_id, is_primary) VALUES ('1234567891', 1, NULL);
This will allow the DB to police a single primary phone number for each person. For example if you try to assign another primary phone number to Michael Jones:
INSERT INTO phonenumber (phonenumber, person_id, is_primary) VALUES ('0123211234', 1, 1);
You will get a "Duplicate entry '1-1' for key 'idx_person_primary'" error.
http://sqlfiddle.com/#!9/dbb3c7/1
The "exactly one primary phone number" is tricky. One way uses triggers. Other databases offer expression-based indexes. This is tricky because:
The constraint spans two tables.
Guaranteeing exact "one-ness" across updates is tricky.
But one method in MySQL that comes close and doesn't use triggers:
create table persons (
personId int auto_increment primary key,
primary_personPhonesId int,
. . .
);
create table personPhones (
personPhonesId int auto_increment primary key,
personId int,
. . .
foreign key (personId) references persons (personId),
unique (personId, personPhonesId) -- seems redundant but needed
);
alter table persons
add foreign key (personId, primary_personPhonesId) on personPhones(personId, personPhonesId);
It is tempting to declare primary_personPhonesId as not null. However, that makes it difficult to insert rows into the two tables.
An alternative method uses computed columns:
create table persons (
personId int auto_increment primary key,
. . .
);
create table personPhones (
personPhonesId int auto_increment primary key,
personId int,
isPrimary boolean,
. . .
foreign key (personId) references persons (personId),
primaryId as (case when isPrimary then personPhonesId end),
unique(primaryId)
);
Similar to the previous solution, this does not guarantee that isPrimary is always set.
You can try the below mentioned design:
Person (Id (PK),name,....)
TelephoneNumber (Id(PK), telNo, PersonId(FK))
PrimaryTelNo (PersonId(FK), TelId(FK))
You can create a table showing mapping of TelId and PersonId and declare the combination of TelId and PersonId as composite primary key
The simplest way is to make the 'first' one primary, but this becomes tricky when you want to change which one is primary. In that case, I believe you can do this...
CREATE TABLE my_table
(person_id INT NOT NULL
,phone VARCHAR(12) not null
,is_primary enum('1') null
,primary key(person_id,phone)
, unique (person_id,is_primary)
);
INSERT INTO my_table VALUES
(1,'123',1),
(1,'234',null),
(1,'345',null),
(2,'456',null),
(2,'567',1),
(2,'678',null);
So, the enum allows values of 1 and null, but while there can be several nulls, there can only be one '1' per person. However, this solution doesn't preclude the possibility that none of the numbers are primary!
You should create a third table person_primary_number with only two fields:
person_id
phone_number_id
In this table you should insert the ids of the person and his primary number. The primary key of this table is on these two columns.
Another way is to add primary_number_id directly to the person table. This is probably the simplest solution.
Then you should have:
person
—————-
id (primary key int autoincrement)
primary_number_id (foreign key for phone_number.id)
name
...
phone_number
———————————-
id (primary key int autoincrement)
person_id (foreign key for person.id)
phone_number
The only problem with this solution is that you can assign as primary phone the number of somebody else.
This violates a strong principle of schema design -- don't pack a list into a cell. But...
If you only need to display the phone number to some human who will be doing a call, and
If that human possibly needs to see non-primary numbers, then
Consider having a VARCHAR(100) column that has a commalist that starts with the 'primary' phone number and continues with alternative numbers.
Note that the application would be responsible for putting the list together, and dealing with updates. Or you could push this back onto the user by providing a UI that asks for "phone number(s), starting with the preferred one to call you with; please separate with commas."
I have a property management application with a full blown accounting system built into it. I have a journal entries table that controls all the postings for various accounting activities such as:
Invoices
Payments
Bills
Deposits
In some cases it's necessary to join these entities to the journal entries table to aggregate accounting entries by different properties and units.
I'm looking for the best way to do this. I have several options:
1) Add a foreign key on the journal entry table to link to the invoice_id, payment_id, bill_id, deposit_id, however most combinations of these will be mutually exclusive (i.e. a deposit would not have a payment) so I would have cases where for a given journal entry I would have nulls in those foreign keys that do not apply to that given journal entry.
2) I could create a single foreign key, let's call it doc_id and another column doc_type to indicate the type of document (Invoice, Payment, Bill, Deposit, etc) and have the combination of doc_id and the document_type_id to reference a primary key on one of the extension tables (i.e. doc_id = 1 & doc_type = Invoice that combination would reference the primary key on the Invoice table).
Which is the better way to go about this or am I thinking about this all wrong?
This sounds like a standard base entity/sub entity pattern. There is one table, let's call it JournalEntries, which contains the attributes that all journal entries have in common: ID, type of entry, when it was created, who created it, and so on.
create table JournalEntries(
ID Int auto_generating primary key,
EType char( 1 ) not null check( EType in( 'I', 'P', 'B', 'D' )) -- Invoice, Payment, etc.
Amount currency not null,
CreateDate Date not null,
..., -- other common attributes
constraint UQ_JournalEntryType unique( ID, EType ) -- create anchor for FKs
);
Notice that ID is the primary key so therefore unique. So the constraint making the combination of ID and EType unique is redundant from a domain definition point of view. All it does is define an anchor for foreign keys.
These FKs will be in the subentity tables -- one table for each subentity: Invoice, Payment, Bill and Deposit. Note that if an entry is defined in the JournalEntries table as a Deposit (EType = 'D') a corresponding entry can only be made in the Deposits table. You can't, for example, mistakenly use that ID in, say, the Payments table.
Let's define one of the subentity tables:
create table Invoices(
ID int primary key, -- value generated by JournalEntries table
IType char( 1 ) not null check( IType = 'I' ), -- Nothing but invoices
..., -- Invoice-specific attributes
constraint FK_InvoiceToEntry foreign key( ID, IType )
references JournalEntries( ID, EType )
);
Now let's create an activity that always has one Invoice associated with it and may have any number of other entries. The constraints ensure only invoices can be inserted and the ID value must match a JournalEntries entry that is defined as an Invoice.
create table Activities(
ID int auto_generating primary key,
InvID int not null,
IType char( 1 ) check( IType = 'I' ),
..., -- other data
constraint FK_ActivityInvoice foreign key( InvID, Type )
);
There may be any number of additional entries and they may be any of the entry types, so you need an intersection table:
create table ActivityEntries(
ActID int not null,
EntID int not null,
DateEntered date not null,
constraint FK_ActEntry_Activity foreign key( ActID )
references Activities( ID ),
constraint FK_ActEntry_JEntry foreign key( EntID )
references JournalEntries( ID )
);
Note that a "Journal Entry" is the JournalEntries data joined with the associated data from one of the subentity tables. So FK references to any journal entry should refer to the JournalEntries table, not any of the subentity tables, even if you know what kind of entry it is. So the Activities rows refer to the JournalEntries table using the EType field as additional data integrity effort because it must be an invoice. The intersection table contains any type of entry so its FK target is just the PK.
Note: for illustration purposes, the type indicator in the JournalEntries table was constrained by a check statement. In an actual database, a much better design would be an entry types lookup table. That maintains the data integrity but is a much more flexible design. (Plus the fact that MySQL still(!) doesn't implement check constraints.)
Issue:
I'm using PostgreSQL Database.
I have one table (Albums) to be linked to two other tables (Clients, Domains). So if you are Client or Domain you can have Album. But in Albums table owner can handle only single foreign key. How can I solve this issue?
Dream: Single Album can own only (1) Client or Domain. Need fix issue with foreign keys. Albums: id | owner (multiple foreign -> Clients:id or Domains:id) --> can not do this | name. I just need some smart rework.
Tables (now can have Album only Domain):
Albums
Clients
Domains
Albums (table with foreign key yet):
id | owner (foreign key -> Domains:id) | name
Clients:
id | first_name | last_name
Domains:
id | owner | name
Add 2 FK columns, and a CHECK constraint, to enforce only one of them is NOT NULL...
Something like this:
CREATE TABLE albums (
id serial PRIMARY KEY,
client_id integer,
domain_id integer,
name varchar(255) NOT NULL,
FOREIGN KEY (client_id) REFERENCES clients(id),
FOREIGN KEY (domain_id) REFERENCES domains(id),
CHECK ((client_id IS NULL) <> (domain_id IS NULL))
);
To query you can use something like this:
SELECT a.id, COALESCE(c.id, d.id) AS owner_id, COALESCE(c.name, d.name) AS owner_name,
a.name AS title
FROM albums a
LEFT JOIN clients c ON a.client_id = c.id
LEFT JOIN domains d ON a.domain_id = d.id
#e_i_pi's version
CREATE TABLE entities (
id serial PRIMARY KEY,
type integer, -- could be any other type
-- any other "common" values
);
CREATE TABLE client_entities (
id integer PRIMARY KEY, -- at INSERT this comes from table `entities`
name varchar(255) NOT NULL,
);
CREATE TABLE domain_entities (
id integer PRIMARY KEY, -- at INSERT this comes from table `entities`
name varchar(255) NOT NULL,
);
CREATE TABLE albums (
id serial PRIMARY KEY,
owner_id integer FOREIGN KEY REFERENCES entities(id), -- maybe NOT NULL?
name varchar(255) NOT NULL,
);
Query:
SELECT a.id, owner_id, COALESCE(c.name, d.name) AS owner_name, a.name AS title
FROM albums a
LEFT JOIN entities e ON a.owner_id = e.id
LEFT JOIN client_entities c ON e.id = c.id AND e.type = 1 -- depending on the type of `type`
LEFT JOIN domain_entities d ON e.id = d.id AND e.type = 2
Righto, so as suggested in the comment to the answer by #UsagiMiyamoto, there is a way to do this that allows declaration of entity types, with cascading. Note that this solution doesn't support unlimited entity types, as we need to maintain concrete FK constraints. There is a way to do this with unlimited entity types, but involves triggers and quite a bit of nastiness.
Here's the easy to understand solution:
-- Start with a test schema
DROP SCHEMA IF EXISTS "entityExample" CASCADE;
CREATE SCHEMA IF NOT EXISTS "entityExample";
SET SEARCH_PATH TO "entityExample";
-- We'll need this to enforce constraints
CREATE OR REPLACE FUNCTION is_entity_type(text, text) returns boolean as $$
SELECT TRUE WHERE $1 = $2
;
$$ language sql;
-- Unique entity types
CREATE TABLE "entityTypes" (
name TEXT NOT NULL,
CONSTRAINT "entityTypes_ukey" UNIQUE ("name")
);
-- Our client entities
CREATE TABLE clients (
id integer PRIMARY KEY,
name TEXT NOT NULL
);
-- Our domain entities
CREATE TABLE domains (
id integer PRIMARY KEY,
name TEXT NOT NULL
);
-- Our overaching entities table, which maintains FK constraints against clients and domains
CREATE TABLE entities (
id serial PRIMARY KEY,
"entityType" TEXT NOT NULL,
"clientID" INTEGER CHECK (is_entity_type("entityType", 'client')),
"domainID" INTEGER CHECK (is_entity_type("entityType", 'domain')),
CONSTRAINT "entities_entityType" FOREIGN KEY ("entityType") REFERENCES "entityTypes" (name) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT "entities_clientID" FOREIGN KEY ("clientID") REFERENCES "clients" (id) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT "entities_domainID" FOREIGN KEY ("domainID") REFERENCES "domains" (id) ON DELETE CASCADE ON UPDATE CASCADE
);
-- Our albums table, which now can have one owner, but of a dynam ic entity type
CREATE TABLE albums (
id serial PRIMARY KEY,
"ownerEntityID" integer,
name TEXT NOT NULL,
CONSTRAINT "albums_ownerEntityID" FOREIGN KEY ("ownerEntityID") REFERENCES "entities"("id")
);
-- Put the entity type in
INSERT INTO "entityTypes" ("name") VALUES ('client'), ('domain');
-- Enter our clients and domains
INSERT INTO clients VALUES (1, 'clientA'), (2, 'clientB');
INSERT INTO domains VALUES (50, 'domainA');
-- Make sure the clients and domains are registered as entities
INSERT INTO entities ("entityType", "clientID")
SELECT
'client',
"clients".id
FROM "clients"
ON CONFLICT DO NOTHING
;
INSERT INTO entities ("entityType", "domainID")
SELECT
'domain',
"domains".id
FROM "domains"
ON CONFLICT DO NOTHING
;
If you don't like the idea of inserting twice (once in client, once in entites, for example) you can have a trigger on inserts in the clients table, or alternately create an insert function that inserts to both tables at once.
I have two tables: Student & User
the Student table has a primary key of INT(9)
the User table has a primary key of MEDIUMINT
Now please take a look at this picture
(source: imgh.us)
Now the problem is: in the messages table i've the messageFrom and messageTo cols, I don't know weather the sender or the receiver is a Student or a User.
I can't reference the two tables because of different primary key types, however, am trying to avoid major changes as possible.
the same issue is everywhere reportedPosts table, comment table. Everywhere.
HOW to get around this issue or a possible solutions to fix this ?
AND please feel free to feedback the database structure, i would like to know and learn from your advices.
thanks in advance.
Both Users and Students (the entities, not the tables) are both examples of People. There are attributes of Users that don't belong to Students and there are attributes of Students that don't belong to Users. Also, there are actions Users may take that Students cannot take and vice versa. However, there are attributes common to both (name, address, phone number, etc.) and actions both may take (send/receive messages, post comments, etc.). This strongly implies a separate table to contain the common attributes and allow the common actions.
create table People(
ID MediumInt auto_generating primary key,
PType char( 1 ) not null check( PType in( 'U', 'S' )) -- User or Student
Name varchar( 64 ) not null,
Address varchar( 128 ),
..., -- other common attributes
constraint UQ_PeopleIDType unique( ID, PType ) -- create anchor for FKs
);
create table Users(
UserID MediumInt not null primary key,
UType char( 1 ) check( UType = 'U' ),
..., -- attributes for Users
constraint FK_Users_People foreign key( UserID, UType )
references People( ID, PType )
);
create table Students(
StudentID MediumInt not null primary key,
SType char( 1 ) check( SType = 'S' ),
..., -- attributes for Students
constraint FK_Students_People foreign key( StudentID, SType )
references People( ID, PType )
);
Notice that if a Person is created with a type of 'S' (Student), the ID value for that Person can only be inserted into the Student table.
Now all tables that must refer to Users may FK to the Users table and those that must refer to Students may FK to the Students table. When tables can refer to either, they may FK to the People table.
Foreign Keys do not solve all problems. Add a suitable index instead of depending on the FK. Then:
Plan A: Do the equivalent of FK checks in the application code, or
Plan B: Forgo any FK checks.
I have 2 tables, customers and affiliates. I need to make sure that customers.email and affiliates.email are exclusive. In other words, a person cannot be both a customer and an affiliate. It's basically the opposite of a foreign key. Is there a way to do this?
You can use a table that stores emails and have unique constrain on the email, and reference that table from the customer and affiliate. (still need to ensure that there are no 2 records referencing the same key)
You can use trigger before insert and before update to check if the email is not present.
Or you can leave this validation to the application logic - not in the database, but in the applicationc ode.
There is no key you can do this with, but it sounds like you shouldn't be using two tables. Instead, you can have one table with either customer/affiliate data (that needs to be unique in this table) and another table that has the type (customer/affiliate).
CREATE TABLE People (
pplid,
pplEmail,
ptid,
UNIQUE KEY (pplEmail)
)
CREATE TABLE PeopleType (
ptid,
ptType
)
INSERT INTO PeopleType VALUES (1, 'affiliates'), (2, 'customers');
You can try the following.
Create a new table, which will be a master for customers and affiliates:
CREATE TABLE party
(
id int not null auto_increment primary key ,
party_type enum('customer','affiliate') not null,
email varchar(100),
UNIQUE (id,party_type)
);
--Then
CREATE TABLE customer
(
....
party_id INT NOT NULL,
party_type enum('customer') NOT NULL DEFAULT 'customer',
PRIMARY KEY (party_id,party_type)
FOREIGN KEY (party_id,party_type) REFERENCES party(id,party_type)
);
CREATE TABLE affiliates
(
....
party_id INT NOT NULL,
party_type enum('affiliate') NOT NULL DEFAULT 'affiliate',
PRIMARY KEY (party_id,party_type)
FOREIGN KEY (party_id,party_type) REFERENCES party(id,party_type)
)
-- enum is used because mysql still doesn't have CHECK constraints
This way each party can be only of one type