+2  A: 

You can create a unique constraint on (question_id, tag_name) in the tags table, which will ensure that the pair is unique. That would mean that the same question may not have the same tag attached more than once. However, the same tag could still apply to different questions.

Greg Hewgill
Please, see my attempt to create such a constraint above.
Masi
Do you mean this http://stackoverflow.com/questions/1202879/to-show-unique-constraint-for-two-columns-in-physical-erd
Masi
I answered to your question at the thread.
Masi
+1  A: 

You cannot create two primary keys, but you can place a uniqueness constraint on an index.

James Conigliaro
+1  A: 

You can only have one primary key (I assume that's what you mean by "private" key), but that key can be a composite key consisting of the question-id and tag-name. In SQL, it would look like (depending on your SQL dialect):

CREATE TABLE Tags
(
  question_id int,
  tag_name varchar(xxx),
  PRIMARY KEY (question_id, tag_name)
);

This will ensure you cannot have the same tag against the same question.

Ken Keenan
Thank you for your answer! --- Please, see my attempt to convert your code to PostgreSQL or Oracle.
Masi
A: 

I will use PostgreSQL or Oracle.

I feel that the following is correspondent to Ken's code which is for MySQL.

CREATE TABLE Tags 
     (
         QUESTION_ID integer FOREIGN KEY REFERENCES Questions(QUESTION_ID) 
                             CHECK (QUESTION_ID>0), 
         TAG_NAME nvarchar(20) NOT NULL,
         CONSTRAINT no_duplicate_tag UNIQUE (QUESTION_ID,TAG_NAME)
     )

I added some extra measures to the query. For instance, CHECK (USER_ID>0) is to ensure that there is no corrupted data in the database.

I dropped out the AUTO_INCREMENT from this QUESTION_ID because I see that it would break our system, since one question cannot then have two purposely-selected tags. In other, tags would go mixed up.

I see that we need to give a name for the constraint. Its name is no_duplicate_tag in the command.

Masi
I'm not sure that's correct: if you make the question_id the primary key alone, it will mean that there can be only one tag per question!
Ken Keenan
@Ken: Thank you for your comment! - Magnus says too that it causes a circular reference at the thread http://stackoverflow.com/questions/1195816/to-improve-sql-queries-in-ddl/1198220#1198220
Masi
+1  A: 

I have updated my NORMA model to more closely match your diagram. I can see where you've made a few mistakes, but some of them may have been due to my earlier model.

I have updated this model to prevent duplicate tags. It didn't really matter before. But since you want it, here it is (for Postgres):

START TRANSACTION ISOLATION LEVEL SERIALIZABLE, READ WRITE;

CREATE SCHEMA so;

SET search_path TO SO,"$user",public;

CREATE DOMAIN so.HashedPassword AS 
    BIGINT CONSTRAINT HashedPassword_Unsigned_Chk CHECK (VALUE >= 0);

CREATE TABLE so."User"
(
    USER_ID SERIAL NOT NULL,
    USER_NAME CHARACTER VARYING(50) NOT NULL,
    EMAIL_ADDRESS CHARACTER VARYING(256) NOT NULL,
    HASHED_PASSWORD so.HashedPassword NOT NULL,
    OPEN_ID CHARACTER VARYING(512),
    A_MODERATOR BOOLEAN,
    LOGGED_IN BOOLEAN,
    HAS_BEEN_SENT_A_MODERATOR_MESSAGE BOOLEAN,
    CONSTRAINT User_PK PRIMARY KEY(USER_ID)
);

CREATE TABLE so.Question
(
    QUESTION_ID SERIAL NOT NULL,
    TITLE CHARACTER VARYING(256) NOT NULL,
    WAS_SENT_AT_TIME TIMESTAMP NOT NULL,
    BODY CHARACTER VARYING NOT NULL,
    USER_ID INTEGER NOT NULL,
    FLAGGED_FOR_MODERATOR_REMOVAL BOOLEAN,
    WAS_LAST_CHECKED_BY_MODERATOR_AT_TIME TIMESTAMP,
    CONSTRAINT Question_PK PRIMARY KEY(QUESTION_ID)
);

CREATE TABLE so.Tag
(
    TAG_ID SERIAL NOT NULL,
    TAG_NAME CHARACTER VARYING(20) NOT NULL,
    CONSTRAINT Tag_PK PRIMARY KEY(TAG_ID),
    CONSTRAINT Tag_UC UNIQUE(TAG_NAME)
);

CREATE TABLE so.QuestionTaggedTag
(
    QUESTION_ID INTEGER NOT NULL,
    TAG_ID INTEGER NOT NULL,
    CONSTRAINT QuestionTaggedTag_PK PRIMARY KEY(QUESTION_ID, TAG_ID)
);

CREATE TABLE so.Answer
(
    ANSWER_ID SERIAL NOT NULL,
    BODY CHARACTER VARYING NOT NULL,
    USER_ID INTEGER NOT NULL,
    QUESTION_ID INTEGER NOT NULL,
    CONSTRAINT Answer_PK PRIMARY KEY(ANSWER_ID)
);

ALTER TABLE so.Question 
    ADD CONSTRAINT Question_FK FOREIGN KEY (USER_ID) 
    REFERENCES so."User" (USER_ID) ON DELETE RESTRICT ON UPDATE RESTRICT;

ALTER TABLE so.QuestionTaggedTag 
    ADD CONSTRAINT QuestionTaggedTag_FK1 FOREIGN KEY (QUESTION_ID) 
    REFERENCES so.Question (QUESTION_ID) ON DELETE RESTRICT ON UPDATE RESTRICT;

ALTER TABLE so.QuestionTaggedTag 
    ADD CONSTRAINT QuestionTaggedTag_FK2 FOREIGN KEY (TAG_ID) 
    REFERENCES so.Tag (TAG_ID) ON DELETE RESTRICT ON UPDATE RESTRICT;

ALTER TABLE so.Answer 
    ADD CONSTRAINT Answer_FK1 FOREIGN KEY (USER_ID) 
    REFERENCES so."User" (USER_ID) ON DELETE RESTRICT ON UPDATE RESTRICT;

ALTER TABLE so.Answer 
    ADD CONSTRAINT Answer_FK2 FOREIGN KEY (QUESTION_ID) 
    REFERENCES so.Question (QUESTION_ID) ON DELETE RESTRICT ON UPDATE RESTRICT;

COMMIT WORK;

Note that there is now a separate Tag table with TAG_ID as the primary key. TAG_NAME is a separate column with a uniqueness constraint over it, preventing duplicate tags. The QuestionTaggedTag table now has (QUESTION_ID, TAG_ID), which is also its primary key.

I hope I didn't go too far in answering this, but when I tried to write smaller answers, I kept having to untangle my earlier answers, and it seemed simpler just to post this.

John Saunders
Thank you for your answer! - I will play a while with this :)
Masi
BTW, all the parts of NORMA that I used for this are the easiest parts. If you want to learn it, the tutorials are on their site: http://www.ormfoundation.org/files/folders/normalabs/default.aspx
John Saunders
You seem to mean that the database still needs UPDATE and DELETE queries. -- Thank you for the link!
Masi
Did you respond to the wrong comment? I haven't said anything about queries.
John Saunders
@John: I did not respond to the wrong answer. -- You say that *all the parts of NORMA that I used for this are the easiest parts*. This suggests me that there are also other parts of NORMA that is DDL/SQL queries which are harder.
Masi
@Masi: not the queries. Remember the diagrams you liked in the other question? That was a simple Object-Role Modeling diagram. The one I used for this answer was also simple. I'm saying you could learn that part of NORMA fairly quickly. Since it's really NORMA that has been providing me with the answers I've posted, if you learned it, you'd have your own answers.
John Saunders
@John: I would love to use NORMA if I could. I use OS X Leopard at the moment.
Masi
To make reading for others easier, these are the tables about which we are discussing: http://stackoverflow.com/questions/1185626/why-does-many-to-many-data-structure-require-two-additional-tables/1185631#1185631
Masi
Oh, too bad. :-)
John Saunders