Add constraint to table postgresql based on other tables - postgresql

I have these three tables in postgres database:
create table staff (
e_id int primary key,
first_name text,
last_name text,
job text,
branch_name text);
create table temp_staff (
t_id int primary key,
t_first_name text,
t_last_name text,
t_job text );
create table replacements(
e_id int ,
t_id int,
from_date date,
until_date date,
primary key(e_id, t_id, from_date) );
Now I want to add a constraint to the replacements table so you can insert a replacement with e_id and t_id only if e_id exists in the staff table and t_id exists in the temp_staff table.
I tried this:
alter table replacements
add constraint check_existence
check(exists (select 1 from staff where e_id = e_id) and exists (select 1 from temp_staff where t_id = t_id));
And got the error:
ERROR: cannot use subquery in check constraint
Thanks in advance for any help.

Related

Passing UUID of one table to another table as a Foreign key value in PostgreSQL

I have table Employee in Postgres:
drop table if exists employee;
create table employee (
id uuid default uuid_generate_v4 () primary key,
first_name varchar not null,
last_name varchar not null
);
And another table salary :
drop table if exists salary;
create table salary (
check_id uuid default uuid_generate_v4 () primary key,
salary int not null,
employee_id uuid references employee (id)
);
employee_id is the foreign key to id in the Employee table, but I don't understand how to insert a value inside employee_id since UUID is unique.
I am inserting values into Employee table:
insert into employee (first_name, last_name, email, code) values ( 'jonh', 'smith', 'jonh#example.com', '1');
And then if I try insert values into salary table:
insert into salary (salary ) values ('1000');
Then select command will return employee_id value empty.
But if I make it default uuid_generate_v4 (), then result is: Key (employee_id)=(c4ccd745-02ba-4a0e-8586-32e3c6a2b84a) is not present in table "employee".
I understand that because employee_id is a foreign key it should match with uuid in employee, but since uuid is mostly unique, how can I make it work?
You have to use the uuid that was inserted into the employee table. You can do this with a CTE in a single statement:
WITH new_employee AS (
INSERT INTO employee (first_name, last_name, email, code)
VALUES ('jonh', 'smith', 'jonh#example.com', '1')
RETURNING id
)
INSERT INTO salary (salary, employee_id)
SELECT 1000, id
FROM new_employee;

Regarding single column or composite primary key

