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

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.

Related

ERROR: No unique constraint matching when having FK

I have three tables that are linked together
My script:
-- Ticket --
CREATE TABLE public.ticket (
id bigint NOT NULL,
libelle character varying(255) NOT NULL,
description character varying(255) NOT NULL,
status character varying(255) NOT NULL,
date_creation timestamp NOT NULL,
date_modification timestamp NOT NULL,
user_createur_id bigint,
referent_realisateur_id bigint,
CONSTRAINT pk_ticket PRIMARY KEY (id)
);
-- Ticket_Avance TABLE --
CREATE TABLE public.ticket_avance (
id bigint NOT NULL,
date_livraison_souhaite timestamp NOT NULL,
date_engagement_livraison timestamp NOT NULL,
referent_demandeur_id bigint
);
ALTER TABLE public.ticket_avance ADD CONSTRAINT "fk_ticket_ticketAvance" FOREIGN KEY (id)
REFERENCES public.ticket (id) MATCH SIMPLE
ON DELETE NO ACTION ON UPDATE NO ACTION;
-- Demande_Travaux TABLE --
DROP TABLE IF EXISTS public.demande_travaux CASCADE;
CREATE TABLE public.demande_travaux (
id bigint NOT NULL,
contrat_id bigint
);
ALTER TABLE public.demande_travaux ADD CONSTRAINT "fk_ticketAvance_DDT" FOREIGN KEY (id)
REFERENCES public.ticket_avance (id) MATCH FULL
ON DELETE NO ACTION ON UPDATE NO ACTION;
I have this error on the demand_travaux creation
SQL Error [42830]: ERROR: there is no unique constraint matching given keys for referenced table "ticket_avance"
ERROR: there is no unique constraint matching given keys for referenced table "ticket_avance"
ERROR: there is no unique constraint matching given keys for referenced table "ticket_avance"
You have forgotten to declare column ID in table ticket_avance as primary key.
Please use the following SQL:
-- Ticket_Avance TABLE --
CREATE TABLE public.ticket_avance (
id bigint NOT NULL,
date_livraison_souhaite timestamp NOT NULL,
date_engagement_livraison timestamp NOT NULL,
referent_demandeur_id bigint,
CONSTRAINT pk_avance PRIMARY KEY (id) --- add this
);
you need add constraint in Ticket_Avance table because you provide reference this id to demande_travaux
-- Ticket_Avance TABLE --
CREATE TABLE ticket_avance (
id bigint NOT NULL,
date_livraison_souhaite timestamp NOT NULL,
date_engagement_livraison timestamp NOT NULL,
referent_demandeur_id bigint,
CONSTRAINT pk_ticket_avance PRIMARY KEY (id) //constraint that you need
);
ALTER TABLE demande_travaux ADD CONSTRAINT "fk_ticketAvance_DDT" FOREIGN KEY (id)
REFERENCES ticket_avance (id) MATCH FULL
ON DELETE NO ACTION ON UPDATE NO ACTION;
here is the demo link of your full query
A foreign key constraint has to target a primary key or unique constraint. The database has to be able to identify a single row in the "parent" table.
You could add primary key constraints:
ALTER TABLE public.ticket_avance ADD PRIMARY KEY (id);
In addition, you should have an index on the column on which the foreign key is defined, particularly if you plan to delete parent rows. With the primary key above, you have such an index on id, but you also should have one on demande_travaux.
The simplest way is to define id as primary key there too:
ALTER TABLE public.demande_travaux ADD PRIMARY KEY (id);

Foreign key Constraint on delete cascade does not work postgres

When I run DELETE FROM users WHERE id='some_id' the record on beta_keys table does not get deleted.
beta_keys table:
CREATE TABLE beta_keys (
id serial PRIMARY KEY,
key VARCHAR(60) UNIQUE NOT NULL,
created_at TIMESTAMP NOT NULL,
updated_at TIMESTAMP
);
users table:
CREATE TABLE users (
id serial PRIMARY KEY,
email VARCHAR (256) UNIQUE NOT NULL,
password VARCHAR (60) NOT NULL,
beta_key_id INTEGER,
created_at TIMESTAMP NOT NULL,
updated_at TIMESTAMP,
CONSTRAINT users_beta_key_id_fkey FOREIGN KEY (beta_key_id)
REFERENCES beta_keys (id) MATCH SIMPLE
ON DELETE CASCADE
);
users references beta_keys. delete cascade works by deleting referencing rows (users) when the referenced row (beta_keys) is deleted.
sqlfiddle: http://sqlfiddle.com/#!17/a7495/1

ERROR: there is no unique constraint matching given keys for referenced table "users"

I have read through some of the similar questions and answers and I don't quit understand the problem. One of my current guess is that Potgres might not allow foreign key-ing of non-primary key.
user:
user registration, 1 user per row, 1 id per row(serial), unique email(primary key)
user_logins:
information about user logins, references user_id from user as foreign key, the rest of the columns together makes a composite primary key
CREATE TABLE users
(
unique_email TEXT NOT NULL,
password TEXT NOT NULL,
user_id SERIAL,
created_date TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT LOCALTIMESTAMP,
CONSTRAINT emailPK
PRIMARY KEY(unique_email)
);
CREATE TABLE user_logins
(
user_id SERIAL,
login_date TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT LOCALTIMESTAMP,
login_ip INET,
CONSTRAINT user_idFK
FOREIGN KEY (user_id)
REFERENCES users(user_id)
ON DELETE CASCADE
ON UPDATE CASCADE,
CONSTRAINT composit_PK_logins
PRIMARY KEY(user_id, login_date, login_ip)
);
user_id must have unique constraints in users table. Otherwise, it will have ambiguities.
In this query, I added UNIQUE constraint in user_id
CREATE TABLE users
(
unique_email TEXT NOT NULL,
password TEXT NOT NULL,
user_id SERIAL UNIQUE,
created_date TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT LOCALTIMESTAMP,
CONSTRAINT emailPK
PRIMARY KEY(unique_email)
);

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.

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.