To prevent the use of duplicate Tags in a database - sql

I would like to know how you can prevent to use of two same tags in a database table.
One said me that use two private keys in a table. However, W3Schools -website says that it is impossible.
My relational table
alt text http://files.getdropbox.com/u/175564/db/db7.png
My logical table
alt text http://files.getdropbox.com/u/175564/db/db77.png
The context of tables
alt text http://files.getdropbox.com/u/175564/db/db777.png
How can you prevent the use of duplicate tags in a question?

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.

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.

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

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.

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.

Related

PostgreSQL | How to store user answers in survey?

I have task to create survey which aviable only by invitation to certain people. One person can take the survey once. The survey is only available for a specific period of time. I am tring to create database structure for this future application. As database I use PostgreSQL.
As I understand one survey can have many questions. In the same time one question can be reused in many surveys. It's many to many relationship.
Thats how I created tables which solve this first task. Please correct me if I'm somewhere missed or did wrong.
QUESTIONS TABLE:
CREATE TABLE QUESTIONS(
ID SERIAL PRIMARY KEY,
TEXT TEXT NOT NULL
);
SURVEYS TABLE:
CREATE TABLE SURVEYS(
ID UUID PRIMARY KEY NOT NULL DEFAULT uuid_generate_v4(),
NAME VARCHAR NOT NULL,
DESCRIPTION TEXT,
START_PERIOD TIMESTAMP,
END_PERIOD TIMESTAMP
);
SURVEYS_QUESTIONS TABLE:
CREATE TABLE SURVEYS_QUESTIONS(
ID SERIAL,
SURVEY_ID UUID NOT NULL,
QUESTION_ID INT NOT NULL,
PRIMARY KEY (ID),
FOREIGN KEY (SURVEY_ID) REFERENCES SURVEYS (ID) ON DELETE CASCADE,
FOREIGN KEY (QUESTION_ID) REFERENCES QUESTIONS (ID) ON DELETE CASCADE
);
Right now I don't understand how correctly connect users with surveys and how correctly store user answers.
Create a TAKEN_SURVEYS table with a foreign key to the SURVEYS table and a foreign key to the USERS table. If you want to ensure there can be only one taken survey record per user, create a unique index on the TAKEN_SURVEYS table.
Consider replacing your last table with two new tables. This would align with classic 101 example of Customers-Orders-Products (here being Users-Surveys-Q&A).
CREATE TABLE USER_SURVEYS (
ID SERIAL,
USER_ID UUID NOT NULL,
SURVEY_ID UUID NOT NULL,
PRIMARY KEY (ID),
FOREIGN KEY (USER_ID) REFERENCES USERS (ID) ON DELETE CASCADE,
FOREIGN KEY (SURVEY_ID) REFERENCES SURVEYS (ID) ON DELETE CASCADE
);
CREATE TABLE USER_SURVEYS_QA (
ID SERIAL,
USER_SURVEY_ID INT NOT NULL,
QUESTION_ID INT NOT NULL,
ANSWER VARCHAR(255),
OTHER_SPECIFY VARCHAR(255),
PRIMARY KEY (ID),
FOREIGN KEY (USER_SURVEY_ID) REFERENCES USER_SURVEYS (ID) ON DELETE CASCADE,
FOREIGN KEY (QUESTION_ID) REFERENCES QUESTIONS (ID) ON DELETE CASCADE
);

SQL insert while checking that one of keys is equal to a field in another table