Is it better to have a single column primary key, or use composite primary keys . I have examples below:
create table emp(
subsidiary_id smallint ,
Employee_id int ,
emp_name varchar,
constraint emp_pkey primary key ( subsidiary_id , Employee_id )
Data will be line
subsidiary_id , employee_id
1, 1
1, 2
1, 3
2, 4
3, 5
2, 6
employee id cannot be same for any subsidiary , always unique but on my reports using both the column in where clause as subsdiary_id = 1 and employee_id = 1 or and so on
so want to know to better approach to create primary on employee_id column or both which one is better?
It sounds like a composite primary key is the right solution for you:
CREATE TABLE emp (
subsidiary_id smallint NOT NULL,
employee_id int NOT NULL,
emp_name text,
CONSTRAINT emp_pkey PRIMARY KEY (subsidiary_id, employee_id)
);
The index that is created for this constraint will also be useful for queries where only subsidiary_id appears in the WHERE condition.

How to write query in postgresql? (relationship many-to-many)

I'm still a newbie.
I created db like this :
DROP DATABASE IF EXISTS image_store_db;
CREATE DATABASE image_store_db;
\c image_store_db;
CREATE TABLE categories_images (
categories_images_id SERIAL PRIMARY KEY,
title VARCHAR NOT NULL,
image_url VARCHAR NOT NULL,
design_url VARCHAR NOT NULL
);
CREATE TABLE images (
images_id SERIAL PRIMARY KEY,
title VARCHAR NOT NULL,
rating REAL NOT NULL,
image_url VARCHAR NOT NULL,
desc_short TEXT NOT NULL,
desc_full TEXT NOT NULL
);
CREATE TABLE ref_categories_images (
categories_images_id integer REFERENCES categories_images (categories_images_id) ON UPDATE CASCADE,
images_id integer REFERENCES images (images_id) ON UPDATE CASCADE ON DELETE CASCADE,
CONSTRAINT ref_categories_images_pkey PRIMARY KEY (images_id, categories_images_id)
);
INSERT INTO categories_images(title, image_url, design_url)
VALUES ('SIMPLE TITLE TEST', '/TEST_URL.PNG', '/TEST_URL.PNG');
INSERT INTO images(title, rating, image_url, desc_short, desc_full)
VALUES ('SIMPLE TITLE TEST', 4.5, '/TEST_URL.PNG', 'TEST_SHORT', 'TEST_FULL');
Pls, help. Teach me, how write a query : insert (for image in category) and select (image from category id) and etc ...
pls ...
My answer assumes that categories_images is the table of categories and images is the table of images. The table and attribute names seem to suggest that the concept of many-to-many join is not all clear to you yet; I would have called the three tables category, image and category_image_map.
For INSERT: if your problem are the serial primary keys, use INSERT ... RETURNING.
You can insert into all three tables in a single statement:
WITH im(im_id) AS (
INSERT INTO categories_images ...
RETURNING categories_images_id
),
cat(cat_id) AS (
INSERT INTO images ...
RETURNING images_id
)
INSERT INTO ref_categories_images (categories_images_id, images_id)
VALUES ((SELECT cat_id FROM cat), (SELECT im_id FROM im));
For the query, you just join the three tables:
SELECT ...
FROM categories_images c
JOIN ref_categories_images r
ON r.categories_images_id = c.categories_images_id
JOIN images i
ON r.images_id = i.images_id
and add a WHERE clause for your condition, for example
WHERE c.categories_images_id = 42
or
WHERE i.image_title = 'Mona Lisa'

"polymorphism" for FOREIGN KEY constraints

There is this field in a table:
room_id INT NOT NULL CONSTRAINT room_id_ref_room REFERENCES room
I have three 2 tables for two kinds of rooms: standard_room and family_room
How to do something like this:
room_id INT NOT NULL CONSTRAINT room_id_ref_room REFERENCES standard_room or family_room
I mean, room_id should reference either standard_room or family_room.
Is it possible to do so?
Here is the pattern I've been using.
CREATE TABLE room (
room_id serial primary key,
room_type VARCHAR not null,
CHECK CONSTRAINT room_type in ("standard_room","family_room"),
UNIQUE (room_id, room_type)
);
CREATE_TABLE standard_room (
room_id integer primary key,
room_type VARCHAR not null default "standard_room",
FOREIGN KEY (room_id, room_type) REFERENCES room (room_id, room_type),
CHECK CONSTRAINT room_type = "standard_room"
);
CREATE_TABLE family_room (
room_id integer primary key,
room_type VARCHAR not null default "family_room",
FOREIGN KEY (room_id, room_type) REFERENCES room (room_id, room_type),
CHECK CONSTRAINT room_type = "family_room"
);
That is, the 'subclasses' point at the super-class, by way of a type descriminator column (such that the pointed to base class is of the correct type, and that primary key of the super class is the same as the child classes.
Here's the same SQL from the accepted answer that works for PostGres 12.8. There's a few issues not only the CREATE_TABLE syntax mistake:
CREATE TABLE room (
room_id serial primary key,
room_type VARCHAR not null,
CONSTRAINT room_in_scope CHECK (room_type in ('standard_room','family_room')),
CONSTRAINT unique_room_type_combo UNIQUE (room_id, room_type)
);
CREATE TABLE standard_room (
room_id integer primary key,
room_type VARCHAR not null default 'standard_room',
CONSTRAINT roomid_std_roomtype_fk FOREIGN KEY (room_id, room_type) REFERENCES public."room" (room_id, room_type),
CONSTRAINT std_room_constraint CHECK (room_type = 'standard_room')
);
CREATE TABLE family_room (
room_id integer primary key,
room_type VARCHAR not null default 'family_room',
CONSTRAINT roomid_fam_roomtype_fk FOREIGN KEY (room_id, room_type) REFERENCES "room" (room_id, room_type),
CONSTRAINT fam_room_constraint CHECK (room_type = 'family_room')
);
NOTE: The SQL above uses constraints to enforce the child room_type values default to the parent tables' room_type values: 'standard_room' or 'family_room'.
PROBLEM: Since the child tables Primary Key's expect either the standard and family room Primary Key that means you can't insert more than one record in thsee two child tables.
insert into room (room_type) VALUES ('standard_room'); //Works
insert into room (room_type) values ('family_room'); //Works
insert into standard_room (room_id,pictureAttachment) VALUES (1,'Before Paint'); //Works
insert into standard_room (room_id,pictureAttachment) VALUES (1,'After Paint'); //Fails
insert into standard_room (room_id,pictureAttachment) VALUES (1,'With Furniture');
insert into family_room (room_id,pictureAttachment) VALUES (2, 'Beofre Kids'); //Works
insert into family_room (room_id,pictureAttachment) VALUES (2,'With Kids'); //Fails
To make the tables accept > 1 row you have to remove the Primary Keys from the 'standard_room' and 'family_room' tables which is BAD database design.
Despite 26 upvotes I will ping OP about this as I can see the answer was typed free hand.
Alternate Solutions
For smallish tables with less than a handful of variations a simple alterative is a single table with Bool columns for different table Primary Key fields.
Single Table "Room"
Id
IsStandardRoom
IsFamilyRoom
Desc
Dimensions
1
True
False
Double Bed, BIR
3 x 4
2
False
True
3 Set Lounge
5.5 x 7
SELECT * FROM Room WHERE IsStdRoom = true;
At the end of the day, in a relational database it's not very common to be adding Room Types when it involves creating the necessary related database tables using DDL commands (CREATE, ALTER, DROP).
A typical future proof database design allowing for more Tables would look something like this:
Multi Many-To-Many Table "Room"
Id
TableName
TableId
1
Std
8544
2
Fam
236
3
Std
4351
Either Standard or Family:
select * from standard_room sr where sr.room_id in
(select TableId from room where TableName = 'Std');
select * from family_room fr where fr.room_id in
(select id from room where TableName = 'Fam');
Or both:
select * from standard_room sr where sr.room_id in
(select TableId from room where TableName = 'Std')
UNION
select * from family_room fr where fr.room_id in
(select id from room where TableName = 'Fam');
Sample SQL to demo Polymorphic fields:
If you want to have different Data Types in the polymorphic foreign key fields then you can use this solution. Table r1 stores a TEXT column, r2 stores a TEXT[] Array column and r3 a POLYGON column:
CREATE OR REPLACE FUNCTION null_zero(anyelement)
RETURNS INTEGER
LANGUAGE SQL
AS $$
SELECT CASE WHEN $1 IS NULL THEN 0 ELSE 1 END;
$$;
CREATE TABLE r1 (
r1_id SERIAL PRIMARY KEY
, r1_text TEXT
);
INSERT INTO r1 (r1_text)
VALUES ('foo bar'); --TEXT
CREATE TABLE r2 (
r2_id SERIAL PRIMARY KEY
, r2_text_array TEXT[]
);
INSERT INTO r2 (r2_text_array)
VALUES ('{"baz","blurf"}'); --TEXT[] ARRAY
CREATE TABLE r3 (
r3_id SERIAL PRIMARY KEY
, r3_poly POLYGON
);
INSERT INTO r3 (r3_poly)
VALUES ( '((1,2),(3,4),(5,6),(7,8))' ); --POLYGON
CREATE TABLE flex_key_shadow (
flex_key_shadow_id SERIAL PRIMARY KEY
, r1_id INTEGER REFERENCES r1(r1_id)
, r2_id INTEGER REFERENCES r2(r2_id)
, r3_id INTEGER REFERENCES r3(r3_id)
);
ALTER TABLE flex_key_shadow ADD CONSTRAINT only_one_r
CHECK(
null_zero(r1_id)
+ null_zero(r2_id)
+ null_zero(r3_id)
= 1)
;
CREATE VIEW flex_key AS
SELECT
flex_key_shadow_id as Id
, CASE
WHEN r1_id IS NOT NULL THEN 'r1'
WHEN r2_id IS NOT NULL THEN 'r2'
WHEN r3_id IS NOT NULL THEN 'r3'
ELSE 'wtf?!?'
END AS "TableName"
, CASE
WHEN r1_id IS NOT NULL THEN r1_id
WHEN r2_id IS NOT NULL THEN r2_id
WHEN r3_id IS NOT NULL THEN r3_id
ELSE NULL
END AS "TableId"
FROM flex_key_shadow
;
INSERT INTO public.flex_key_shadow (r1_id,r2_id,r3_id) VALUES
(1,NULL,NULL),
(NULL,1,NULL),
(NULL,NULL,1);
SELECT * FROM flex_key;

insert into and select

how would the query on:
Update the field total_horas with the hours worked on each project
I have:
insert into proyecto(total_horas)
select trabaja.nhoras
from trabaja;
But it's trying to insert in the first firld of "proyecto" instead on the field "total_horas"
my table:
CREATE TABLE proyecto (
cdpro CHAR(3) NOT NULL PRIMARY KEY,
nombre VARCHAR(30),
coddep CHAR(2),
FOREIGN KEY (coddep)
REFERENCES departamento(cddep)
ON DELETE CASCADE
);
also altered with: alter table proyecto ADD total_horas char ;
You have to put a where condition in select statement.And please elaborate you question. trabaja.nhoras is the column name and you are selecting it from table trabaja
Example:
INSERT INTO proyecto
(total_horas)
SELECT trabaja.nhoras
FROM trabaja
WHERE 'condition' = 'some condition';