One to many dynamic database relations issue - sql-server

I just need some confirmation is database designed like this is fine or not. And if not am I doing something wrong here.
I have following tables:
TableA{TableAID,...}
TableB{TableBID,...}
TableC{TableCID,...}
etc.
And I have one table that I use like some kind of 'news feed'. When I add something in any table A,B,C I also add row in this table.
Feed{FeedID, TypeID, ReferenceID,...}
FeedID is PK auto increment
TypeID is number that reference types table and based on this ID I know is row in this table from table A,B,C.
ReferenceId is ID of item in tables A,B,C.
A,B,C tables all have different fields.
Now when I want to get feed data I also need to grab some data from each of this table to use it in application. In my query to get this I use a lot SELECT CASE CLAUSE like:
I first join to all tables in query (A,B,C)
...
CASE Feed.TypeId
WHEN 1 THEN tableA.someData
WHEN 2 THEN tableB.someData
WHEN 3 THEN tableC.someData
END AS Data,
...

Without getting into suitability of this for a specific purpose, your supertype-subtype model is "reversed".
So DDL looks something like
CREATE TABLE Feed (
FeedID integer IDENTITY(1,1) not null
, FeedType char(1) not null
-- Common_Columns_Here
, Common_Column varchar(20)
);
ALTER TABLE Feed ADD CONSTRAINT pk_Feed PRIMARY KEY (FeedID) ;
CREATE TABLE Feed_A (
FeedID integer not null
-- A_Specific_Columns_Here
, A_Specific_Column varchar(20)
);
ALTER TABLE Feed_A ADD
CONSTRAINT pk_Feed_A PRIMARY KEY (FeedID)
, CONSTRAINT fk1_Feed_A FOREIGN KEY (FeedID) REFERENCES Feed(FeedID) ;
CREATE TABLE Feed_B (
FeedID integer not null
-- B_Specific_Columns_Here
, B_Specific_Column varchar(20)
);
ALTER TABLE Feed_B ADD
CONSTRAINT pk_Feed_B PRIMARY KEY (FeedID)
, CONSTRAINT fk1_Feed_B FOREIGN KEY (FeedID) REFERENCES Feed(FeedID) ;
CREATE TABLE Feed_C (
FeedID integer not null
-- C_Specific_Columns_Here
, C_Specific_Column varchar(20)
);
ALTER TABLE Feed_C ADD
CONSTRAINT pk_Feed_C PRIMARY KEY (FeedID)
, CONSTRAINT fk1_Feed_C FOREIGN KEY (FeedID) REFERENCES Feed(FeedID) ;
Now, in order to read from this structure, create a view first
create view vFeed as
select
f.FeedID
, FeedType
, Common_Column
, A_Specific_Column
, B_Specific_Column
, C_Specific_Column
from Feed as f
left join Feed_A as a on (a.FeedID = f.FeedID and f.FeedType = 'A')
left join Feed_B as b on (b.FeedID = f.FeedID and f.FeedType = 'B')
left join Feed_C as c on (c.FeedID = f.FeedID and f.FeedType = 'C')
;
Look what happens when I want to select data which I know is from feed A. Note that FeedType is not specified in this query, only column name which belongs to Feed_A (and common column).
select
FeedID
, Common_Column
, A_Specific_Column
from vFeed;
Notice that execution plan shows only Feed and Feed_A tables, query optimizer eliminated tables _B and _C; no need to touch those two.
In other words, you can ask for a specific feed data by simply using only specific columns in a query, and let the optimizer sort everything else out -- no need for CASE ... WHEN .. acrobatics from your example.

