I have a question that circulates a lot in my head, I'd like to find a response that can unlock me ... in the field of database (DBMS and Modeling MPD).
Is it possible to find a circular relationship between two (or more)tables
such as:
Create Table TA ( TA_ID INTEGER not null, A SMALLINT, AA NUMERIC(5,0),TB_ID INTEGER, constraint PK_TA primary key (TA_ID) );
Create Table TB (TB_ID INTEGER not null, B SMALLINT, BB FLOAT, TA_ID INTEGER, constraint PK_TB primary key (TB_ID) );
alter TABLE TB add constraint Fk_TB_TA foreign key (TA_ID) references TA (TA_ID);
alter TABLE TA add constraint Fk_TA_TB foreign key (TB_ID) references TB (TB_ID);
If you're looking for a set of tables that refer to each other through FKs, then you could try this code.
SELECT *
FROM
sys.foreign_keys fk
WHERE
EXISTS
(SELECT * FROM sys.foreign_keys fk2 WHERE fk2.referenced_object_id = fk.parent_object_id AND fk2.parent_object_id = fk.referenced_object_id)
Ok, is it possible? With the NOT NULL constraints on both - NO. If you allow NULLS in one, then YES.
Is it a good idea, I can't think of a reason why you'd do this but sometimes problems in the real world are complicated and I generally don't say never.
A quick search pulls up this question that shows that this IS happening in the real world, check this out: How to do an INSERT into tables with circular relationships (SQL SERVER)
Related
I have a database which has three tables
Messages - PK = MessageId
Drafts - PK = DraftId
History - FK = RelatedItemId
The History table has a single foreign Key [RelatedItemId] which maps to one of the two Primary keys in Messages and Drafts.
Is there a name for this relationship?
Is it just bad design?
Is there a better way to design this relationship?
Here are the CREATE TABLE statements for this question:
CREATE TABLE [dbo].[History](
[HistoryId] [uniqueidentifier] NOT NULL,
[RelatedItemId] [uniqueidentifier] NULL,
CONSTRAINT [PK_History] PRIMARY KEY CLUSTERED ( [HistoryId] ASC )
)
CREATE TABLE [dbo].[Messages](
[MessageId] [uniqueidentifier] NOT NULL,
CONSTRAINT [PK_Messages] PRIMARY KEY CLUSTERED ( [MessageId] ASC )
)
CREATE TABLE [dbo].[Drafts](
[DraftId] [uniqueidentifier] NOT NULL,
CONSTRAINT [PK_Drafts] PRIMARY KEY CLUSTERED ( [DraftId] ASC )
)
In a short description the solution you have used is called:
Polymorphic Association
Objective: Reference Multiple Parents
Resulting anti-pattern: Use dual-purpose foreign key, violating first normal form (atomic issue), loosing referential integrity
Solution: Simplify the Relationship
More information about the problem.
BTW createing a common super-table will help you:
Is there a name for this relationship?
There is no standard name that I'm aware of, but I've heard people using the term "generic FKs" or even "inner-platform effect".
Is it just bad design?
Yes.
The reason: it prevents you from declaring a FOREIGN KEY, and therefore prevents the DBMS from enforcing referential integrity directly. Therefore you must enforce it trough imperative code, which is surprisingly difficult.
Is there a better way to design this relationship?
Yes.
Create separate FOREIGN KEY for each referenced table. Make them NULL-able, but make sure exactly one of them is non-NULL, through a CHECK constraint.
Alternatively, take a look at inheritance.
Best practice I have found is to create a Function that returns whether the passed in value exists in either of your Messages and Drafts PK columns. You can then add a constraint on the column on the History that calls this function and will only insert if it passes (i.e. it exists).
Adding non-parsed example Code:
CREATE FUNCTION is_related_there (
IN #value uniqueidentifier )
RETURNS TINYINT
BEGIN
IF (select count(DraftId) from Drafts where DraftId = #value + select count(MessageId) from Messages where MessageId = #value) > 0 THEN
RETURN 1;
ELSE
RETURN 0;
END IF;
END;
ALTER TABLE History ADD CONSTRAINT
CK_HistoryExists CHECK (is_related_there (RelatedItemId) = 1)
Hope that runs and helps lol
Sorry about the title, couldn't think of a better way to write it.
Here's my problem...
I have 2 tables in my database [Drawings] and [Revisions];
[Drawings] 1-----* [Revisions]
ProjectId(pk) ProjectId(pk)(fk)
DrawingNo(pk) DrawingNo(pk)(fk)
RevisionNo(pk)
LatestRevision
There is a foreign key in [revisions] referencing [drawings] on [ProjectId] and [DrawingNo].
I need to implement a way of enforcing that the drawings latest revision number equals a corresponding revision number in the revisions table:
... WHERE [Drawings].[LatestRevision] NOT IN (
SELECT [RevisionNo]
FROM [Revisions]
WHERE [Drawings].[ProjectId] = [Revisions].[ProjectId]
AND [Drawings].[DrawingNo] = [Revisions].[DrawingNo])
How would I put something like this into a foreign key?
I need this to work on sql server 2008 express onwards.
Thanks in advance for any help!
Schema:
TABLE Drawings
( ProjectId varchar,
DrawingNo varchar,
LatestRevisions varchar,
...other columns
PRIMARY KEY(ProjectId, DrawingNo)
)
TABLE Revisions
( ProjectId varchar,
DrawingNo varchar,
RevisionNo varchar,
...other columns
PRIMARY KEY(ProjectId, DrawingNo, RevisionNo)
FOREIGN KEY(ProjectId, DrawingNo) REFERENCES (Drawings(ProjectId, DrawingNo))
)
Drawing 'A' can have revision '1', and Drawing 'B' can have a different revision '1',
Revision number by itself is not unique
I will take the schema as follows:
TABLE drawings
( projectid integer,
drawingno integer,
latestRevision integer,
primary key (projectid, drawingno)
)
TABLE revisions
( revisionno integer primary key,
projectid integer,
drawingno integer,
foreign key (projectid, drawingno)
references (drawings (projectid, drawingno))
)
In this case, I would issue:
ALTER TABLE drawings
ADD FOREIGN KEY (latestRevision)
REFERENCES (revisions(revisionNo))
This would mean that every revisions.revisionNo is unique and the column drawings.latestRevision is a foreign key that references the primary key of revisions table, that is, revisionNo.
Please let me know if there are any changes to the schema you have.
Also, the foreign key is enforced only if it is referencing a primary key of another table. If revisions.revisionno is not a primary key or if the primary key constraint is disabled on this column, then the ALTER TABLE .. ADD FOREIGN KEY statement is bound to return an error.
The following structures replaces your tables with views and looks similar to what you describe, except it's instead maintained behind the scenes rather than being an explicit foreign key. I don't know what operations you'd want to support on Revisions, at the moment I only support INSERT:
create table dbo._Drawings (
ProjectId int not null,
DrawingId int not null,
constraint PK_Drawings PRIMARY KEY (ProjectID,DrawingID)
)
go
create table dbo._Revisions (
ProjectID int not null,
DrawingID int not null,
RevisionNo int not null,
_PreviousRevision as CASE WHEN RevisionNo > 1 THEN RevisionNo - 1 END persisted,
_NextRevision int null,
constraint PK_Revisions PRIMARY KEY (ProjectID,DrawingID,RevisionNo),
constraint FK_Revisions_Drawings FOREIGN KEY (ProjectID,DrawingID)
references _Drawings (ProjectID,DrawingID),
constraint CK_RevisionNos CHECK (RevisionNo >= 1),
constraint UK_Revisions_Previous UNIQUE (ProjectID,DrawingID,_PreviousRevision),
constraint UK_Revisions_Next UNIQUE (ProjectID,DrawingID,_NextRevision),
constraint FK_Revisions_Previous FOREIGN KEY (ProjectID,DrawingID,_PreviousRevision)
references _Revisions (ProjectID,DrawingID,RevisionNo),
constraint FK_Revisions_Next FOREIGN KEY (ProjectID,DrawingID,_NextRevision)
references _Revisions (ProjectID,DrawingID,RevisionNo)
)
The above two tables are the "backing store" for the data. The _Revisions table ensures that the revision sequences are strictly monotonically increasing from 1. Each row maintains a foreign key to its immediate preceding and succeeding revisions, except the first and last, for which NULLs substitute (but the unique constraints ensure only one of each exists for each ProjectID,DrawingID combination.
create view dbo.Drawings
with schemabinding
as
select
d.ProjectID,
d.DrawingID,
r.RevisionNo as LatestRevision
from
dbo._Drawings d
left join
dbo._Revisions r
on
d.ProjectId = r.ProjectID and
d.DrawingId = r.DrawingID and
r._NextRevision is null
The above view mimics your asked for Drawings table and would be used for any actual data access. If you wanted to enforce an invariant that each drawing must have at least one revision, you could switch the left join to an inner join and make this an indexed view. You'd need to add a trigger to support INSERTs, in much the same way as the below does for Revisions, which then populates both tables.
create view dbo.Revisions
with schemabinding
as
select
ProjectID,
DrawingID,
RevisionNo
from
dbo._Revisions
This view creates the impression that Revisions is as simple as in your query
create trigger T_Revisions_I
on dbo.Revisions
instead of insert
as
;with SplitData as (
select ProjectID,DrawingID,RevisionNo,RevisionNo-1 as Prev, Seq
from inserted cross join (select 1 union all select 2) t(Seq)
)
merge into dbo._Revisions r
using SplitData s
on
r.ProjectID = s.ProjectID and
r.DrawingID = s.DrawingID and
(
(s.Seq = 1 and r.RevisionNo = s.Prev) or
(s.Seq = 2 and r.RevisionNo = s.RevisionNo)
)
when matched and s.Seq = 1
then update set _NextRevision = s.RevisionNo
when not matched and s.Seq = 2
then insert (ProjectID,DrawingID,RevisionNo) values (s.ProjectID,s.DrawingID,s.RevisionNo)
;
And finally, this trigger is responsible for maintaining the _Revisions structure in the way that the constraints I created above require. The trick is that we use a MERGE statement so that at the same time as we insert the new row, we also update the previous row so that it's _NextRevision column is no longer null and references the row that we're inserting.
More triggers can be added to support more advanced usage.
I have Entity Relationship Model (ERD) where entities IndividualCategory and TeamCategory relate to entity Category. Now I want to create tables in Oracle DB. I started like this:
CREATE TABLE Category(
category_id INT PRIMARY KEY,
...
);
CREATE TABLE Individual_category(
category_id INT CONSTRAINT fk_cat_indivcat REFERENCES Category(category_id),
...,
CONSTRAINT pk_indivgamecat PRIMARY KEY (category_id)
);
CREATE TABLE Team_category(
category_id INT CONSTRAINT fk_cat_teamcat REFERENCES Category(category_id),
...,
CONSTRAINT pk_teamcat PRIMARY KEY (category_id)
);
This combination of Foreign key and Primary key constraints assures that for every Individual_category there will be corresponding record in Category "super" table (or "parent" table ?). And there will be only one IndividualCategory record for particular Category record. Same for Team_category.
To enforce inheritance I need one more constraint: A constraint that assures that for every record in Category there will be either record in IndividualCategory (X)OR a record in TeamCategory but not both.
How do I create such constraint ?
EDIT: This is what I meant by 'inheritance in E-R model'. This is from my DB teacher's slides (they call it "Entity sub-type" there but they sometimes call it just inheritance):
A completely different way to do this using deferrable constraints:
CREATE TABLE Category(
category_id INT PRIMARY KEY,
team_category_id INT,
individual_category_id INT,
...
);
CREATE TABLE Individual_category(
individual_category_id INT PRIMARY KEY,
category_id INT NOT NULL,
...,
);
CREATE TABLE Team_category(
team_category_id INT PRIMARY KEY,
category_id INT NOT NULL,
...,
);
Make sure a Category is a TeamCategory xor an IndividualCategory:
alter table Category add constraint category_type_check check
( (team_category_id is null and individual_category_id is not null)
or (team_category_id is not null and individual_category_id is null)
);
Create deferrable integrity constraints so that one can insert a Category and Team/Individual_Category within the same transaction; otherwise, you couldn't insert a Category before the TeamCategory/IndividualCategory, and vice-versa. A catch-22.
alter table category add constraint category_team_fk
foreign key (team_category_id)
references team_category (team_category_id)
deferrable initially deferred;
alter table category add constraint category_individual_fk
foreign key (individual_category_id)
references individual_category (individual_category_id)
deferrable initially deferred;
alter table individual_category add constraint individual_category_fk
foreign_key (category_id)
references category (category_id)
deferrable initially deferred;
alter table team_category add constraint team_category_fk
foreign_key (category_id)
references category (category_id)
deferrable initially deferred;
How one may do this is, using a simplified example:
CREATE TABLE Category(
category_id INT PRIMARY KEY,
category_type varchar2(300) not null,
...
[list of required attributes for only individual category, but nullable],
[list of required attributes for only team category, but nullable]
);
alter table category add constraint check_category_individual check
( category_type <> 'INDIVIDUAL'
or ( category_type = 'INDIVIDUAL'
and [list of individual category attributes IS NOT NULL]
)
);
alter table category add constraint check_category_team check
( category_type <> 'TEAM'
or ( category_type = 'TEAM'
and [list of team category attributes IS NOT NULL]
)
);
You could then create views, like:
create view individual_category as
select category_id, [base category attributes], [individual category attributes]
from category
where category_type = 'INDIVIDUAL;
You can even put an INSTEAD OF trigger on the view so it would be appear to the application to be a table like any other.
Another way to implement complex constraints in the database is using materialized views (MVs).
For this example an MV could be defined as follows:
create materialized view bad_category_mv
refresh complete on commit
as
select c.category_id
from category c
left outer join individual_category i on i.category_id = c.category_id
left outer join team_category i on t.category_id = c.category_id
where ( (i.category_id is null and t.category_id is null)
or (i.category_id is not null and t.category_id is not null)
);
alter table bad_category_mv
add constraint bad_category_mv_chk
check (1=0) deferrable;
So the MV is populated only for categories that break the rule, but then the check constraint ensures that any transaction that results in a row in the MV will fail (since 1=0 is never true).
I have blogged about this approach in the past here.
CAUTION: Although I am interested in this approach I have never used it "in anger" in a production database. Careful benchmarking is needed to ensure that the overhead of the full MV refresh whenever the data is changed is not too high.
ERD inheritance is a classic example of the gen-spec design pattern. There are numerous articles on how to design gen-spec in a relational DBMS like Oracle. you can find some of them by doing a Google search on "generalization specialization relational modeling".
Much of what you will see in these articles has already been outlined by other responses to this question. This topic has surfaced many times in SO. For a sample prior discussion, click here.
The main feature of the classic solution is that the specialized tables have an id column that is both a primary key and a foreign key that references the id column of the generalized table. In this manner, the subentities do not acquire an identity of their own. The feature you really need to watch out for is the constraint that implements disjunction. Not all of the articles enforce this rule in their presented solution.
Is there a way to define a constraint using SQL Server 2005 to not only ensure a foreign key exists in another table, but also meets a certain criteria?
For example, say I have two tables:
Table A
--------
Id - int
FK_BId - int
Table B
--------
Id - int
Name - string
SomeBoolean - bit
Can I define a constraint that sayd FK_BId must point to a record in Table B, AND that record in Table B must have SomeBoolean = true? Thanks in advance for any help you can provide.
You can enforce the business rule using a composite key on (Id, SomeBoolean), reference this in table A with a CHECK constraint on FK_BSomeBoolean to ensure it is always TRUE. BTW I'd recommend avoiding BIT and instead using CHAR(1) with domain checking e.g.
CHECK (SomeBoolean IN ('F', 'T'))
The table structure could look like this:
CREATE TABLE B
(
Id INTEGER NOT NULL UNIQUE, -- candidate key 1
Name VARCHAR(20) NOT NULL UNIQUE, -- candidate key 2
SomeBoolean CHAR(1) DEFAULT 'F' NOT NULL
CHECK (SomeBoolean IN ('F', 'T')),
UNIQUE (Id, SomeBoolean) -- superkey
);
CREATE TABLE A
(
Ib INTEGER NOT NULL UNIQUE,
FK_BId CHAR(1) NOT NULL,
FK_BSomeBoolean CHAR(1) DEFAULT 'T' NOT NULL
CHECK (FK_BSomeBoolean = 'T')
FOREIGN KEY (FK_BId, FK_BSomeBoolean)
REFERENCES B (Id, SomeBoolean)
);
I think what you're looking for is out of the scope of foreign keys, but you could do the check in triggers, stored procedures, or your code.
If it is possible to do, I'd say that you would make it a compound foreign key, using ID and SomeBoolean, but I don't think it actually cares what the value is.
In some databases (I can't check SQL Server) you can add a check constraint that references other tables.
ALTER TABLE a ADD CONSTRAINT fancy_fk
CHECK (FK_BId IN (SELECT Id FROM b WHERE SomeBoolean));
I don’t believe this behavior is standard.
This question is pretty much similar to this one, but for SQL Server 2005 :
I have 2 tables in my database:
--'#' denotes the primary key
[Libraries]
#ID #Application Name
1 MyApp Title 1
2 MyApp Title 2
[Content]
#ID Application LibraryID Content
10 MyApp 1 xxx
11 MyApp 1 yyy
(the database is obviously much more complex and having this double key makes sense)
Each library is identified by its unique ID and Application name. I'm trying to ensure that each content is properly referencing an existing library.
When creating the constraint (using the Wizard) as
Primary key table Foreign key table
[Libraries] [Content]
ID ---> LibraryID
Application ---> Application
I have the following error:
The columns in table 'Libraries' do
not match an existing primary key or
UNIQUE constraint
Do you have any idea of what is going on? and if it's possible at all using SQL Server? (I can't modify the [Library] table at all)
Thanks a lot for your help!
Of course it's possible to create a foreign key relationship to a compound (more than one column) primary key. You didn't show us the statement you're using to try and create that relationship - it should be something like:
ALTER TABLE dbo.Content
ADD CONSTRAINT FK_Content_Libraries
FOREIGN KEY(LibraryID, Application)
REFERENCES dbo.Libraries(ID, Application)
Is that what you're using?? If (ID, Application) is indeed the primary key on dbo.Libraries, this statement should definitely work.
Luk: just to check - can you run this statement in your database and report back what the output is??
SELECT
tc.TABLE_NAME,
tc.CONSTRAINT_NAME,
ccu.COLUMN_NAME
FROM
INFORMATION_SCHEMA.TABLE_CONSTRAINTS tc
INNER JOIN
INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE ccu
ON ccu.TABLE_NAME = tc.TABLE_NAME AND ccu.CONSTRAINT_NAME = tc.CONSTRAINT_NAME
WHERE
tc.TABLE_NAME IN ('Libraries', 'Content')
Note that the fields must be in the same order. If the Primary Key you are referencing is specified as (Application, ID) then your foreign key must reference (Application, ID) and NOT (ID, Application) as they are seen as two different keys.
The key is "the order of the column should be the same"
Example:
create Table A (
A_ID char(3) primary key,
A_name char(10) primary key,
A_desc desc char(50)
)
create Table B (
B_ID char(3) primary key,
B_A_ID char(3),
B_A_Name char(10),
constraint [Fk_B_01] foreign key (B_A_ID,B_A_Name) references A(A_ID,A_Name)
)
the column order on table A should be --> A_ID then A_Name; defining the foreign key should follow the same order as well.
The Content table likely to have multiple duplicate Application values that can't be mapped to Libraries. Is it possible to drop the Application column from the Libraries Primary Key Index and add it as a Unique Key Index instead?
I had the same problem and I think I have the solution.
If your field Application in table Library has a foreign key that references a field in another table (named Application I would bet), then your field Application in table Library has to have a foreign key to table Application too.
After that you can do your composed foreign key.
Excuse my poor english, and sorry if I'm wrong.