get a value for each data and update older records after adding new column - postgresql

I have added a new column 'bookCode' to an existing table.
For now, when new books are added to a table, before inserting we do some requests for each book, get their unique bookcode and insert it to the table.
The problem here is how to update older book records' bookcodes.
So for each book that exist in table we need to get the bookcode and update the field.
What you can advice? What are the best practices?
I am using nodejs and postgresql.

You can write an update request for records where bookCode is null. To write this query, you must use the same logic that you use before inserting records. I don't know your table structure. If your book codes stores in same table, you can update these using join to the same table. If not, then join to another table. I wrote some sample queries for updating.
Sample 1. (Update book codes from same table):
CREATE TABLE books (
id int4 NOT NULL DEFAULT nextval('newtable_id_seq'::regclass),
bookcode int4 NULL,
bookname varchar NULL,
CONSTRAINT newtable_pk PRIMARY KEY (id)
);
INSERT INTO books (id, bookcode, bookname) VALUES(1, NULL, 'Book1');
INSERT INTO books (id, bookcode, bookname) VALUES(2, NULL, 'Book2');
INSERT INTO books (id, bookcode, bookname) VALUES(3, 1245, 'Book1');
INSERT INTO books (id, bookcode, bookname) VALUES(4, 1655, 'Book5');
INSERT INTO books (id, bookcode, bookname) VALUES(5, 2211, 'Book4');
INSERT INTO books (id, bookcode, bookname) VALUES(6, 1219, 'Book8');
INSERT INTO books (id, bookcode, bookname) VALUES(7, 9955, 'Book2');
INSERT INTO books (id, bookcode, bookname) VALUES(8, NULL, 'Book10');
INSERT INTO books (id, bookcode, bookname) VALUES(9, 3357, 'Book10');
update books b1
set
bookcode = b2.bookcode
from books b2
where
b1.bookcode is null
and b1.bookname = b2.bookname
and b2.bookcode is not null
Sample 2. Update book codes from another table
update books b1
set
bookcode = b2.bookcode
from book_code_table b2
where
b1.bookcode is null
and b1.bookname = b2.bookname

Related

Select rows with and without match of join