I'm using sequelize and postgresql but I think this is a more generic SQL/table question.
I have a setup similar to:
CREATE TABLE "Mixtime" (
id bigint NOT NULL,
duration character varying(255) NOT NULL,
created_at timestamp with time zone NOT NULL,
updated_at timestamp with time zone NOT NULL,
spell_id bigint NOT NULL,
ingredient_id bigint NOT NULL,
user_id bigint NOT NULL
);
CREATE TABLE "Spell" (
id bigint NOT NULL,
instructions character varying(5000) NOT NULL,
created_at timestamp with time zone NOT NULL,
user_id bigint NOT NULL
);
CREATE TABLE "Ingredient" (
id bigint NOT NULL,
ing_name character varying(255) NOT NULL,
created_at timestamp with time zone NOT NULL,
updated_at timestamp with time zone NOT NULL,
user_id bigint NOT NULL
);
CREATE TABLE "Users" (
id bigint NOT NULL,
user_name character varying(255) NOT NULL,
created_at timestamp with time zone NOT NULL,
updated_at timestamp with time zone NOT NULL,
);
ALTER TABLE ONLY "Mixtime"
ADD CONSTRAINT "Mixtime_pkey" PRIMARY KEY (id);
ALTER TABLE ONLY "Spell"
ADD CONSTRAINT "Spell_pkey" PRIMARY KEY (id);
ALTER TABLE ONLY "Ingredient"
ADD CONSTRAINT "Ingredient_pkey" PRIMARY KEY (id);
ALTER TABLE ONLY "Users"
ADD CONSTRAINT "Users_pkey" PRIMARY KEY (id);
ALTER TABLE ONLY "Mixtime"
ADD CONSTRAINT "Mixtime_spell_id_fkey" FOREIGN KEY (spell_id) REFERENCES "Spell"(id) ON UPDATE CASCADE ON DELETE CASCADE;
ALTER TABLE ONLY "Mixtime"
ADD CONSTRAINT "Mixtime_ingredient_id_fkey" FOREIGN KEY (ingredient_id) REFERENCES "Ingredient"(id) ON UPDATE CASCADE ON DELETE CASCADE;
ALTER TABLE ONLY "Mixtime"
ADD CONSTRAINT "Mixtime_user_id_fkey" FOREIGN KEY (user_id) REFERENCES "Users"(id) ON UPDATE CASCADE ON DELETE CASCADE;
ALTER TABLE ONLY "Spell"
ADD CONSTRAINT "Spell_user_id_fkey" FOREIGN KEY (user_id) REFERENCES "Users"(id) ON UPDATE CASCADE ON DELETE CASCADE;
ALTER TABLE ONLY "Ingredient"
ADD CONSTRAINT "Ingredient_user_id_fkey" FOREIGN KEY (user_id) REFERENCES "Users"(id) ON UPDATE CASCADE ON DELETE CASCADE;
What I'd like to do is make sure when I insert into Mixtime that user_id matches Spell & Ingredients' user_id fields
Pseudo code:
If (newMixtime.userId != Spell.user_id || newMixtime.userId != Ingredient.user_id) {
failHere
} else {
insert newMixtime into M
}
Note that this is not a join table. All three tables need to be query-able by the user_id field and table Mixtime has specific extra fields, it's just referencing Spell & Ingredient.
I could (and am currently) validating at the ORM layer by querying the db first, but this seems like something that should be possible in the DB layer and would save me trips.
If you know how to map this into Sequlize's Model syntax, that'd be grand, but I can probably figure that out if I have a pure postgres/SQL solution.
A fully normalized set of tables wouldn't have this constraint. The simplest way to represent this information is for every row in every table to be "owned" by exactly one row of exactly one parent table, as represented by a single foreign key. Unless you have a pressing need to share ingredients among different steps/spells of one user but NOT share them among different users, you don't need a user_id field on the ingredient.
Having true multi-table constraints (other than foreign keys) is extremely hard to do accurately and reliably. You can use triggers, but they tend to be very engine-specific, and I don't recommend putting your logic into triggers unless there's really no alternative.

How to replace compound primary key in table?

I have following table:
CREATE TABLE "PostViews"
(
"createdAt" timestamp with time zone NOT NULL,
"updatedAt" timestamp with time zone NOT NULL,
"PostId" integer NOT NULL,
"UserId" integer NOT NULL,
CONSTRAINT "PostViews_pkey" PRIMARY KEY ("PostId", "UserId"),
CONSTRAINT "PostViews_PostId_fkey" FOREIGN KEY ("PostId")
REFERENCES "Posts" (id) MATCH SIMPLE
ON UPDATE CASCADE ON DELETE CASCADE,
CONSTRAINT "PostViews_UserId_fkey" FOREIGN KEY ("UserId")
REFERENCES "Users" (id) MATCH SIMPLE
ON UPDATE CASCADE ON DELETE CASCADE
)
It is join-through table which links many Users to many Posts with compound pk PostId,UserId. What I have to do is to drop uniqueness from PostId,UserId in order to allow to store more than one PostView per post per user.
Should I remove pk and add index for PostId,UserId (or add index first and then drop pk)?
Or Should I add id serial column, make it pk and then drop compound pk?
You do not explain the updatedAt column purpose so I would drop it, rename the createdAt as viewedAt and make it part of the PK:
constraint PostViews_pkey PRIMARY KEY (PostId, UserId, viewedAt)
The chance of the user viewing the post twice at the exact same time is insignificant. If you want to provide for that case then wrap the client insertion code in a try/catch and retry in case of a PK exception.
Do not use double quotes for identifiers. It will be a real pain forever.

create foreign key in oracle

