thank you for your attention. It's been a while since I learned normalization in school and university and if you go work in the industries, you will find that often these theoretic things might interfere with what you boss tells you to do. So here is an example of something I'm thinking about for a while and I would love to hear your oppinion. I'm not even sure if there is a right or wrong on this approach so every answer is welcome.
Scenario:
We are developing a person management system (PMS). For this, we have a Person table, to store generic information about each person (e. g. Name), a Country table, to store the native country of a person and a Race table for the persons race.
Therefore you could have two foreign key columns on Person, linking to Country and Race, right?
However, my boss want's me to use a fourth table. Lets call it PersonType. The PersonType consists of a primary key as well as the foreign keys colums to Country and Race. The Person table then only has one foreign key column to PersonType.
Therefore I would have a n:1 relation between Person and PersonType as well as n:1 relations between PersonType and Country/Race, right?
In my opinion, the PersonType table is not necessary because you could just put the foreign key columns directly on the Person table but my boss argues that the PersonType can be used to constrain which Country/Race combinations are valid. I understand that argument but I'm asking myself if this database is still properly normalized.
(Of course we are not really developing a PMS but I thought it's easy to imagine and I can't talk about what we are really developing due to an NDA).
UPDATE 21/10/2016
Heres how the table structure looks like in an abstract way:
table person_v1(
person_id int primarykey,
name string,
country_id int foreignkey(country),
race_id int foreignkey(race)
)
table person_v2(
person_id int primarykey,
name string,
person_type_id int foreignkey(person_type)
)
table person_type(
person_type_id int primarykey,
country_id int foreignkey(country),
race_id int foreignkey(race)
)
table country(
country_id int primarykey,
name string
)
table race(
race_id int primarykey,
name string
)
Thank you for your answers so far
The number of 1:N or M:N relationships doesn't determine a relation's normal form. The question actually has nothing to do with normalization.
Some tables . . .
Your design
Follow standards when you can.
For countries, I'll follow ISO 3166-1.
create table countries (
iso_country_code char(3) primary key,
country_name varchar(75) not null unique
);
insert into countries (iso_country_code, country_name) values
('USA', 'United States of America'),
('GBR', 'United Kingdom of Great Britain and Northern Ireland'),
('MKD', 'Macedonia (the former Yugoslav Republic of)'),
('ZZZ', 'Unknown country'); -- 'ZZZ' is reserved for a user-assigned value.
For race, I'll follow CDC/HL7 race codes.
There are other standards.
One of them might be more appropriate.
See http://www.cdc.gov/nchs/data/dvs/race_ethnicity_codeset.pdf
Most applications allow multiple race codes for each person.
I'm ignoring that real-world fact for this question.
create table races (
cdc_unique_id char(6) primary key,
cdc_race_concept varchar(50) not null unique
);
insert into races (cdc_unique_id, cdc_race_concept) values
('2056-0', 'Black'),
('2106-3', 'White'),
('2076-8', 'Native Hawaiian or other Pacific islander'),
('zzzz-z', 'Unknown');
create table persons (
person_id integer primary key,
person_full_name varchar(25) not null,
iso_country_code char(2) not null
default 'ZZZ'
references countries (iso_country_code)
on update cascade
on delete set default,
cdc_unique_id char(6) not null
default 'zzzz-z'
references races (cdc_unique_id)
on update cascade
on delete set default
);
All three of these tables are in at least 5NF.
One potential problem with your design
is that it allows arbitrary parings
of country and race.
Imagine that, instead of country and race,
we were talking about city and state in the USA.
An arbitrary paring of city and state would allow
"San Francisco, AL".
But there is no city named "San Francisco" in Alabama.
That's why allowing arbitrary parings might be a bad decision.
Your boss's design
-- The same as the table above.
create table countries (
iso_country_code char(3) primary key,
country_name varchar(75) not null unique
);
insert into countries (iso_country_code, country_name) values
('USA', 'United States of America'),
('GBR', 'United Kingdom of Great Britain and Northern Ireland'),
('MKD', 'Macedonia (the former Yugoslav Republic of)'),
('ZZZ', 'Unknown country'); -- 'ZZZ' is reserved for a user-assigned value.
-- Also the same as the table above.
create table races (
cdc_unique_id char(6) primary key,
cdc_race_concept varchar(50) not null unique
);
insert into races (cdc_unique_id, cdc_race_concept) values
('2056-0', 'Black'),
('2106-3', 'White'),
('2076-8', 'Native Hawaiian or other Pacific islander'),
('zzzz-z', 'Unknown');
-- This table is new.
create table person_types (
iso_country_code char(3) not null
default 'ZZZ'
references countries (iso_country_code)
on update cascade
on delete set default,
cdc_unique_id char(6) not null
default 'zzzz-z'
references races (cdc_unique_id)
on update cascade
on delete set default,
primary key (iso_country_code, cdc_unique_id)
);
insert into person_types values
('USA', '2016-3'),
('USA', '2056-0'),
('GBR', '2016-3'),
('GBR', '2056-0'),
This "person_types" table records a fact
that your database design does not.
It records the fact
that white persons and black persons
might be native to both the USA and to Great Britain.
If recording that fact is important, you must include "person_types".
Also, it's worth noting that this table is immune to the problems cited in other comments; you cannot repeatedly add rows where both iso_country_code and cdc_unique_id are null (not null constraint), you cannot duplicate iso_country_code and cdc_unique_id (primary key constraint), etc.
Conceptually, you decide which facts to store before you normalize. Normalization can't help you with attributes that don't exist in your schema. That's a different database design task.
-- Structurally identical to the table above.
-- Constraints are slightly different.
--
create table persons (
person_id integer primary key,
person_full_name varchar(25) not null,
iso_country_code char(2) not null
default 'ZZZ',
cdc_unique_id char(6) not null
default 'zzzz-z',
constraint person_types_fk foreign key (iso_country_code, cdc_unique_id)
references person_types (iso_country_code, cdc_unique_id)
on update cascade
on delete set default
);
All four of these tables are in at least 5NF.
The difference is not that one set of tables is more normalized than the other.
The difference is that one set of tables records a fact that the other does not.
Let's assume this table is common to both yours and your boss's designs:
CREATE TABLE RaceMeetings
( country_name VARCHAR(30) NOT NULL,
race_name VARCHAR(25) NOT NULL,
UNIQUE ( country_name, race_name ) );
From what I can tell, your design is this (one table):
CREATE TABLE People_v1
( person_name VARCHAR(35) NOT NULL UNIQUE,
country_name VARCHAR(30) NOT NULL,
race_name VARCHAR(25) NOT NULL,
FOREIGN KEY ( country_name, race_name )
REFERENCES RaceMeetings ( country_name, race_name ) );
...and you boss's design is this (two tables):
CREATE TABLE People_v2
( person_name VARCHAR(35) NOT NULL UNIQUE );
CREATE TABLE RaceMeetingAttendance
( person_name VARCHAR(35) NOT NULL UNIQUE
REFERENCES People_v2 ( person_name ),
country_name VARCHAR(30) NOT NULL,
race_name VARCHAR(25) NOT NULL,
FOREIGN KEY ( country_name, race_name )
REFERENCES RaceMeetings ( country_name, race_name ) );
Both designs
are fully normalized
are in 5NF
restrict { country_name, race_name } combination for a person to be defined in table RaceMeetings.
Your boss's design is additionally in 6NF but that does not necessarily confer any practical advantage.
However, I do prefer your boss's design based on the rule of thumb that a table should model either an entity or a relationship between entities but never both. Put another way, a race meeting such as "The race 'Pacific Grand Prix' is held in Japan" doesn't strike me as being an attribute of a person. It seem to me more like a relationship (a correspondence from person to race meeting) and using a separate table to model this relationship allows me to usefully give it the name `RaceMeetingAttendance'.
That said, your design has the advantage that a person who hasn't attended a race meeting cannot exist in your database, should that be a requirement.
Related
Suppose I have two tables: A (with columns: a,b,c,d) and B (with columns: x,y,z). Now, (a,b) together make the primary key for table A and x is the primary key of table B. Is it possible to make b a foreign key of table A that refers x from table B?
Please reply ASAP!
Thanks in advance! :-)
Yes, there is no issue with that. A classic example (using MySQL for demonstration purposes) is a database table holding a number of companies and another holding employees which can work for any one of those companies:
create table companies (
id int primary key,
name varchar(20));
create table employees (
id int,
c_id varchar(20) references companies(id),
name varchar(20),
primary key (id, c_id));
insert into companies (id, name) values (1, 'ABC');
insert into companies (id, name) values (2, 'DEF');
insert into companies (id, name) values (3, 'HIJ');
insert into employees (id, c_id, name) values (101, 1, "Allan");
insert into employees (id, c_id, name) values (102, 1, "Bobby");
insert into employees (id, c_id, name) values (101, 2, "Carol");
insert into employees (id, c_id, name) values (101, 3, "David");
Note that the primary key for employees is a composite key made up of the employee ID and company ID. Note also that the company ID is a foreign key constraint on the primary key of companies, the exact situation (functionally) that you asked about.
The query showing who works for what company shows this in action:
select c.id, c.name, e.id, e.name
from companies c, employees e
where c.id = e.c_id
order by c.id, e.id
c.id c.name e.id e.name
---- ------ ---- ------
1 ABC 101 Allan
1 ABC 102 Bobby
2 DEF 101 Carol
3 HIJ 101 David
Can a column in a composite primary key also be a foreign key referencing a primary key of another table? Of course it can. The important question is, when is this a good idea?
The most common scenario is probably the intersection or junction table. Customers can have more than one Address (Shipping, Billing, etc) and Addresses can have more than one Customer using them. So the table CUSTOMER_ADDRESSES has a primary key which references both CUSTOMER and ADDRESS primary key (and for bonus points the ADDRESS_TYPE reference data table too).
My examples use Oracle 12c syntax:
create table customer_address
( customer_id number(38,0) not null
, address_id number(38,0) not null
, address_type_code varchar2(3) not null
, constraint customer_address_pk primary key
(customer_id, address_id, address_type_code)
, constraint customer_address_customer_fk foreign key
(customer_id) references customer(customer_id)
, constraint customer_address_address_fk foreign key
(address_id) references address(address_id)
, constraint customer_address_type_fk foreign key
(address_type_code) references address_type(address_type_code)
);
The second scenario occurs when the primary key of the child table is comprises the parent key and an identifier (usually a number) which is only unique within the parent key. For instance, an Order has an Order Header and some Order Lines. The Order is identified by the Order Header ID and its lines are identified by a monotonically incrementing number. The ORDER_LINE table may look like this:
create table order_line
( order_header_id number(38,0) not null
, order_line_no number(38,0) not null
, product_id number(38,0) not null
, qty number(38,0) not null
, constraint order_line_pk primary key
(order_header_id, order_line_no)
, constraint order_line_header_fk foreign key
(order_header_id) references order_header(order_header_id)
, constraint order_line_product_fk foreign key
(product_id) references product(product_id)
);
Note that we could model ORDER_LINE as another intersection table, with a primary key of (order_header_id, product_id) and relegate order_line_no to the status of ordinary attribute: it depends on the business rules we must represent.
This second scenario is rarer than you might think: composite primary keys are pretty rare in real life. For instance, I think the model presented in that other answer is weak. The chances are we will need to use Employee as a foreign key for a number of relationships (e.g. Manager, Assignment, Sales). Using a composite key for foreign keys is clumsy (more typing!). Furthermore, when we drill into these models we often find that one of the key columns is a natural key rather than a primary key, and so might be subject to change. Cascading changes to natural key columns in composite foreign keys is a PITN.
Hence it is common practice to use a surrogate (or synthetic) primary key, say using a sequence or identify column, and enforce the natural key with a unique constraint. The latter step is often forgotten but it is crucial to maintaining referential integrity. Given a situation in which we need to store details of Employees from several Companies, including the Companies' Employee Identifier we might have an EMPLOYEE table like this:
create table employee
( employee_id number(38,0) generated always as number
, company_id number(38,0) not null
, company_employee_id varchar2(128) not null
, name varchar2(128) not null
, constraint employee_pk primary key
(employee_id)
, constraint employee_uk unique
(company_id, company_employee_id)
, constraint employee_company_fk foreign key
(company_id) references company(company_id)
);
One situation where it is common to find composite primary keys cascaded to dependent tables is in data warehouses and other VLDBs. Here the composite key columns form part of a denormalization strategy to support Partitioning schemes and/or efficient access paths.
I am using access 2010 to do this.
I have created two tables and I would like to get it one to one and i don't seem to get it in to one to one relationship.
CREATE TABLE Person(
ID VARCHAR(1) UNIQUE,
Name VARCHAR(30),
PRIMARY KEY (ID)
);
CREATE TABLE Passport(
ID VARCHAR(1) UNIQUE,
Country VARCHAR(30),
PRIMARY KEY (ID),
FOREIGN KEY (ID) REFERENCES Person(ID)
);
From the little knowledge that I have, This should be a one to one relationship but it is a one to many. How do I get it one to one via ACCESS?
I have tried most things that I can find in books but with no success.
Please help.
You may simply be misled by the way relationships are displayed in the Relationships window. I ran your exact DDL and when I view the relationship in the Relationships window it looks like one-to-many...
...but when I right-click on the line joining the two tables and choose "Edit Relationship..." the dialog shows that it is actually one-to-one:
I beleive that in fact you created a one-to-one relationship as you wish.
the person id is unique
the passport id is unique
the passport id(unique) equals one person id(also unique).
So its one-to-one
However, it should be better design to have different ids for person and passport to have more clear design. Something like this:
CREATE TABLE Person(
PERSONID VARCHAR(1) UNIQUE,
Name VARCHAR(30),
PRIMARY KEY (PERSONID)
);
CREATE TABLE Passport(
PASSPORTID VARCHAR(1) UNIQUE,
Country VARCHAR(30),
PERSONID VARCHAR(1),
PRIMARY KEY (PASSPORTID),
FOREIGN KEY (PERSONID) REFERENCES Person(PERSONID)
);
This will also accomodate cases of persons with multiple passports.
Hello I have a problem seperating my 3 payment types: CASH, CREDIT, BANK
Each of them has different details.
The details are user defined which means that in a credit card payment (for ex: you should input your credit card details, bank details, cash details (currency and etc))
Business Process: The user will choose his payment type in a
combobox:
Then the user will input the details of that payment type.
This is what I've tried:
PaymentType(PaymentType_ID(PK), PaymentTypes)
...
.....
......
.........
then I'm stuck. I don't know how. Please help me. If you will answer explain to me please. I don't want to ask the same question here again. If I'm faced with a similar situation.
***I can't merge all of them into 1 table because they different columns. They have different specific details...
All three payment types have a few things in common. They all have an account number, an amount, a timestamp, a payment type, and some kind of transaction identifier. All the common attributes go in one table. (Some of the data types are deliberately naive, because they're application-dependent, and I don't know your application.)
create table payment_types (
payment_type_code char(2) primary key,
payment_type varchar(8) not null unique
);
insert into payment_types values
('Ca', 'Cash'),('Cr', 'Credit'),('Ba', 'Bank');
create table payments (
transaction_id integer primary key,
account_code varchar(5) not null, -- references accounts, not shown
amount_usd numeric(5,2) not null,
payment_type_code char(2) not null references payment_types (payment_type_code),
transaction_timestamp timestamp not null default current_timestamp,
unique (transaction_id, payment_type_code)
);
The unique constraint on {transaction_id, payment_type_code} lets SQL use that pair of columns as the target for a foreign key constraint. That's crucial to keeping the rows from the several tables from getting mixed up.
Each payment has different attributes, depending on the payment type. And each payment can be of only one type.
create table payment_cash (
transaction_id integer primary key,
payment_type_code char(2) not null default 'Ca' check (payment_type_code = 'Ca'),
foreign key (transaction_id, payment_type_code)
references payments (transaction_id, payment_type_code),
other_cash_columns char(1) not null
);
create table payment_credit (
transaction_id integer primary key,
payment_type_code char(2) not null default 'Cr' check (payment_type_code = 'Cr'),
foreign key (transaction_id, payment_type_code)
references payments (transaction_id, payment_type_code),
other_credit_columns char(1) not null
);
create table payment_bank (
transaction_id integer primary key,
payment_type_code char(2) not null default 'Ba' check (payment_type_code = 'Ba'),
foreign key (transaction_id, payment_type_code)
references payments (transaction_id, payment_type_code),
other_bank_columns char(1) not null
);
The default value and check constraint for payment_type_code makes it impossible, for example, to insert credit details for a cash payment. That would be possible--and it would be a Bad Thing--if the foreign key constraint used only the transaction id.
As a general rule, you don't cascade updates or deletes for financial transactions. Instead, correct errors by inserting a compensating transaction.
To make this more friendly to users and application code, create three updatable views that join the payments table to the detail. How to make them updatable depends on your dbms.
create view credit_payments_all as
select p.transaction_id, p.account_code, p.amount_usd,
p.payment_type_code, p.transaction_timestamp,
c.other_credit_columns
from payments p
inner join payment_credit c on c.transaction_id = p.transaction_id
-- Rules, triggers, stored procedures, functions, or whatever you need
-- to make this view updatable.
Then any code that needs to insert a credit transaction can just insert into the view credit_payments_all.
I've got 3 relevant tables in my database.
CREATE TABLE dbo.Group
(
ID int NOT NULL,
Name varchar(50) NOT NULL
)
CREATE TABLE dbo.User
(
ID int NOT NULL,
Name varchar(50) NOT NULL
)
CREATE TABLE dbo.Ticket
(
ID int NOT NULL,
Owner int NOT NULL,
Subject varchar(50) NULL
)
Users belong to multiple groups. This is done via a many to many relationship, but irrelevant in this case. A ticket can be owned by either a group or a user, via the dbo.Ticket.Owner field.
What would be the MOST CORRECT way describe this relationship between a ticket and optionally a user or a group?
I'm thinking that I should add a flag in the ticket table that says what type owns it.
You have a few options, all varying in "correctness" and ease of use. As always, the right design depends on your needs.
You could simply create two columns in Ticket, OwnedByUserId and OwnedByGroupId, and have nullable Foreign Keys to each table.
You could create M:M reference tables enabling both ticket:user and ticket:group relationships. Perhaps in future you will want to allow a single ticket to be owned by multiple users or groups? This design does not enforce that a ticket must be owned by a single entity only.
You could create a default group for every user and have tickets simply owned by either a true Group or a User's default Group.
Or (my choice) model an entity that acts as a base for both Users and Groups, and have tickets owned by that entity.
Heres a rough example using your posted schema:
create table dbo.PartyType
(
PartyTypeId tinyint primary key,
PartyTypeName varchar(10)
)
insert into dbo.PartyType
values(1, 'User'), (2, 'Group');
create table dbo.Party
(
PartyId int identity(1,1) primary key,
PartyTypeId tinyint references dbo.PartyType(PartyTypeId),
unique (PartyId, PartyTypeId)
)
CREATE TABLE dbo.[Group]
(
ID int primary key,
Name varchar(50) NOT NULL,
PartyTypeId as cast(2 as tinyint) persisted,
foreign key (ID, PartyTypeId) references Party(PartyId, PartyTypeID)
)
CREATE TABLE dbo.[User]
(
ID int primary key,
Name varchar(50) NOT NULL,
PartyTypeId as cast(1 as tinyint) persisted,
foreign key (ID, PartyTypeId) references Party(PartyID, PartyTypeID)
)
CREATE TABLE dbo.Ticket
(
ID int primary key,
[Owner] int NOT NULL references dbo.Party(PartyId),
[Subject] varchar(50) NULL
)
The first option in #Nathan Skerl's list is what was implemented in a project I once worked with, where a similar relationship was established between three tables. (One of them referenced two others, one at a time.)
So, the referencing table had two foreign key columns, and also it had a constraint to guarantee that exactly one table (not both, not neither) was referenced by a single row.
Here's how it could look when applied to your tables:
CREATE TABLE dbo.[Group]
(
ID int NOT NULL CONSTRAINT PK_Group PRIMARY KEY,
Name varchar(50) NOT NULL
);
CREATE TABLE dbo.[User]
(
ID int NOT NULL CONSTRAINT PK_User PRIMARY KEY,
Name varchar(50) NOT NULL
);
CREATE TABLE dbo.Ticket
(
ID int NOT NULL CONSTRAINT PK_Ticket PRIMARY KEY,
OwnerGroup int NULL
CONSTRAINT FK_Ticket_Group FOREIGN KEY REFERENCES dbo.[Group] (ID),
OwnerUser int NULL
CONSTRAINT FK_Ticket_User FOREIGN KEY REFERENCES dbo.[User] (ID),
Subject varchar(50) NULL,
CONSTRAINT CK_Ticket_GroupUser CHECK (
CASE WHEN OwnerGroup IS NULL THEN 0 ELSE 1 END +
CASE WHEN OwnerUser IS NULL THEN 0 ELSE 1 END = 1
)
);
As you can see, the Ticket table has two columns, OwnerGroup and OwnerUser, both of which are nullable foreign keys. (The respective columns in the other two tables are made primary keys accordingly.) The CK_Ticket_GroupUser check constraint ensures that only one of the two foreign key columns contains a reference (the other being NULL, that's why both have to be nullable).
(The primary key on Ticket.ID is not necessary for this particular implementation, but it definitely wouldn't harm to have one in a table like this.)
Another approach is to create an association table that contains columns for each potential resource type. In your example, each of the two existing owner types has their own table (which means you have something to reference). If this will always be the case you can have something like this:
CREATE TABLE dbo.Group
(
ID int NOT NULL,
Name varchar(50) NOT NULL
)
CREATE TABLE dbo.User
(
ID int NOT NULL,
Name varchar(50) NOT NULL
)
CREATE TABLE dbo.Ticket
(
ID int NOT NULL,
Owner_ID int NOT NULL,
Subject varchar(50) NULL
)
CREATE TABLE dbo.Owner
(
ID int NOT NULL,
User_ID int NULL,
Group_ID int NULL,
{{AdditionalEntity_ID}} int NOT NULL
)
With this solution, you would continue to add new columns as you add new entities to the database and you would delete and recreate the foreign key constraint pattern shown by #Nathan Skerl. This solution is very similar to #Nathan Skerl but looks different (up to preference).
If you are not going to have a new Table for each new Owner type then maybe it would be good to include an owner_type instead of a foreign key column for each potential Owner:
CREATE TABLE dbo.Group
(
ID int NOT NULL,
Name varchar(50) NOT NULL
)
CREATE TABLE dbo.User
(
ID int NOT NULL,
Name varchar(50) NOT NULL
)
CREATE TABLE dbo.Ticket
(
ID int NOT NULL,
Owner_ID int NOT NULL,
Owner_Type string NOT NULL, -- In our example, this would be "User" or "Group"
Subject varchar(50) NULL
)
With the above method, you could add as many Owner Types as you want. Owner_ID would not have a foreign key constraint but would be used as a reference to the other tables. The downside is that you would have to look at the table to see what the owner types there are since it isn't immediately obvious based upon the schema. I would only suggest this if you don't know the owner types beforehand and they won't be linking to other tables. If you do know the owner types beforehand, I would go with a solution like #Nathan Skerl.
Sorry if I got some SQL wrong, I just threw this together.
Yet another option is to have, in Ticket, one column specifying the owning entity type (User or Group), second column with referenced User or Group id and NOT to use Foreign Keys but instead rely on a Trigger to enforce referential integrity.
Two advantages I see here over Nathan's excellent model (above):
More immediate clarity and simplicity.
Simpler queries to write.
you can also use an enum to identify whether Owner is user or group like this:
CREATE TABLE dbo.Group
(
ID int NOT NULL,
Name varchar(50) NOT NULL
)
CREATE TABLE dbo.User
(
ID int NOT NULL,
Name varchar(50) NOT NULL
)
CREATE TYPE Enum_OwnerType AS ENUM ('Group', 'User');
CREATE TABLE dbo.Ticket
(
ID int NOT NULL,
Owner int NOT NULL,
OwnerType Enum_OwnerType NOT NULL,
Subject varchar(50) NULL
)
Maybe it's no better than any of proposed solutions, it might not offer any advantage. In fact, I think that this might require altering Enum_OwnerType and even ticket in order to change OwnerType, I guess... I hope it's useful anyway.
I have many cases like this and I just use polymorphic ability like below:
example
I have turnovers table that have this columns id, amount, user_id and I need to know the refrence of every records, So I just add two Fields table_id and table_type and my final turnovers table is like id, amount, user_id,table_id, table_type.
if new record is about order record inserted like this
[1,25000,2,22,order]
and if new record is about increment credit like this
[1,25000,2,23,credit]
note
if using M:M tables its take so much time two retrieve the records
and my way
Cons is turnovers table records number is grows up
Pons is more flexible in new records and readable and search ability
nathan_jr's 4th option (model an entity that acts as a base for both Users and Groups, and have tickets owned by that entity) doesn't enforce referential integrity on PartyId. You'd have to do that on the application layer which invites all sorts of trouble. Can't really call it an antipattern when django's genericforeignkey implements the same solution, but no doubt you can design something more robust and performant using your framework's orm (using something like django's Multi-table inheritance)
CREATE TABLE dbo.OwnerType
(
ID int NOT NULL,
Name varchar(50) NULL
)
insert into OwnerType (Name) values ('User');
insert into OwnerType (Name) values ('Group');
I think that would be the most general way to represent what you want instead of using a flag.
I had posted a similar question before, but this is more specific. Please have a look at the following diagram:
The explanation of this design is as follows:
Bakers produce many Products
The same Product can be produced by more than one Baker
Bakers change their pricing from time-to-time for certain (of their) Products
Orders can be created, but not necessarily finalised
The aim here is to allow the store manager to create an Order "Basket" based on whatever goods are required, and also allow the system being created to determine the best price at that time based on what Products are contained within the Order.
I therefore envisaged the ProductOrders table to initially hold the productID and associated orderID, whilst maintaining a null (undetermined) value for bakerID and pricingDate, as that would be determined and updated by the system, which would then constitute a finalised order.
Now that you have an idea of what I am trying to do, please advise me on how to to best set these relationships up.
Thank you!
If I understand correctly, an unfinalised order is not yet assigned a baker / pricing (meaning when an order is placed, no baker has yet been selected to bake the product).
In which case, the order is probably placed against the Products Table and then "Finalized" against the BakersProducts table.
A solution could be to give ProductsOrders 2 separate "ProductID's", one being for the original ordered ProductId (i.e. Non Nullable) - say ProductId, and the second being part of the Foreign key to the assigned BakersProducts (say ProductId2). Meaning that in ProductsOrders, the composite foreign keys BakerId, ProductId2 and PricingDate are all nullable, as they will only be set once the order is Finalized.
In order to remove this redundancy, what you might also consider is using surrogate keys instead of the composite keys. This way BakersProducts would have a surrogate PK (e.g. BakersProductId) which would then be referenced as a nullable FK in ProductsOrders. This would also avoid the confusion with the Direct FK in ProductsOrders to Product.ProductId (which from above, was the original Product line as part of the Order).
HTH?
Edit:
CREATE TABLE dbo.BakersProducts
(
BakerProductId int identity(1,1) not null, -- New Surrogate PK here
BakerId int not null,
ProductId int not null,
PricingDate datetime not null,
Price money not null,
StockLevel bigint not null,
CONSTRAINT PK_BakerProducts PRIMARY KEY(BakerProductId),
CONSTRAINT FK_BakerProductsProducts FOREIGN KEY(ProductId) REFERENCES dbo.Products(ProductId),
CONSTRAINT FK_BakerProductsBaker FOREIGN KEY(BakerId) REFERENCES dbo.Bakers(BakerId),
CONSTRAINT U_BakerProductsPrice UNIQUE(BakerId, ProductId, PricingDate) -- Unique Constraint mimicks the original PK for uniqueness ... could also use a unique index
)
CREATE TABLE dbo.ProductOrders
(
OrderId INT NOT NULL,
ProductId INT NOT NULL, -- This is the original Ordered Product set when order is created
BakerProductId INT NULL, -- This is nullable and gets set when Order is finalised with a baker
OrderQuantity BIGINT NOT NULL,
CONSTRAINT FK_ProductsOrdersBakersProducts FOREIGN KEY(BakersProductId) REFERENCES dbo.BakersProducts(BakerProductId)
.. Other Keys here
)