PostgreSQL | How to store user answers in survey? - sql

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
);

Related

PostgreSQL Composite Foreign Key 'columns list must not contain duplicates'

This is my example schema:
CREATE TABLE users (
userid BIGSERIAL PRIMARY KEY,
name varchar(25) NOT NULL,
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE follows (
userid1 int NOT NULL,
userid2 int NOT NULL,
PRIMARY KEY (userid1, userid2),
FOREIGN KEY (userid1, userid2) REFERENCES users (userid) ON DELETE CASCADE
);
If I run this command, I get:
ERROR: number of referencing and referenced columns for foreign key disagree
And If I add ... REFERENCES users (userid, userid) ON DELETE CASCADE
I get:
ERROR: foreign key referenced-columns list must not contain duplicate
I know it works if I type in each line their respective references, but it would be better to not repeat myself.
How can I achieve that with composite foreign keys using the same dependency?
You need two separate foreign key constraints:
CREATE TABLE follows (
userid1 int NOT NULL,
userid2 int NOT NULL,
PRIMARY KEY (userid1, userid2),
FOREIGN KEY (userid1) REFERENCES users (userid) ON DELETE CASCADE,
FOREIGN KEY (userid2) REFERENCES users (userid) ON DELETE CASCADE
);
Though I prefer the syntax shown by Gordon, if you want a one liner you can compress it as:
CREATE TABLE follows (
userid1 int NOT NULL REFERENCES users ON DELETE CASCADE,
userid2 int NOT NULL REFERENCES users ON DELETE CASCADE,
PRIMARY KEY (userid1, userid2)
);
The down side of this short hand notation is that the FKs don't have names. It could be tricky to manage them in the future should you need to remove them or modify them. Experience has shown me that it's better to name them, and for that you need to use the full syntax, as in:
CREATE TABLE follows (
userid1 int NOT NULL,
userid2 int NOT NULL,
PRIMARY KEY (userid1, userid2),
CONSTRAINT fk1 FOREIGN KEY (userid1)
REFERENCES users (userid) ON DELETE CASCADE,
CONSTRAINT fk2 FOREIGN KEY (userid2)
REFERENCES users (userid) ON DELETE CASCADE
);
...just to get the fk1 and fk2 identifiers.

Postgresql, references to unique constraint

I'm running PostgreSQL 9.4 and have the following table:
CREATE TABLE user_cars (
user_id SERIAL REFERENCES users (id) ON DELETE CASCADE,
car CHARACTER VARYING(255) NOT NULL,
CONSTRAINT test UNIQUE (user_id, car)
);
The table allows a user to have multiple cars, but only use the car name once. But other users may have the same car name.
I would like to have another table with references to the unique constraint test, and have tried stuff like:
CREATE TABLE mappings (
other_id CHARACTER(9) REFERENCES other (id) ON DELETE CASCADE,
user_cars REFERENCES user_cards (test) ON DELETE CASCADE
);
But that fails "obviously". I would like to make sure that other_id only have a single references to a user_car entry.
So to explain, how can I in table mappings have a references to test from table user_cars.
This is the thing that fails currently:
user_cars REFERENCES user_cards (test) ON DELETE CASCADE
Don't use composite foreign key references, if you can avoid it. Just add a unique id to the table:
CREATE TABLE user_cars (
user_car_id serial primary key,
user_id int REFERENCES users (id) ON DELETE CASCADE,
car CHARACTER VARYING(255) NOT NULL,
CONSTRAINT test UNIQUE (user_id, car)
);
Then mappings is simply:
CREATE TABLE mappings (
mapping_id serial primary key,
user_car_id int references user_cars(user_car_id) on delete cascade,
other_id CHARACTER(9) REFERENCES other (id) ON DELETE CASCADE,
);
If car should be unique, add UNIQUE constrain only on car column.
If user should be unique, add UNIQUE constrain only on user column.
If you add UNIQUE constrain on combination, then there will be duplicate values in the table.
UPDATE:
You can add multiple constraints on single column. With Foreign key add UNIQUE constraint as well on user_cars column in mapping table.

Transform Many to Many relation to One to Many relation

I have the following tables (simplified):
create table dbo.Users
(
User_Id int identity not null
constraint PK_Users_Id primary key clustered (Id),
);
create table dbo.UsersSeals
(
UserId int not null,
SealId int not null,
constraint PK_UsersSeals_UserId_SealId primary key clustered (UserId, SealId)
);
create table dbo.Seals
(
Seal_Id int identity not null
constraint PK_Seals_Id primary key clustered (Id),
);
alter table dbo.UsersSeals
add constraint FK_UsersSeals_UserId foreign key (UserId) references Users(Id) on delete cascade on update cascade,
constraint FK_UsersSeals_SealId foreign key (SealId) references Seals(Id) on delete cascade on update cascade;
So I have a MANY to MANY relation between Users and Seals. One user can have many seals and on seal can have many users. I need a ONE to MANY where one user can have many seals but a seal has only one user.
Yes, I could remove the UsersSeals table and add a UserId into Seals table. However, I am using seals, the same way, with other tables.
I would like to have only one Seals tables with One to Many relation with Users tables and other tables.
Can this be done?
Add a separate unique constraint on the UsersSeals table on your SealID column
You then guarantee that this table is unique on SealID, which means that one seal can be associated with only one user but a user can have many seals.

Problems understanding FOREIGN KEY / CASCADE constraints

I need some help at understanding how foreign keys and cascades work. I understood the theory but I'm having troubles to apply these to a real world example.
Let's assume I've got the following tables (and an arbitrary number of other tables that may reference table tags):
CREATE TABLE tags (
id INT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(255) UNIQUE
) Engine=InnoDB;
CREATE TABLE news (
id INT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
title VARCHAR(63),
content TEXT,
INDEX (title)
) Engine=InnoDB;
So I create a further table to provide the many-to-many relation between news and tags:
CREATE TABLE news_tags (
news_id INT UNSIGNED,
tags_id INT UNSIGNED,
FOREIGN KEY (news_id) REFERENCES news (id) ON DELETE ...,
FOREIGN KEY (tags_id) REFERENCES tags (id) ON DELETE ...
) Engine=InnoDB;
My requirements to the cascades:
If I delete a news, all corresponding entries in news_tags should be removed as well.
Same applies for table x that may be added later with x_tags-table.
If I delete a tag, all corresponding entries in news_tags and in every further table x_tags should be removed as well.
I'm afraid that I may have to revisit my table structure for this purpose, but that's alright since I'm only trying to figure out how stuff works.
Any links to good tutorials, SQL-queries or JPA-examples appreciated!
You seem to be proposing something like this, which sounds reasonable to me:
CREATE TABLE tags
(
id INTEGER NOT NULL,
name VARCHAR(20) NOT NULL,
UNIQUE (id),
UNIQUE (name)
);
CREATE TABLE news
(
id INTEGER NOT NULL,
title VARCHAR(30) NOT NULL,
content VARCHAR(200) NOT NULL,
UNIQUE (id)
);
CREATE TABLE news_tags
(
news_id INTEGER NOT NULL,
tags_id INTEGER NOT NULL,
UNIQUE (tags_id, news_id),
FOREIGN KEY (news_id)
REFERENCES news (id)
ON DELETE CASCADE
ON UPDATE CASCADE,
FOREIGN KEY (tags_id)
REFERENCES tags (id)
ON DELETE CASCADE
ON UPDATE CASCADE
);

To prevent the use of duplicate Tags in a database

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.