is there anyone who can help me to create a foreign key for my Status table. I need to PLACE a foreign key constraint on the code in the status table, referring to the id in the Building table.
TABLE building
(
build_name VARCHAR2(50,0) NOT NULL,
id NUMBER (38,0) NOT NULL,
mapid NUMBER (10,0) NOT NULL
);
TABLE STATUS
(
code VARCHAR(2 BYTE) NOT NULL,
status_name VARCHAR2(40 BYTE) NOT NULL,
);
Bulding table has constraint building_gmidx with id as primary key.
Here is a quick syntax for your current requirement, however I recommend you to go through the oracle documentation for a proper understanding of what this means.
ALTER TABLE STATUS ADD (
CONSTRAINT status_fk_building FOREIGN KEY (code)
REFERENCES building (id)
ENABLE VALIDATE);
Did you leave out CREATE TABLE *name* to save time when writing the question? The first thing I see is that your Foreign key has to have the same data type and size as your Primary key. If the Tables already exist the code would be:
ALTER TABLE status MODIFY (code NUMBER(38));
From there you would need to
ALTER TABLE status ADD FOREIGN KEY (code) REFERENCES (building_gmidx)
Why is the constraint for building Primary key named building_gmidx and the column plain id?? All Normalized table attributes should be Unique. Wouldn't it be better if you just named the column "id" building_gmidx instead. Just in case there are other types of id's added later you do not have to think about which table "id" pertains to. Let me know if this works.

Foreign keys in MySQL?

I have been slowly learning SQL the last few weeks. I've picked up all of the relational algebra and the basics of how relational databases work. What I'm trying to do now is learn how it's implemented.
A stumbling block I've come across in this, is foreign keys in MySQL. I can't seem to find much about the other than that they exist in the InnoDB storage schema that MySQL has.
What is a simple example of foreign keys implemented in MySQL?
Here's part of a schema I wrote that doesn't seem to be working if you would rather point out my flaw than show me a working example.
CREATE TABLE `posts` (
`pID` bigint(20) NOT NULL auto_increment,
`content` text NOT NULL,
`time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`uID` bigint(20) NOT NULL,
`wikiptr` bigint(20) default NULL,
`cID` bigint(20) NOT NULL,
PRIMARY KEY (`pID`),
Foreign Key(`cID`) references categories,
Foreign Key(`uID`) references users
) ENGINE=InnoDB;
Assuming your categories and users table already exist and contain cID and uID respectively as primary keys, this should work:
CREATE TABLE `posts` (
`pID` bigint(20) NOT NULL auto_increment,
`content` text NOT NULL,
`time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`uID` bigint(20) NOT NULL,
`wikiptr` bigint(20) default NULL,
`cID` bigint(20) NOT NULL,
PRIMARY KEY (`pID`),
Foreign Key(`cID`) references categories(`cID`),
Foreign Key(`uID`) references users(`uID`)
) ENGINE=InnoDB;
The column name is required in the references clause.
Edited: Robert and Vinko state that you need to declare the name of the referenced column in the foreign key constraint. This is necessary in InnoDB, although in standard SQL you're permitted to omit the referenced column name if it's the same name in the parent table.
One idiosyncrasy I've encountered in MySQL is that foreign key declaration will fail silently in several circumstances:
Your MySQL installation doesn't include the innodb engine
Your MySQL config file doesn't enable the innodb engine
You don't declare your table with the ENGINE=InnoDB table modifier
The foreign key column isn't exactly the same data type as the primary key column in the referenced table
Unfortunately, MySQL gives no message that it has failed to create the foreign key constraint. It simply ignores the request, and creates the table without the foreign key (if you SHOW CREATE TABLE posts, you may see no foreign key declaration). I've always thought this is a bad feature of MySQL!
Tip: the integer argument for integer data types (e.g. BIGINT(20)) is not necessary. It has nothing to do with the storage size or range of the column. BIGINT is always the same size regardless of the argument you give it. The number refers to how many digits MySQL will pad the column if you use the ZEROFILL column modifier.
This has some code showing how to create foreign keys by themselves, and in CREATE TABLE.
Here's one of the simpler examples from that:
CREATE TABLE parent (
id INT NOT NULL,
PRIMARY KEY (id)
) ENGINE=INNODB;
CREATE TABLE child (
id INT,
parent_id INT,
INDEX par_ind (parent_id),
FOREIGN KEY (parent_id) REFERENCES parent(id)
ON DELETE CASCADE
) ENGINE=INNODB;
I agree with Robert. You are missing the name of the column in the references clause (and you should be getting the error 150). I'll add that you can check how the tables got created in reality with:
SHOW CREATE TABLE posts;
The previous answers deal with the foreign key constraint. While the foreign key constraint is definitely useful to maintain referential integrity, the concept of "foreign key" itself is fundamental to the relational model of data, regardless of whether you use the constraint or not.
Whenever you do an equijoin, you are equating a foreign key to something, usually the key it references. Example:
select *
from
Students
inner join
StudentCourses
on Students.StudentId = StudentCourses.StudentId
StudentCourses.StudentId is a foreign key referencing Students.StudentId.