I have 3 tables.
chat_room
create table chat_room
(
id uuid default uuid_generate_v4() not null
constraint chat_room_pk
primary key
constraint fk__chat_room__group_chat_room
references group_chat_room
on update cascade on delete cascade
constraint fk__chat_room__private_chat_room
references private_chat_room
on update cascade on delete cascade,
name varchar(255) not null,
description varchar(255),
profile_pic varchar(128),
created_at timestamp with time zone default now() not null,
updated_at timestamp with time zone
);
group_chat_room
create table group_chat_room
(
id uuid not null
constraint group_chat_room_pk
primary key
constraint fk__group_chat_room___chat_room
references chat_room
on update cascade on delete cascade,
pus_code char(7) not null
constraint fk__group_chat_room__puskesmas
references puskesmas
on update cascade on delete cascade
);
create unique index group_chat_room_pus_code_uindex
on group_chat_room (pus_code);
private_chat_room
create table private_chat_room
(
id uuid not null
constraint private_chat_room_pk
primary key
constraint fk__private_chat_room__chat_room
references chat_room
on update cascade on delete cascade
);
As you can see chat_room has foreign key constraints that refer to group_chat_room and private_chat_room. Also both group_chat_room and private_chat_room have FK constraints that refer to chat_room.
When I want to INSERT a row into group_chat_room I would use this
with chat_room as (
insert into chat_room (id, name) values ('Some ID', 'Some Name')
)
insert into group_chat_room(id, pus_code) values ('Some ID', 'Some Code');
However because of those constraints this would produce an error
[23503] ERROR: insert or update on table "chat_room" violates foreign key constraint "fk__chat_room__private_chat_room" Detail: Key (id)=(cef8c655-d46a-4f63-bdc8-77113b1b74b4) is not present in table "private_chat_room".
How do I only insert to group_chat_room without having to insert it to private_chat_room?
The main problem here is creating multiple required bidirectional foreign keys. You can probably work around it, eventually. But it makes the data model more complicated and the code much more so. And it is completely unnecessary. Everything you have can be accomplished with just 1 table. If group_chat_room and private_chat_room are independently required then create a view for each. Further, as simple views, they are fully update-able.
You accomplish this by moving the column 'pus_code' to chat_room and adding 2 boolean values to indicate if this is a private or group room or both. Yes, strange as it sounds you can get a private_group_chat_room. (Note: there is nothing in your design preventing it and the error you are getting is because it is required). If you do want that then create a check constraint requiring at least 1 of the boolean columns be false.
create table chat_room
(
id integer generated always as identity
constraint chat_room_pk
primary key,
name varchar(255) not null,
description varchar(255),
profile_pic varchar(128),
is_private boolean not null default false,
is_group boolean not null default false,
pus_code varchar(7)
constraint fk__group_chat_room__puskesmas
references puskesmas
on update cascade on delete cascade,
created_at timestamp with time zone default now() not null,
updated_at timestamp with time zone,
constraint not_group_of_pus_code_check
check ( (not is_group and pus_code is null)
or (is_group and pus_code is not null)
)
);
-- create unique partial index
create unique index group_chat_room_pus_code_uindex on chat_room(pus_code)
where is_group;
-- group_chat_room
create view group_chat_room
( id
, name
, description
, profile_pic
, is_private
, pus_code
, created_at
, updated_at
) as
select id
, name
, description
, profile_pic
, is_private
, pus_code
, created_at
, updated_at
from chat_room
where is_group;
-- private_chat_room
create view private_chat_room
( id
, name
, description
, profile_pic
, is_group
, pus_code
, created_at
, updated_at
) as
select id
, name
, description
, profile_pic
, is_group
, pus_code
, created_at
, updated_at
from chat_room
where is_private;
See fiddle for full example and a few tests. Note: fiddle has an issue with generate_uuid_v4() (did not exist) so for demo I changed to identity. It will fine in an operational environment.
Related
I have 4 related tables: order_info, master_bill, master_bill_order_leg and order_leg.
MasterBill and OrderLeg have many to many relationship.
This is the quick documentation from IntelliJ for the DB tables.
Table master_bill:
create table master_bill
(
id bigint not null
primary key,
mb_no bigint not null,
created_by text not null,
updated_by text not null,
created_at timestamp with time zone default now() not null,
updated_at timestamp with time zone default now() not null
);
Table order_info:
create table order_info
(
order_id bigint not null
primary key
references public.order_t
on delete cascade,
mb_id bigint not null
references master_air_waybill
on delete cascade,
created_by text not null,
updated_by text not null,
created_at timestamp with time zone default now() not null,
updated_at timestamp with time zone default now() not null
);
Table master_bill_order_leg:
create table master_bill_order_leg
(
mb_id bigint
references master_bill
on delete cascade,
order_leg_id bigint
references order_leg
on delete cascade,
constraint master_bill_order_mb_id_order_leg_id_key
unique (mb_id, order_leg_id)
);
Table order_leg:
create table order_leg
(
id bigserial
primary key,
created_by text not null,
updated_by text not null,
created_at timestamp with time zone default now() not null,
updated_at timestamp with time zone default now() not null,
constraint order_leg_unique_c
unique (flight_id, flight_date, departure_iata, arrival_iata)
);
I have set the foreign keys and they look like this:
Table master_bill_order_leg
master_bill_order_leg_mb_id_fkey (mb_id) -> master_bill(id)
master_bill_order_leg_order_leg_id_fkey (order_leg_id) -> order_leg(id)
Table order_info
order_info_mb_id_fkey (mb_id) -> master_bill(id) ON DELETE CASCADE
order_info_order_id_fkey (order_id) -> order_t(id) ON DELETE CASCADE
I thought that if order from order_t table is deleted that all relevant rows from other tables would be deleted too, but that is not the case, only order_info row is deleted on cascade. So, I have tried with deleting a row from master_bill table:
DELETE
FROM master_bill
WHERE id = :mbId
But, then I get an error:
org.postgresql.util.PSQLException: ERROR: update or delete on table
"master_bill" violates foreign key constraint
"master_bill_order_leg_mb_id_fkey" on table
"master_bill_order_leg" Detail: Key (id)=(1076) is
still referenced from table "master_bill_order_leg".
I thought that this foreign key would delete on cascade from master_bill_order_leg table if I delete a row from master_bill, since master_bill is a parent table.
What am I doing wrong here?
You can use both foreign key and trigger to achieve this. (Result here)
create table master_bill
(
id bigint not null primary key,
mb_no bigint not null,
created_by text not null,
updated_by text not null,
created_at timestamp with time zone default now() not null,
updated_at timestamp with time zone default now() not null
);
create table order_info
(
order_id bigint not null primary key,
mb_id bigint not null references master_bill(id) on delete cascade,
created_by text not null,
updated_by text not null,
created_at timestamp with time zone default now() not null,
updated_at timestamp with time zone default now() not null
);
create table master_bill_order_leg
(
mb_id bigint references master_bill(id) on delete cascade,
order_leg_id bigint,
constraint master_bill_order_mb_id_order_leg_id_key unique (mb_id, order_leg_id)
);
create table order_leg
(
id bigint primary key,
created_by text not null,
updated_by text not null,
created_at timestamp with time zone default now() not null,
updated_at timestamp with time zone default now() not null
);
CREATE OR REPLACE FUNCTION public.f_delete_order_leg()
RETURNS trigger
LANGUAGE plpgsql
AS $function$
begin
delete from order_leg where id in (select order_leg_id from old_table);
return null;
end;
$function$
;
create trigger t_delete_order_leg
after delete
on master_bill_order_leg
referencing old table as old_table
for each statement
execute function f_delete_order_leg();
The way a CASCADE DELETE works is when you delete a record in the parent table, the related records in the child table are deleted
When you are running
DELETE
FROM order_info
WHERE order_id = :id
this is a child table (its FK references a PK in a parent table) it doesn't have any foreign keys in other tables that reference it
A simple example of how cascade deletes works is a two table database of departments and employees - one department can have many employees and this is enforced by an FK / PK relationship:
CREATE TABLE Department
(
DeptId INT PRIMARY KEY,
DeptName VARCHAR(10)
);
CREATE TABLE Employee
(
EmployeeID INT ,
EmployeeName VARCHAR(10),
DepartmentID INT,
CONSTRAINT DepartmentID_FK foreign key (DepartmentID) references Department(DeptId) ON DELETE CASCADE
);
INSERT INTO Department VALUES (1,'IT'),(2,'HR');
INSERT INTO Employee VALUES (1,'John',2),(2,'Kim',2),(3,'Sam',1);
At this point, if I run
SELECT * FROM Employee
I get 3 records.
If I delete a department from the parent table:
DELETE FROM Department WHERE DeptID = 2
and I run the select again, I now have 1 record (Sam) as the cascade delete has deleted those employees in department ID 2 (john and Kim)
I have 2 tables under PostgreSQL:
a table property that is the mother table (id, created_at ..) and
a habitation table (surface, room, etc...) that inherits property. On the mother table, I have a many-many relationship table. I have added results in the "habitation" table.
When I try to insert "property_table" with habitation IDs, I have a SQL error telling me that the property_id ID does not exist. It exists when I go to see the property or habitation table. I want to insert habitation IDs (so property) in property_tag , should I force insert? Help please
The SQL schemas :
CREATE TABLE "property"
(
id serial NOT NULL,
created_at timestamp with time zone NOT NULL DEFAULT now(),
updated_at timestamp with time zone NOT NULL DEFAULT now(),
address_id int NOT NULL,
permission int NOT NULL DEFAULT 6,
user_id int NOT NULL,
PRIMARY KEY (id),
FOREIGN KEY (address_id) REFERENCES "address"(id),
FOREIGN KEY (user_id) REFERENCES "user"(id)
);
CREATE TABLE habitation (
total_surface float,
living_surface float,
ground_surface float,
ground_surface_unity character varying(15) DEFAULT 'm2',
room integer,
bedroom integer,
floor integer,
level integer,
year integer
) INHERITS (property);
CREATE TABLE property_tag
(
tag_id int NOT NULL,
property_id int NOT NULL,
PRIMARY KEY (tag_id, property_id),
FOREIGN KEY (tag_id) REFERENCES "tag"(id) ON DELETE CASCADE,
FOREIGN KEY (property_id) REFERENCES "property"(id) ON DELETE CASCADE
);
CREATE TABLE "tag"
(
id serial NOT NULL,
created_at timestamp with time zone NOT NULL DEFAULT now(),
updated_at timestamp with time zone NOT NULL DEFAULT now(),
name character varying(127) NOT NULL,
user_id int NOT NULL,
color character varying(7) NOT NULL DEFAULT 'A4A4A8',
PRIMARY KEY (id),
FOREIGN KEY (user_id) REFERENCES "user"(id) ON DELETE CASCADE
);
When you insert into table "habitation", the row exists physically only in table "habitation", but not in "property".
Your table "property_tag" references to table "property" (which have no rows, cause rows are in table "habitation") and therefore you are getting error during insert.
You have to change your reference in table "property_tag" from "property" to "habitation".
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
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.
I'm not sure why these have to be unique, but from reading the MySQL forums it appears that they do. However, I think it has something more to do with the INDEX name. I have two tables that have foreign key constraints referencing the same primary key on a third table. If it helps, I'm using MySQL workbench to design the schema.
I usually name my foreign key on each table the same name as the primary key it references. I guess this isn't possible. It will create the first table with the foreign key constraint, but when it tries to create the second table it throws an error. Here is the second table it throws the error on:
CREATE TABLE IF NOT EXISTS `joe`.`products_to_categories` (
`product_to_category_id` INT NOT NULL AUTO_INCREMENT ,
`category_id` INT NOT NULL ,
`product_id` INT NOT NULL ,
PRIMARY KEY (`product_to_category_id`) ,
INDEX `category_id` (`category_id` ASC) ,
INDEX `product_id` (`product_id` ASC) ,
CONSTRAINT `category_id`
FOREIGN KEY (`category_id` )
REFERENCES `joe`.`categories` (`category_id` )
ON DELETE CASCADE
ON UPDATE NO ACTION,
CONSTRAINT `product_id`
FOREIGN KEY (`product_id` )
REFERENCES `joe`.`products` (`product_id` )
ON DELETE CASCADE
ON UPDATE NO ACTION)
ENGINE = InnoDB;
I want the foreign key names to be the same as the primary key in both of the other tables. What should I remove here so that I can use these names. What is the best practice here.
It is not possible because you would have a conflict in the filename for the file that is used for the index IIRC. I probably would name the key < tablename >_< column_name > or something like that.
You are creating an index (constraint) by the name of product_id via:
INDEX product_id
Then you are going and creating another constraint (for the foreign key) with the same name:
CONSTRAINT product_id
What you need to do is allow the server to provide a default, unique constraint name by removing the
CONSTRAINT product_id
See this URL: http://dev.mysql.com/doc/refman/5.1/en/innodb-foreign-key-constraints.html
"If the CONSTRAINT symbol clause is given, the symbol value must be unique in the database. If the clause is not given, InnoDB creates the name automatically."
In PostgreSQL, the default for naming indexes is to append "_pkey" and "_fkey" to the name of the primary and foreign key, respectively. So your case would look like:
INDEX `product_id_fkey` (`product_id` ASC) ,
UPDATE: I just tried this and it worked. See if that's what you had in mind.
use test;
create table if not exists test.product
(
product_id int not null auto_increment,
name varchar(80) not null,
primary key(product_id)
);
create table if not exists test.category
(
category_id int not null auto_increment,
name varchar(80) not null,
primary key(category_id)
);
create table if not exists test.product_category
(
product_id int,
category_id int,
primary key(product_id, category_id),
constraint product_id_fkey
foreign key(product_id) references product(product_id)
on delete cascade
on update no action,
constraint category_id_fkey
foreign key(category_id) references category(category_id)
on delete cascade
on update no action
);
insert into test.product(name) values('teddy bear');
insert into test.category(name) values('toy');
insert into test.product_category
select p.product_id, c.category_id from product as p, category as c
where p.name = 'teddy bear' and c.name = 'toy';