2

Below is your typical many-to-many relationship. Pretend there are 2 tasks that have the exact same Tags. because of foreign key/primary key relationship constraints, there is no way for the rows in the table TasksTag to be reused (both Tasks would have the same PK; impossible)

In my scenario, theoretically, a task will usually contain most or all tags, that means every Task will have roughly 300+ rows in the TasksTag table. This scales horribly because there will be a few hundred Tasks per month and a lot of which will have the same Tag list.

Can someone explain another way of doing this without removing the constraints? Am I worrying about space too much?

Thanks!

enter image description here

5
  • 1) How would two tasks with the same tag violate the foreign key constraints? 2) if you assume 1,000 tasks per month with 1,000 tags per task, you get a 1,000,000 rows per month. Assuming the keys are INT then you get around 8MB of data per month for the TasksTag table. Not too bad.
    – Hannah Vernon
    Jul 3, 2014 at 18:28
  • I thought foreign keys can only link to unique indexes? Therefore if 2 tasks have the same tag then there will be 2 entries in the TasksTag Table instead of just one where the Tasks could link to the same TasksTag entry. Thanks for the reassurance on the space.
    – ryand
    Jul 3, 2014 at 18:52
  • See my answer below for a more concrete example of how you might want to implement your structure.
    – Hannah Vernon
    Jul 3, 2014 at 19:04
  • I assume that you have the TagID and TaskID columns indexed. If not then index the join columns to help satisfy your scaling comment.
    – RLF
    Jul 3, 2014 at 19:14
  • @RLF Could you elaborate on that? I do have the TagId and TaskId columns indexed, SQL server indexes all primary keys. The Join Table has a composite primary key which is also indexed.
    – ryand
    Jul 3, 2014 at 19:19

1 Answer 1

3

The below sample structure illustrates how you can do the TasksTags table most efficiently. The Tasks table enforces unique task names. The Tags table enforces unique tag names. The TasksTags table joins these together allowing any combination of Tasks and Tags.

USE tempdb;
CREATE TABLE dbo.Tasks
(
    TaskID INT NOT NULL CONSTRAINT PK_Tasks PRIMARY KEY CLUSTERED IDENTITY(1,1)
    , TaskName VARCHAR(255) NOT NULL CONSTRAINT UQ_Tasks_TaskName UNIQUE
);
CREATE TABLE dbo.Tags
(
    TagID INT NOT NULL CONSTRAINT PK_Tags PRIMARY KEY CLUSTERED IDENTITY(1,1)
    , TagName VARCHAR(255) NOT NULL CONSTRAINT UQ_Tags_TagName UNIQUE
);
CREATE TABLE dbo.TasksTags
(
    TaskID INT NOT NULL
    , TagID INT NOT NULL
    , CONSTRAINT PK_TasksTags PRIMARY KEY CLUSTERED (TaskID, TagID)
);
INSERT INTO dbo.Tasks (TaskName) VALUES ('Task1');
INSERT INTO dbo.Tasks (TaskName) VALUES ('Task2');
INSERT INTO dbo.Tasks (TaskName) VALUES ('Task3');

INSERT INTO dbo.Tags (TagName) VALUES ('MyTag1');
INSERT INTO dbo.Tags (TagName) VALUES ('MyTag2');
INSERT INTO dbo.Tags (TagName) VALUES ('MyTag3');

INSERT INTO dbo.TasksTags (TaskID, TagID) VALUES (1,2);
INSERT INTO dbo.TasksTags (TaskID, TagID) VALUES (1,3);
INSERT INTO dbo.TasksTags (TaskID, TagID) VALUES (1,1);
INSERT INTO dbo.TasksTags (TaskID, TagID) VALUES (2,2);
INSERT INTO dbo.TasksTags (TaskID, TagID) VALUES (2,3);
INSERT INTO dbo.TasksTags (TaskID, TagID) VALUES (3,2);
INSERT INTO dbo.TasksTags (TaskID, TagID) VALUES (3,1);


SELECT TK.TaskName, TG.TagName
FROM dbo.TasksTags TT
    INNER JOIN dbo.Tasks TK ON TT.TaskID = TK.TaskID
    INNER JOIN dbo.Tags TG ON TG.TagID = TT.TagID;

enter image description here

EDIT

The following allows a list of tags to be associated with more than one task. You could blend this with the prior structure to create a totally customizable list of tags for each task.