This – allegedly easy – task currently I cannot solve.
SQL Fiddle
http://sqlfiddle.com/#!17/90dce/1
Schema
Given this schema and data
CREATE TABLE asset (
"id" BIGINT NULL DEFAULT NULL,
"name" TEXT NULL DEFAULT NULL,
PRIMARY KEY ("id")
);
CREATE INDEX IF NOT EXISTS "IDX_id" ON asset (id);
CREATE TABLE category (
"id" BIGINT NULL DEFAULT NULL,
"ctype" TEXT NULL DEFAULT NULL,
"name" TEXT NULL DEFAULT NULL,
PRIMARY KEY ("id")
);
CREATE INDEX IF NOT EXISTS "IDX_id" ON category (id);
CREATE TABLE asset_category (
"asset_id" BIGINT NULL DEFAULT NULL,
"category_id" BIGINT NULL DEFAULT NULL,
CONSTRAINT "FK_asset_id" FOREIGN KEY ("asset_id") REFERENCES "asset" ("id") ON UPDATE CASCADE ON DELETE SET NULL,
CONSTRAINT "FK_category_id" FOREIGN KEY ("category_id") REFERENCES "category" ("id") ON UPDATE CASCADE ON DELETE SET NULL,
UNIQUE (asset_id, category_id)
);
INSERT INTO asset (id, "name") VALUES(1, 'Awesome Asset with a hit');
INSERT INTO asset (id, "name") VALUES(2, 'Great Asset without a hit');
INSERT INTO category (id, "name", "ctype") VALUES(1, 'First Category', NULL);
INSERT INTO category (id, "name", "ctype") VALUES(2, 'Second Category', 'directory');
INSERT INTO asset_category ("asset_id", "category_id") VALUES(1, 1);
INSERT INTO asset_category ("asset_id", "category_id") VALUES(1, 2);
INSERT INTO asset_category ("asset_id", "category_id") VALUES(2, 1);
Task
I want to get all assets with their category Id (in case they have one of type "directory". Otherwise NULL as category.
See my query below, I wrote two joins letting me limit the results in the ON clause. However, since both are related to the other category, the first JOIN hinders me to get a clean result.
What I tried
This query Query A
SELECT a.id "assetId", c.id "categoryId"
FROM asset a
LEFT JOIN asset_category ac ON ac.asset_id = a.id
left join category c on (
c.id = ac.category_id
AND
c.ctype = 'directory'
)
restulting in:
assetId categoryId
1 (null)
1 2
2 (null)
That is almost good, except, assetId 1 appears twice. This probably due to first JOIN, which creates a relation to assetcategory and the other category not of type 'directory'. Same as assetId 2.
Query B uses inner join:
SELECT a.id "assetId", c.id "categoryId"
FROM asset a
LEFT JOIN asset_category ac ON ac.asset_id = a.id
inner join category c on (
c.id = ac.category_id
AND
c.ctype = 'directory'
)
resulting in
assetId categoryId
1 2
However, here the problem is, it hides asset with id 2 for me as join is not successfully resolving asset id 2.
Desired output
assetId | categoryId
1 | 2
2 | null
I would be really happy about this seemingly simple task.
demo:db<>fiddle
Your first query is a good approach. It seems you wanted only one record per id. This is what is DISTINCT ON for:
SELECT DISTINCT ON (a.id)
a.id, c.id
FROM asset a
LEFT JOIN asset_category ac ON a.id = ac.asset_id
LEFT JOIN category c ON c.id = ac.category_id AND c."ctype" = 'directory'
ORDER BY a.id, ctype NULLS LAST
So, just order your joined result by id first, and order ctype = NULL records to bottom, which makes the directory values bubble up being the first one. DISTINCT ON takes the first record for each id afterwards which is the one you expect.

Cannot enter data into tables with inter related foreign keys

I have created the tables with following code, but the foreign key constraints does not allow data addition. What can I do to solve this problem?
CREATE TABLE Employee(
Ssn VARCHAR(10) PRIMARY KEY NOT NULL,
BDate DATE,
FName VARCHAR(25),
MInit VARCHAR(5),
LName VARCHAR(25),
Address VARCHAR(40),
Sex VARCHAR(6),
Salary INT,
SupervisorSsn VARCHAR(10),
DNumber INT
);
CREATE TABLE
CREATE TABLE Department(
DNumber INT PRIMARY KEY NOT NULL,
DName VARCHAR(15),
MgrSsn VARCHAR(10),
MgrStartDate DATE,
NumberofEmployees INT,
CONSTRAINT Department_MgrSsn_FK FOREIGN KEY(MgrSsn) REFERENCES Employee(Ssn) ON DELETE SET DEFAULT ON UPDATE CASCADE
);
ALTER TABLE Employee
ADD CONSTRAINT Employee_SupervisorSsn_FK FOREIGN KEY(SupervisorSsn) REFERENCES Employee(Ssn) ON DELETE SET DEFAULT ON UPDATE CASCADE,
ADD CONSTRAINT Employee_DNumber_FK FOREIGN KEY(DNumber) REFERENCES Department(DNumber) ON DELETE SET DEFAULT ON UPDATE CASCADE;
There are several ways to do that in Postgres.
Update later
The most obvious one: insert null values firs, then update it later:
insert into department
(dnumber, dname)
values
(1, 'One'),
(2, 'Two'),
(3, 'Three');
insert into employee (ssn, fname, lname, supervisorssn, dnumber)
values
('123', 'Arthur', 'Dent', '456', 1),
('456', 'Ford', 'Prefect', null, 2),
('789', 'Zaphod', 'Beeblebrox', null, 3);
update department
set mgrssn = '456'
where dnumber in (1,2);
update department
set mgrssn = '789'
where dnumber = 3;
Online example
Deferred constraints
Make the constraints deferred, so that they will be checked at the end of the transaction, rather when running the INSERT:
ALTER TABLE department
add constraint fk_dempt2emp foreign key (mgrssn) references employee
deferrable initially deferred; --<<
Then you can insert the rows in any order you like as long as everything happens in a single transaction:
begin transaction; --<< important!
insert into department
(dnumber, dname, mgrssn)
values
(1, 'One', '456'),
(2, 'Two', '456'),
(3, 'Three', '789')
insert into employee (ssn, fname, lname, supervisorssn, dnumber)
values
('123', 'Arthur', 'Dent', '456', 1),
('456', 'Ford', 'Prefect', null, 2),
('789', 'Zaphod', 'Beeblebrox', null, 3);
commit; -- the FKs will be checked here
Do everything in a single statement
You can use a data modifying CTE to insert rows into two tables. As this is evaluated as a single statement, the constraints do not need to be deferrable.
with new_depts as (
insert into department
(dnumber, dname, mgrssn)
values
(1, 'One', '456'),
(2, 'Two', '456'),
(3, 'Three', '789')
)
insert into employee (ssn, fname, lname, supervisorssn, dnumber)
values
('123', 'Arthur', 'Dent', '456', 1),
('456', 'Ford', 'Prefect', null, 2),
('789', 'Zaphod', 'Beeblebrox', null, 3)
;
Online example

Copying records in a table with self referencing ids

I have a table with records which can reference another row in the same table so there is a parent-child relationship between rows in the same table.
What I am trying to achieve is to create the same data for another user so that they can see and manage their own version of this structure through the web ui where these rows are displayed as a tree.
Problem is when I bulk insert this data by only changing user_id, I lose the relation between rows because the parent_id values will be invalid for these new records and they should be updated as well with the newly generated ids.
Here is what I tried: (did not work)
Iterate over main_table
copy-paste the static values after each
do another insert on a temp table for holding old and new ids
update old parent_ids with new ids after loop ends
My attempt at doing such thing(last step is not included here)
create or replace function test_x()
returns void as
$BODY$
declare
r RECORD;
userId int8;
rowPK int8;
begin
userId := (select 1)
create table if not exists id_map (old_id int8, new_id int8);
create table if not exists temp_table as select * from main_table;
for r in select * from temp_table
loop
rowPK := insert into main_table(id, user_id, code, description, parent_id)
values(nextval('hibernate_sequence'), userId, r.code, r.description, r.parent_id) returning id;
insert into id_map (old_id, new_id) values (r.id, rowPK);
end loop;
end
$BODY$
language plpgsql;
My PostgreSQL version is 9.6.14.
DDL below for testing.
create table main_table(
id bigserial not null,
user_id int8 not null,
code varchar(3) not null,
description varchar(100) not null,
parent_id int8 null,
constraint mycompkey unique (user_id, code, parent_id),
constraint mypk primary key (id),
constraint myfk foreign key (parent_id) references main_table(id)
);
insert into main_table (id, user_id, code, description, parent_id)
values(0, 0, '01', 'Root row', null);
insert into main_table (id, user_id, code, description, parent_id)
values(1, 0, '001', 'Child row 1', 0);
insert into main_table (id, user_id, code, description, parent_id)
values(2, 0, '002', 'Child row 2', 0);
insert into main_table (id, user_id, code, description, parent_id)
values(3, 0, '002', 'Grand child row 1', 2);
How to write a procedure to accomplish this?
Thanks in advance.
It appears your task is coping all data for a given user to another while maintaining the hierarchical relationship within the new rows. The following accomplishes that.
It begins creating a new copy of the existing rows with the new user_id, including the old row parent_id. That will be user in the next (update) step.
The CTE logically begins with the new rows which have parent_id and joins to the old parent row. From here it joins to the old parent row to the new parent row using the code and description. At that point we have the new id along with the new parent is. At that point just update with those values. Actually for the update the CTE need only select those two columns, but I've left the intermediate columns so you trace through if you wish.
create or replace function copy_user_data_to_user(
source_user_id bigint
, target_user_id bigint
)
returns void
language plpgsql
as $$
begin
insert into main_table ( user_id,code, description, parent_id )
select target_user_id, code, description, parent_id
from main_table
where user_id = source_user_id ;
with n_list as
(select mt.id, mt.code, mt.description, mt.parent_id
, mtp.id p_id,mtp.code p_code,mtp.description p_des
, mtc.id c_id, mtc.code c_code, mtc.description c_description
from main_table mt
join main_table mtp on mtp.id = mt.parent_id
join main_table mtc on ( mtc.user_id = target_user_id
and mtc.code = mtp.code
and mtc.description = mtp.description
)
where mt.parent_id is not null
and mt.user_id = target_user_id
)
update main_table mt
set parent_id = n_list.c_id
from n_list
where mt.id = n_list.id;
return;
end ;
$$;
-- test
select * from copy_user_data_to_user(0,1);
select * from main_table;
CREATE TABLE 'table name you want to create' SELECT * FROM myset
but new table and myset column name should be equal and you can also
use inplace of * to column name but column name exist in new table
othwerwise getting errors

Insert into table, return id and then insert into another table with stored id

I have the following three tables:
Please note that the below DDL came models generated by Django then grabbed out of Postgresql after they were created. So modifying the tables is not an option.
CREATE TABLE "parentTeacherCon_grade"
(
id INTEGER PRIMARY KEY NOT NULL,
"currentGrade" VARCHAR(2) NOT NULL
);
CREATE TABLE "parentTeacherCon_parent"
(
id INTEGER PRIMARY KEY NOT NULL,
name VARCHAR(50) NOT NULL,
grade_id INTEGER NOT NULL
);
CREATE TABLE "parentTeacherCon_teacher"
(
id INTEGER PRIMARY KEY NOT NULL,
name VARCHAR(50) NOT NULL
);
CREATE TABLE "parentTeacherCon_teacher_grade"
(
id INTEGER PRIMARY KEY NOT NULL,
teacher_id INTEGER NOT NULL,
grade_id INTEGER NOT NULL
);
ALTER TABLE "parentTeacherCon_parent" ADD FOREIGN KEY (grade_id) REFERENCES "parentTeacherCon_grade" (id);
CREATE INDEX "parentTeacherCon_parent_5c853be8" ON "parentTeacherCon_parent" (grade_id);
CREATE INDEX "parentTeacherCon_teacher_5c853be8" ON "parentTeacherCon_teacher" (grade_id);
ALTER TABLE "parentTeacherCon_teacher_grade" ADD FOREIGN KEY (teacher_id) REFERENCES "parentTeacherCon_teacher" (id);
ALTER TABLE "parentTeacherCon_teacher_grade" ADD FOREIGN KEY (grade_id) REFERENCES "parentTeacherCon_grade" (id);
CREATE UNIQUE INDEX "parentTeacherCon_teacher_grade_teacher_id_20e07c38_uniq" ON "parentTeacherCon_teacher_grade" (teacher_id, grade_id);
CREATE INDEX "parentTeacherCon_teacher_grade_d9614d40" ON "parentTeacherCon_teacher_grade" (teacher_id);
CREATE INDEX "parentTeacherCon_teacher_grade_5c853be8" ON "parentTeacherCon_teacher_grade" (grade_id);
My Question is: How do I write an insert statement (or statements) where I do not have keep track of the IDs? More specifically I have a teacher table, where teachers can teach relate to more than one grade and I am attempting to write my insert statements to start populating my DB. Such that I am only declaring a teacher's name, and grades they relate to.
For example, if I have a teacher that belong to only one grade then the insert statement looks like this.
INSERT INTO "parentTeacherCon_teacher" (name, grade_id) VALUES ('foo bar', 1 );
Where grades K-12 are enumerated 0,12
But Need to do something like (I realize this does not work)
INSERT INTO "parentTeacherCon_teacher" (name, grade_id) VALUES ('foo bar', (0,1,3) );
To indicate that this teacher relates to K, 1, and 3 grades
leaving me with this table for the parentTeacherCon_teacher_grade
+----+------------+----------+
| id | teacher_id | grade_id |
+----+------------+----------+
| 1 | 3 | 0 |
| 2 | 3 | 1 |
| 3 | 3 | 3 |
+----+------------+----------+
This is how I can currently (successfully) insert into the Teacher Table.
INSERT INTO public."parentTeacherCon_teacher" (id, name) VALUES (3, 'Foo Bar');
Then into the grade table
INSERT INTO public.parentTeacherCon_teacher_grade (id, teacher_id, grade_id) VALUES (1, 3, 0);
INSERT INTO public.parentTeacherCon_teacher_grade (id, teacher_id, grade_id) VALUES (2, 3, 1);
INSERT INTO public.parentTeacherCon_teacher_grade (id, teacher_id, grade_id) VALUES (3, 3, 3);
A bit more information.
Here is a diagram of the database
Other things I have tried.
WITH i1 AS (INSERT INTO "parentTeacherCon_teacher" (name) VALUES ('foo bar')
RETURNING id) INSERT INTO "parentTeacherCon_teacher_grade"
SELECT
i1.id
, v.val
FROM i1, (VALUES (1), (2), (3)) v(val);
Then I get this error.
[2016-08-10 16:07:46] [23502] ERROR: null value in column "grade_id" violates not-null constraint
Detail: Failing row contains (6, 1, null).
If you want to insert all three rows in one statement, you can use:
INSERT INTO "parentTeacherCon_teacher" (name, grade_id)
SELECT 'foo bar', g.grade_id
FROM (SELECT 0 as grade_id UNION ALL SELECT 1 UNION ALL SELECT 3) g;
Or, if you prefer:
INSERT INTO "parentTeacherCon_teacher" (name, grade_id)
SELECT 'foo bar', g.grade_id
FROM (VALUES (0), (2), (3)) g(grade_id);
EDIT:
In Postgres, you can have data modification statements as a CTE:
WITH i as (
INSERT INTO public."parentTeacherCon_teacher" (id, name)
VALUES (3, 'Foo Bar')
RETURNING *
)
INSERT INTO "parentTeacherCon_teacher" (name, teacher_id, grade_id)
SELECT 'foo bar', i.id, g.grade_id
FROM (VALUES (0), (2), (3)) g(grade_id) CROSS JOIN
i

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'