As I suggested in my comment (and along with #Andomar's wisdom), I think something like this would work better:
CREATE TABLE dbo.FeedTypes
(
FeedTypeID INT IDENTITY(1,1) PRIMARY KEY,
SomedataA INT,
SomedataB VARCHAR(32),
SomedataC DATETIME
--, ... other columns
);
CREATE TABLE dbo.Feeds
(
FeedID INT IDENTITY(1,1) PRIMARY KEY,
FeedTypeID INT NOT NULL FOREIGN KEY
REFERENCES dbo.FeedTypes(FeedTypeID)
--, ... other columns
);
You could enforce the presence/absence of data in the relevant columns for a given type using complex check constraints or triggers. But you'd have to have pretty complex logic (as you would in your current model) if a feed can change types easily.

Add all the data you wish to display in the "News Feed" in the Feed table. It is duplicate data, but it will make your life a lot easier in the long run.
It also ensures that your newsfeed stays historically correct. This means that when I update a record in one of the three tables, the "old" feed data stays intact instead of being updated with the new values.

Related

Unique constraint including field in referenced table

Is is possible to add a uniqueness constraint to a table that would include a field from a referenced table? If so, how can that be done?
(Note that I'm using Microsoft SQL Server.)
To explain why this is necessary, here's a simplified version of my problem.
Firstly, let's say I have a table of things, defining the fixed properties of each individual thing:
CREATE TABLE dbo.things(
id INT IDENTITY(1,1) NOT NULL
CONSTRAINT [things$PrimaryKey] PRIMARY KEY CLUSTERED,
thing_name NVARCHAR(20) NOT NULL
CONSTRAINT [things$thing_name] UNIQUE,
-- etc.
);
I also have different configurations, for a range of different elements, not just things, and these are captured in a table too:
CREATE TABLE dbo.configurations(
id INT IDENTITY(1,1) NOT NULL
CONSTRAINT [configurations$PrimaryKey] PRIMARY KEY CLUSTERED,
config_name NVARCHAR(20) NOT NULL
CONSTRAINT [configurations$config_name] UNIQUE,
-- etc.
);
I then want to have different configurations for each thing:
CREATE TABLE dbo.thing_configurations(
id INT IDENTITY(1,1) NOT NULL
CONSTRAINT [thing_configurations$PrimaryKey] PRIMARY KEY CLUSTERED,
thing_id INT NOT NULL
CONSTRAINT [thing_configurations$things_fk] REFERENCES dbo.things(id),
quantity INT NOT NULL
CONSTRAINT [thing_configurations$quantity] CHECK(quantity >= 0),
-- etc.
);
Next, I want to identify the configuration of each thing for each particular configuration in an association table. I can ensure that the configuration and associated thing configuration are unique, but I want to ensure that each thing has no more than one thing configuration for each configuration.
CREATE TABLE dbo.config_thing_configurations(
config_id INT NOT NULL
CONSTRAINT [config_thing_configurations$configurations_fk] REFERENCES dbo.configurations(id),
thing_config_id INT NOT NULL
CONSTRAINT [config_thing_configurations$thing_configurations_fk] REFERENCES dbo.thing_configurations(id)
-- How can I ensure that I do not have multiple configurations of a each thing in a configuration.
);
More specifically, say I have these things:
id
thing_name
1
Apple
2
Bicycle
...
...
And these configurations:
id
config_name
1
Base configuration.
2
Experiment #1
...
...
And these thing configurations:
id
thing_id
quantity
1
1 (Apple)
20
2
1 (Apple)
30
3
2 (Bicycle)
5
...
...
How can I prevent each configuration from having two or more different configurations of each individual thing in the thing_configurations table?
config_id
thing_config_id
1
1 (20 Apples)
1
2 (30 Apples)
...
...
For example, in the above table, I have two conflicting configurations for Apples in configuration 1.
Ideally, I'd like to be able to enforce a uniqueness constraint for config_id and the thing_id referenced through the thing_config_id. Is that possible?
So this is possible, through the creation of an indexed view in combination with a unique index.
In this specific example, this is achieved by first creating a view that associates the configuration and thing identifiers:
CREATE VIEW dbo.thing_config_view WITH SCHEMABINDING AS
SELECT
thing_configurations.thing_id,
config_thing_configurations.config_id
FROM dbo.config_thing_configurations
INNER JOIN dbo.thing_configurations ON config_thing_configurations.thing_config_id = thing_configurations.id;
Next, we create a unique index on those fields from the view:
CREATE UNIQUE CLUSTERED INDEX [thing_config_view$unique_thing_configurations] ON dbo.thing_config_view(thing_id, config_id);
and it works exactly as required.
This could be handled through Triggers. The trigger content would depend on what you want to occur when a query is called that would violate the condition. Something like this for insert (untested):
CREATE TRIGGER tr_config_thing_configurations ON config_thing_configurations
INSTEAD OF INSERT
AS
IF EXISTS(
SELECT 1
FROM inserted i
INNER JOIN thing_configurations tc ON tc.id = i.thing_config_id
INNER JOIN things t ON t.id = tc.thing_id
WHERE t.id IN (
SELECT it.id
FROM config_thing_configurations ictc
INNER JOIN thing_configurations itc ON itc.id = ictc.thing_config_id
INNER JOIN things it ON it.id = itc.thing_id
WHERE
ictc.config_id = i.config_id
)
BEGIN
RAISERROR ('Configuration already has a configuration for thing', 16, 1);
ROLLBACK TRANSACTION;
RETURN
END
INSERT INTO config_thing_configurations (config_id, thing_config_id) SELECT config_id, thing_config_id FROM inserted;
GO
You would need another for update or you could write a single to handle both cases.

SQL Server check constraints - only one particular value per group [duplicate]

How could I set a constraint on a table so that only one of the records has its isDefault bit field set to 1?
The constraint is not table scope, but one default per set of rows, specified by a FormID.
Use a unique filtered index
On SQL Server 2008 or higher you can simply use a unique filtered index
CREATE UNIQUE INDEX IX_TableName_FormID_isDefault
ON TableName(FormID)
WHERE isDefault = 1
Where the table is
CREATE TABLE TableName(
FormID INT NOT NULL,
isDefault BIT NOT NULL
)
For example if you try to insert many rows with the same FormID and isDefault set to 1 you will have this error:
Cannot insert duplicate key row in object 'dbo.TableName' with unique
index 'IX_TableName_FormID_isDefault'. The duplicate key value is (1).
Source: http://technet.microsoft.com/en-us/library/cc280372.aspx
Here's a modification of Damien_The_Unbeliever's solution that allows one default per FormID.
CREATE VIEW form_defaults
AS
SELECT FormID
FROM whatever
WHERE isDefault = 1
GO
CREATE UNIQUE CLUSTERED INDEX ix_form_defaults on form_defaults (FormID)
GO
But the serious relational folks will tell you this information should just be in another table.
CREATE TABLE form
FormID int NOT NULL PRIMARY KEY
DefaultWhateverID int FOREIGN KEY REFERENCES Whatever(ID)
From a normalization perspective, this would be an inefficient way of storing a single fact.
I would opt to hold this information at a higher level, by storing (in a different table) a foreign key to the identifier of the row which is considered to be the default.
CREATE TABLE [dbo].[Foo](
[Id] [int] NOT NULL,
CONSTRAINT [PK_Foo] PRIMARY KEY CLUSTERED
(
[Id] ASC
) ON [PRIMARY]
) ON [PRIMARY]
GO
CREATE TABLE [dbo].[DefaultSettings](
[DefaultFoo] [int] NULL
) ON [PRIMARY]
GO
ALTER TABLE [dbo].[DefaultSettings] WITH CHECK ADD CONSTRAINT [FK_DefaultSettings_Foo] FOREIGN KEY([DefaultFoo])
REFERENCES [dbo].[Foo] ([Id])
GO
ALTER TABLE [dbo].[DefaultSettings] CHECK CONSTRAINT [FK_DefaultSettings_Foo]
GO
You could use an insert/update trigger.
Within the trigger after an insert or update, if the count of rows with isDefault = 1 is more than 1, then rollback the transaction.
CREATE VIEW vOnlyOneDefault
AS
SELECT 1 as Lock
FROM <underlying table>
WHERE Default = 1
GO
CREATE UNIQUE CLUSTERED INDEX IX_vOnlyOneDefault on vOnlyOneDefault (Lock)
GO
You'll need to have the right ANSI settings turned on for this.
I don't know about SQLServer.But if it supports Function-Based Indexes like in Oracle, I hope this can be translated, if not, sorry.
You can do an index like this on suposed that default value is 1234, the column is DEFAULT_COLUMN and ID_COLUMN is the primary key:
CREATE
UNIQUE
INDEX only_one_default
ON my_table
( DECODE(DEFAULT_COLUMN, 1234, -1, ID_COLUMN) )
This DDL creates an unique index indexing -1 if the value of DEFAULT_COLUMN is 1234 and ID_COLUMN in any other case. Then, if two columns have DEFAULT_COLUMN value, it raises an exception.
The question implies to me that you have a primary table that has some child records and one of those child records will be the default record. Using address and a separate default table here is an example of how to make that happen using third normal form. Of course I don't know if it's valuable to answer something that is so old but it struck my fancy.
--drop table dev.defaultAddress;
--drop table dev.addresses;
--drop table dev.people;
CREATE TABLE [dev].[people](
[Id] [int] identity primary key,
name char(20)
)
GO
CREATE TABLE [dev].[Addresses](
id int identity primary key,
peopleId int foreign key references dev.people(id),
address varchar(100)
) ON [PRIMARY]
GO
CREATE TABLE [dev].[defaultAddress](
id int identity primary key,
peopleId int foreign key references dev.people(id),
addressesId int foreign key references dev.addresses(id))
go
create unique index defaultAddress on dev.defaultAddress (peopleId)
go
create unique index idx_addr_id_person on dev.addresses(peopleid,id);
go
ALTER TABLE dev.defaultAddress
ADD CONSTRAINT FK_Def_People_Address
FOREIGN KEY(peopleID, addressesID)
REFERENCES dev.Addresses(peopleId, id)
go
insert into dev.people (name)
select 'Bill' union
select 'John' union
select 'Harry'
insert into dev.Addresses (peopleid, address)
select 1, '123 someplace' union
select 1,'work place' union
select 2,'home address' union
select 3,'some address'
insert into dev.defaultaddress (peopleId, addressesid)
select 1,1 union
select 2,3
-- so two home addresses are default now
-- try adding another default address to Bill and you get an error
select * from dev.people
join dev.addresses on people.id = addresses.peopleid
left join dev.defaultAddress on defaultAddress.peopleid = people.id and defaultaddress.addressesid = addresses.id
insert into dev.defaultaddress (peopleId, addressesId)
select 1,2
GO
You could do it through an instead of trigger, or if you want it as a constraint create a constraint that references a function that checks for a row that has the default set to 1
EDIT oops, needs to be <=
Create table mytable(id1 int, defaultX bit not null default(0))
go
create Function dbo.fx_DefaultExists()
returns int as
Begin
Declare #Ret int
Set #ret = 0
Select #ret = count(1) from mytable
Where defaultX = 1
Return #ret
End
GO
Alter table mytable add
CONSTRAINT [CHK_DEFAULT_SET] CHECK
(([dbo].fx_DefaultExists()<=(1)))
GO
Insert into mytable (id1, defaultX) values (1,1)
Insert into mytable (id1, defaultX) values (2,1)
This is a fairly complex process that cannot be handled through a simple constraint.
We do this through a trigger. However before you write the trigger you need to be able to answer several things:
do we want to fail the insert if a default exists, change it to 0 instead of 1 or change the existing default to 0 and leave this one as 1?
what do we want to do if the default record is deleted and other non default records are still there? Do we make one the default, if so how do we determine which one?
You will also need to be very, very careful to make the trigger handle multiple row processing. For instance a client might decide that all of the records of a particular type should be the default. You wouldn't change a million records one at a time, so this trigger needs to be able to handle that. It also needs to handle that without looping or the use of a cursor (you really don't want the type of transaction discussed above to take hours locking up the table the whole time).
You also need a very extensive tesing scenario for this trigger before it goes live. You need to test:
adding a record with no default and it is the first record for that customer
adding a record with a default and it is the first record for that customer
adding a record with no default and it is the not the first record for that customer
adding a record with a default and it is the not the first record for that customer
Updating a record to have the default when no other record has it (assuming you don't require one record to always be set as the deafault)
Updating a record to remove the default
Deleting the record with the deafult
Deleting a record without the default
Performing a mass insert with multiple situations in the data including two records which both have isdefault set to 1 and all of the situations tested when running individual record inserts
Performing a mass update with multiple situations in the data including two records which both have isdefault set to 1 and all of the situations tested when running individual record updates
Performing a mass delete with multiple situations in the data including two records which both have isdefault set to 1 and all of the situations tested when running individual record deletes
#Andy Jones gave an answer above closest to mine, but bearing in mind the Rule of Three, I placed the logic directly in the stored proc that updates this table. This was my simple solution. If I need to update the table from elsewhere, I will move the logic to a trigger. The one default rule applies to each set of records specified by a FormID and a ConfigID:
ALTER proc [dbo].[cpForm_UpdateLinkedReport]
#reportLinkId int,
#defaultYN bit,
#linkName nvarchar(150)
as
if #defaultYN = 1
begin
declare #formId int, #configId int
select #formId = FormID, #configId = ConfigID from csReportLink where ReportLinkID = #reportLinkId
update csReportLink set DefaultYN = 0 where isnull(ConfigID, #configId) = #configId and FormID = #formId
end
update
csReportLink
set
DefaultYN = #defaultYN,
LinkName = #linkName
where
ReportLinkID = #reportLinkId

How can I create a table in SQL only if a column in another table is true?

For example, assume I want to create the following table:
CREATE TABLE Add_Friends(
PRIMARY KEY(email_1, email_2),
email_1 VARCHAR(50) FOREIGN KEY REFERENCES Normal_Users,
email_2 VARCHAR(50) FOREIGN KEY REFERENCES Normal_Users,
accepted_or_not BIT
)
CREATE TABLE Messages1(
message_ID INT PRIMARY KEY IDENTITY,
date_sent date,
message_content VARCHAR(100),
sender VARCHAR(50) FOREIGN KEY REFERENCES Normal_Users,
reciever VARCHAR(50) FOREIGN KEY REFERENCES Normal_Users
)
Users should only be able to send messages to each other if they are friends (the last column in Add_Friends). How can I define this constraint in the Messages1 table?
If I understand correctly your question:
You don't need to create a table for each conversation, you can put all the messages together in the same table and id the conversation registering the primarykey from the add frinds.
I assume you have another table with all the data of your "persons" and then Add_Friends is the table where you add realtionsship between those persons, so to do the restriction you want you need to first check if the relationship between sender and reciever exist and then do an insert if the query returns a value.
SELECT ID_FRIEND
FROM ADD_FRIENDS
WHERE email_1 = "+ID_SENDER+" AND email_2 = "+ID_RECIEVER+"
If this query returns a value it means the realationship exist and you can do the insert.
Is better to do this in two steps so you can also restrict things like if the person is or not registed, online status or even be able to write a message in the first place.
And then to search for a chat you can do:
SELECT *
FROM messages AS m INNER JOIN add_friends as AF ON M.SENDER= AF.EMAIL1
WHERE email_2 = '" + EMAIL_CONVERSION_WITH + "'"
You can achieve this in any language.
You can create a user defined function passing it the sender & receiver - that function can check if a record of those emails exists in Add_Friends and if accepted_or_not = 1 and return BIT (0 / 1). Then you can use the function in a constraint:
ALTER TABLE [Messages1]
WITH CHECK ADD CONSTRAINT [Your_Constraint_Name]
CHECK ([dbo].[Your_Function_Name]([sender], [reciever]) = 1)
ALTER TABLE [Messages1] CHECK CONSTRAINT [Your_Constraint_Name]
Instead of having a column accepted_or_notin Add_Friends, you can define another table 'Friends_Accepted':
CREATE TABLE Friends_Accepted(
Id INT PRIMARY KEY IDENTITY,
email_canSend VARCHAR(50) FOREIGN KEY REFERENCES Normal_Users,
email_canReceive VARCHAR(50) FOREIGN KEY REFERENCES Normal_Users,
)
In this table, you would add TWO lines when a friend request is accepted -- one for each direction of the conversation: (A.Email,B.Email), (B.Email, A.Email)
Now you can have the table Messages1 reference Friends_Accepted:
CREATE TABLE Messages1(
message_ID INT PRIMARY KEY IDENTITY,
date_sent date,
message_content VARCHAR(100),
fromTo_Id FOREIGN KEY REFERENCES Friends_Accepted,
)
So a message from (A to B) and a message from (B to A) will reference two DIFFERENT lines in Friends_Accepted

Foreign Key to multiple tables

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.

Data Base Design for Images

I would like your advice regarding Data Base design.
I have 4 different data elements (tables A,B,C,D) example:
A - Contents
B - Categories
C - Authors
and
D - Images
Every record in tables A,B,C could have associated 1 or more different Images in Table D,
BUT for every image in D must be uniquely associated only a record in A,B,C.
This means that images cannot be shared (between others tables).
My idea was to create different Image tables for every data elements, using ONE to MANY association type.
Example:
Content --> Image-Contents
and
Categories --> Image-Categories
Questions?
My database design is a good one?
Since Tables "Image-Contents" and "Image-Categories", could have similar property like "File-Url" or "Image-Title", I was concerning if could be exist a most suitable database design solution.
Thanks for your time
I think you would want a table that maps each of ABC to an image. For example:
Content -> ContentImages -> Images
--------- ------------- ------
ContentId ImageId ImageId
ContentId
Categories -> CategoryImages -> Images
---------- ---------------- ------
CategoryId ImageId ImageId
CategoryId
Authors -> AuthorImages -> Images
---------- ---------------- ------
AuthorId ImageId ImageId
AuthorId
It may seem a little cumbersome but i think this is the normal form.
Perhaps the most common way to implement this design is with the "one table per owner type" scheme you mentioned (Tables for Images, "Owner A", "Owner A Images", and repeat for owners B, C, etc). Another common way to implement this is with one "central" table for Images, with the single owner's Id stored within that table. Your criteria are particularly limiting, in that an image may be associated with one and only one owner, but there are multiple types of owner. Implementing such constraints inside the database is tricky, but implementing them outside of the database is much more difficult and problematic for all the usual reasons (application doing the databases work, and what happens when someone modifies the database outside of the dedicated application?)
The following is an example of how these structures and constraints might be implemented within the database. It may appear fussy, detailed, and overly-complex, but it will do the job, and once properly implemented you would never have to worry whether or not your data was consistant and valid.
First off, all images are stored in the following table. It must be known what "type" of owner an image may be assigned to; set that in ImageType, and (as per the constraints in the later tables) the image can not be assigned to any other kind of owner. Ever. (You could also put a CHECK constraint on ImageType to ensure that only valid image types could be loaded in the table.)
CREATE TABLE Image
(
ImageId int not null
,ImageType char(1) not null
,constraint PK_Image
primary key clustered (ImageId, ImageType)
)
Next, build some owner tables. You could have any number of these, I'm just making two for sake of the example.
CREATE TABLE A
(
AId int not null
constraint PK_A
primary key clustered
)
CREATE TABLE B
(
BId int not null
constraint PK_B
primary key clustered
)
Build the association tables, noting the comments next to the constraint definitions. (This is the overly-fussy part...)
CREATE TABLE Image_A
(
ImageId int not null
constraint PK_Image_A
primary key clustered -- An image can only be assigned to one owner
,AId int not null
,ImageType char(1) not null
constraint DF_Image_A
default 'A'
constraint CK_Image_A__ImageType
check (ImageType in ('A')) -- Always have this set to the type of the owner for this table
,constraint FK_Image_A__A
foreign key (AId) references A (AId) -- Owner must exist
,constraint FK_Image_A__Image
foreign key (ImageId, ImageType) references Image (ImageId, ImageType) -- Image must exist *for this type of owner*
)
-- Same comments for this table
CREATE TABLE Image_B
(
ImageId int not null
constraint PK_Image_B
primary key clustered
,BId int not null
,ImageType char(1) not null
constraint DF_Image_B
default 'B'
constraint CK_Image_B__ImageType
check (ImageType in ('B'))
,constraint FK_Image_B__B
foreign key (BId) references B (BId)
,constraint FK_Image_B__Image
foreign key (ImageId, ImageType) references Image (ImageId, ImageType)
)
Load some sample data
INSERT Image values (1, 'A')
INSERT Image values (2, 'A')
INSERT Image values (3, 'B')
INSERT Image values (4, 'B')
INSERT A values (101)
INSERT A values (102)
INSERT B values (201)
INSERT B values (102)
View the current contents of the tables:
SELECT * from A
SELECT * from B
SELECT * from Image
SELECT * from Image_A
SELECT * from Image_B
And do some tests:
-- Proper fit
INSERT Image_A (ImageId, AId) values (1, 101)
-- Run it again, can only assign once
-- Cannot assign the same image to a second owner of the proper type
INSERT Image_A (ImageId, AId) values (1, 102)
-- Can't assign image to an invalid owner type
INSERT Image_B (ImageId, BId) values (1, 201)
-- Owner can be assigned multiple images
INSERT Image_A (ImageId, AId) values (2, 101)
(This drops the testing tables)
drop table Image
drop table A
drop table B
drop table Image_A
drop table Image_B
(Techincally, this is a good example of a variant on the exclusive type/subtype data modelling "problem".)
create table A (IDA int not null, primary key(IDA));
create table B (IDB int not null, primary key(IDB));
create table C (IDC int not null, primary key(IDC));
create table Image(IDI int, A int null, B int null, C int null, Contents image,
foreign key (A) references A(IDA),
foreign key (B) references B(IDB),
foreign key (C) references C(IDC),
check (
(A is not null and B is null and C is null) or
(A is null and B is not null and C is null) or
(A is null and B is null and C is not null)
));
Yes, you're looking in the right direction.
Keep your current setup of the four tables and then create 3 more that hold only metadata that tells you the linking between, for example, the content table and the image tables.
For example, the images-content table will have columns: id, content-id, image-id
And so on.

Resources