USE tempdb;
CREATE TABLE dbo.Tasks
(
    TaskID INT NOT NULL CONSTRAINT PK_Tasks PRIMARY KEY CLUSTERED IDENTITY(1,1)
    , TaskName VARCHAR(255) NOT NULL CONSTRAINT UQ_Tasks_TaskName UNIQUE
);
CREATE TABLE dbo.Tags
(
    TagID INT NOT NULL CONSTRAINT PK_Tags PRIMARY KEY CLUSTERED IDENTITY(1,1)
    , TagName VARCHAR(255) NOT NULL CONSTRAINT UQ_Tags_TagName UNIQUE
);
CREATE TABLE dbo.TagList
(
    TagListID INT NOT NULL CONSTRAINT PK_TagList PRIMARY KEY CLUSTERED IDENTITY(1,1)
    , TagListName VARCHAR(255) NOT NULL CONSTRAINT UQ_TagList_Name UNIQUE
);
CREATE TABLE dbo.TagListTags
(
    TagListID INT NOT NULL CONSTRAINT FK_TagListTags_TagListID FOREIGN KEY REFERENCES dbo.TagList(TagListID)
    , TagID INT NOT NULL CONSTRAINT FK_TagListTags_TagID FOREIGN KEY REFERENCES dbo.Tags(TagID)
    , CONSTRAINT PK_TasksTags PRIMARY KEY CLUSTERED (TagListID, TagID)
);
CREATE TABLE dbo.TasksTagList
(
    TaskID INT NOT NULL CONSTRAINT FK_TasksTagList_TaskID FOREIGN KEY REFERENCES dbo.Tasks(TaskID)
    , TagListID INT NOT NULL CONSTRAINT FK_TasksTagList_TagListID FOREIGN KEY REFERENCES dbo.TagList(TagListID)
    , CONSTRAINT PK_TasksTagList PRIMARY KEY CLUSTERED (TaskID, TagListID)
);


INSERT INTO dbo.Tasks (TaskName) VALUES ('Task1');
INSERT INTO dbo.Tasks (TaskName) VALUES ('Task2');
INSERT INTO dbo.Tasks (TaskName) VALUES ('Task3');

INSERT INTO dbo.Tags (TagName) VALUES ('MyTag1');
INSERT INTO dbo.Tags (TagName) VALUES ('MyTag2');
INSERT INTO dbo.Tags (TagName) VALUES ('MyTag3');

INSERT INTO dbo.TagList (TagListName) VALUES ('Tag List 1');
INSERT INTO dbo.TagList (TagListName) VALUES ('Tag List 2');

INSERT INTO dbo.TagListTags (TagListID, TagID) VALUES (1, 1);
INSERT INTO dbo.TagListTags (TagListID, TagID) VALUES (1, 2);
INSERT INTO dbo.TagListTags (TagListID, TagID) VALUES (1, 3);
INSERT INTO dbo.TagListTags (TagListID, TagID) VALUES (2, 1);
INSERT INTO dbo.TagListTags (TagListID, TagID) VALUES (2, 3);

INSERT INTO dbo.TasksTagList (TaskID, TagListID) VALUES (1, 1);
INSERT INTO dbo.TasksTagList (TaskID, TagListID) VALUES (2, 1);
INSERT INTO dbo.TasksTagList (TaskID, TagListID) VALUES (2, 2);


SELECT Tasks.TaskName, Tags.TagName
FROM dbo.TasksTagList TTL
    INNER JOIN dbo.TagListTags TLT ON TTL.TagListID = TLT.TagListID
    INNER JOIN dbo.Tags ON TLT.TagID = Tags.TagID
    INNER JOIN dbo.Tasks ON TTL.TaskID = Tasks.TaskID;
4
  • That's exactly what I have now, What I want are 2 Tasks sharing the exact same Tag list using the exact same rows in TaksTags while keeping the constraints... I'm 99% sure its not possible but it seems like it should be...
    – ryand
    Jul 3, 2014 at 19:12
  • I've edited my answer to show how you could accomplish that.
    – Hannah Vernon
    Jul 3, 2014 at 19:38
  • 2
    I'm going to mark this as the answer for your effort but the real answer is to use the top example and realize that even though the junction table is going to get huge, it only has 2 int columns so the memory footprint is relatively small.
    – ryand
    Jul 3, 2014 at 19:41
  • Depending on your requirements, you could go either way. Depends how complicated you want, and how many rows you are expecting. Several million is no big deal™, however several billion might become an issue to manage.
    – Hannah Vernon
    Jul 3, 2014 at 19:42

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.