Need to create audit in postgresql for two tables - postgresql

We have two tables product and product_unapproved :
Product Table
Product_code
Product_price
Created_Date
Created_By
Updated_Date
Updated_By
1
25
25/08/2022
Admin1
30/08/2022
Admin2
2
40
26/08/2022
Admin1
31/08/2022
Admin1
3
50
27/08/2022
Admin2
01/09/2022
Admin3
Product Unapproved Table
Product_code
Product_price
Created_Date
Created_By
Updated_Date
Updated_By
2
30
26/09/2022
Manu1
WHenever manufacturer updates price it goes in product_unapproved table and admin needs to approve this record. On admin approval record from product_unapproved is deleted and values are updated in product table.
Admin can also directly update price value which will update record in product table directly
Now I need to maintain audit table
Product_Audit
Product_code
Product_price_old_value
Product_price_new_value
Manufacturer_Name
Admin_Name
Approval-Time
In which Manufacturer_name will be updated if values are copied from product_unapproved table to product_table else it should be null.
I tried implementing this by triggers but created_by value in product_unapproved table is deleted before my trigger in product table is called

Related

PostgreSQL how to build a query which updates data to an existing table but to avoid duplicate value in a specific column

I'm trying to build a very specific PSQL query that has to update a table data adding to a column called sign_order a number that identifies an order of an entity called recipient to sign a document.
To be clear but out of scope is that there is a document that has recipients and the recipients they have to sign that order on a specific order.
I tried initially the following query but the issue is that works if we have no data on the table but soon as it runs on a table filled it generates duplicates for the same type
UPDATE
recipients
SET
sign_order = CASE
"role"
WHEN 'CONSENTEE' THEN 1
WHEN 'GUARDIAN' THEN 2
WHEN 'ASSENTEE' THEN 3
WHEN 'COUNTERSIGNEE' THEN 4
END
WHERE
"role" IN ('CONSENTEE', 'GUARDIAN', 'ASSENTEE', 'COUNTERSIGNEE');
ALTER TABLE recipients ALTER COLUMN sign_order SET NOT NULL;
So what happening here is that is adding the signed order when it finds the case but creates a duplicate for example if that finds already a Guardian it ads as 2 but for the same document we can have multiple Guardian and we have an issue as that is added as Guardian 2 then Guardian 2 but should be Guardian 2 Guardian 3 and so on.
The same affects the rest.
A view of the issue where the recipients under the same document are assigned with the same signing order:
select consentee_id, "role" , count(*)
from recipients
group by "role", consentee_id
order by "count" desc;
Result
The base case order is as in the query;
1 CONSENTEE
2 GUARDIAN
3 ASSENTEE
4 COUNTERSIGNEE
This order needs to be maintained and as an example of the right output running the query should be:
recipient
sign_order
CONSENTEE
1
GUARDIAN
2
GUARDIAN
3
GUARDIAN
4
ASSENTEE
5
COUNTERSIGNEE
6
COUNTERSIGNEE
7
COUNTERSIGNEE
8
I need to add 2 constraints as unique for these consentee_id and sign_order and then make the query more complex to solve the problem described.
The ALTER part I believe with the constraints will look as it is
ALTER TABLE recipients ALTER COLUMN sign_order SET NOT NULL
AND
ADD CONSTRAINT unique_sign_order UNIQUE (consentee_id, sign_order);
However, need some help to achieve the goal of this solving problem issue.
Update to give a better view of the table
Table properties
DDL
CREATE TABLE public.recipients (
id text NOT NULL,
name text NOT NULL,
email text NULL,
phone text NULL,
locale text NULL,
consentee_id text NULL,
is_primary bool NULL DEFAULT false,
sign_order int4 NOT NULL,
"role" text NOT NULL,
created_at timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP,
deleted_at timestamptz NULL,
CONSTRAINT recipients_id_unique UNIQUE (id),
CONSTRAINT recipients_pkey PRIMARY KEY (id),
CONSTRAINT recipients_consentee_id_foreign FOREIGN KEY (consentee_id) REFERENCES public.consentees(id) ON DELETE CASCADE
);
Assuming you have some as yet unmentioned unique column (PK perhaps) you can assign the sign_order value in a CTE, then the main portion of the query updates the column for the PK. I've also assumed your table also contains more that 1 document to be signed (if not it a pretty limited table) with its own signing order. In the following these column are id and doc_id respectively. If those assumptions are invalid, well do not know if this will work.
The big change from what you had was to move the CASE expression into an order by clause of a row_number window function. Then assign sort_order from the row_number value and that is a CTE. (see demo)
with assign_sign_order ( rid, sorder) as
( select id, row_number() over (partition by doc_id
order by
case "role"
when 'CONSENTEE' then 1
when 'GUARDIAN' then 2
when 'ASSENTEE' then 3
when 'COUNTERSIGNEE' then 4
end
) so
, "role", doc_id
from recipients order by id
)
update recipients r
set sign_order = s.sorder
from assign_sign_order s
where r.id = s.rid ;
An alternative would be to create a ENUM for the "role", this simplifies the above query, and would reduce maintenance to just updating the ENUM (no change to the query). Providing you do not want to remove one. It is also in the demo.

Delete all records that violate new unqiue constraint

I have a table that has the following fields
----------------------------------
| id | user_id | doc_id |
----------------------------------
I want to create a new unique constraint to make sure that there are no repeat user_id and doc_id records. Aka a user can only be linked to a doc one time. That is simple enough.
ALTER TABLE mytable
ADD CONSTRAINT uniquectm_const UNIQUE (user_id, doc_id);
The issue is I have records that currently violate that constraint. I was wondering if there is an easy way to query for those records or to tell postgres just delete anything that violates the constraint.
Identifying records that violate your new key:
SELECT *
FROM
(
SELECT id, user_id, doc_id
, COUNT(*) OVER (PARTITION BY user_id, doc_id) as unique_check
FROM mytable
)
WHERE unique_check > 1;
Then you can figure out from those duplicates, which should be deleted and perform the delete.
To my knowledge there is no other way to perform this since any automated "Delete any duplicates" command would leave the database engine to decide which of the two-or-more duplicate records to get rid of.
If the entire record is a duplicate (all columns match) then you could just create a new table with your new unique constraint and do a INSERT INTO newtable SELECT DISTINCT * FROM oldtable but I'm betting that isn't the case.

Creating a 2-way relationship in PostgreSQL table

I have 3 tables representing UUIDs, Name, Location, and Info of a house, room and drawers (this is an example as my work is sensitive).
So, for example 1 house will have many rooms (one to many) and the many rooms will contain many drawers (many to many).
The idea is that an associations table will be created where each UUID of the rows in the table will be associated with the corresponding UUID of the other table.
For example, if I query the house which is represent by ID1 it will return the following:
SELECT * FROM house where 'ID_1='1';
| ID_1|ID_2 |
| ----| -----|
| 1 | 201 |
| 1 | 254 |
| 1 | 268 |
So far, I have created a temporary version of the associations table of how I need it to be represented in the real table. However, now I need a function to automatically fill in the IDs properly for all rows from the temporary associations table to the real associations table. For example:
INSERT INTO associations (id_1, id_2) VALUES
('1','201'),
('201','1')
I need it to be directionless so that when I query id_1 I'm also getting it's linked id_2 in the result
Let's say your query to get a one-way relationship looks like this:
SELECT room_uuid AS left_uuid, house_the_room_is_in_uuid AS right_uuid
FROM rooms
WHERE house_the_room_is_in_uuid IS NOT NULL
AND is_active
All you need to get the reverse relationship is to put the list in the other order; the rest of the query doesn't need to change, however complex it is:
SELECT house_the_room_is_in_uuid AS left_uuid, room_uuid AS right_uuid
FROM rooms
WHERE house_the_room_is_in_uuid IS NOT NULL
AND is_active
Both of those will be valid as queries to insert into a table with two UUID columns:
CREATE TABLE my_lookup_table (left_uuid UUID, right_uuid UUID);
INSERT INTO my_lookup_table (left_uuid, right_uuid)
SELECT ... -- either of the above
To combine them, either insert each into the same table in turn, or use a UNION to create one result set with both sets of rows:
SELECT room_uuid AS left_uuid, house_the_room_is_in_uuid AS right_uuid
FROM rooms
WHERE is_in_house_uuid IS NOT NULL
AND is_active
UNION
SELECT house_the_room_is_in_uuid AS left_uuid, room_uuid AS right_uuid
FROM rooms
WHERE is_in_house_uuid IS NOT NULL
AND is_active
All that's required for a union is that the queries have the same number and type of columns. The names (if relevant at all) come from the first query, but I find it more readable if you include the aliases on both.
Since the result of that UNION is itself just a two-column result set, it can be used with the same INSERT statement as before. That would allow you to insert into the table even if it had a self-referencing foreign key constraint as discussed here:
ALTER TABLE my_lookup_table ADD CONSTRAINT
my_lookup_table_combinations_must_be_unique
UNIQUE (left_uuid, right_uuid);
ALTER TABLE my_lookup_table ADD CONSTRAINT
my_lookup_table_must_have_rows_both_ways_around
FOREIGN KEY (right_uuid, left_uuid)
REFERENCES my_lookup_table (left_uuid, right_uuid);
If you tried to insert just one set of rows, this would fail, but with the UNION, by the end of the statement/transaction, each row is in the table both ways around, so the constraint is met.

database table for scheduling system

Please correct me if I design it wrong. I need to design 3 tables as follow:
students : id, name
sections : id, student_id, s_name
schedules: id, section_id, c_name
And here are requirements:
There are students, and each of those student has sections, and each of those sections has schedule.
The relationship from students to sections, sections to schedules is 1 to many
student can have many sections
section can have only 1 student / section can have many schedules
schedule can have only 1 section
========= Here are my tables: =====
students
id --> primary and auto increment
name
sections
id --> primary and auto increment
student_id --> foreign key reference to id of students table
s_name
schedules
id --> primary and auto increment
section_id --> foreign key reference to id of sections table
c_name
I would appreciate any helps and many thanks.
Yes, your schema seems to be BCNF normalized, but it does not fulfil all the given requirement. I would change a few things to make it more intuitive.
2. Sections Table:
section_id -> primary, auto increment
student_id -> Foreign Key to students.id
Explanation: Section ID is one to one with student, but student is one to many with section.
3. SectionNames Table:
section_id -> primary and foreign key, from section table.
section_name -> String, name of section.
Explanation : You would add this if you need to store s_name, like you're doing right now.
4. Schedule Table:
class_id -> Primary, auto increment key
section_id -> Foreign key, sections table.
Explanation: Schedule's (Class's) one to one relation to section, and sections one to many relation to classes.
5. ScheduleName Table:
class_id -> Primary, and foreign key to schedule table.
class_name -> String
Explanation: Stores c_name as per your table schema.

Insert multiple rows in role table based on condition

I have two tables one is role and another one is user_role. User_role table contains all users of particular role. Now, i added two extra new roles into the role table. I want to insert the rows into user_role table who has role_id=4 with the new role_ids. I cannot update exsting rows because i want exsting rows also means the users who have role_id=4 also belongs to new role_ids.
I tried in this way
INSERT INTO table2 (all_links, fields_one, fields_two)
select URI, fields, details FROM table1
WHERE date > "12-11-2013 00-00-00";
But here two tables are there, but in my case only one table is there. And in user_role table id is sequene so cant select from another table dirctely and do insert.
Please help on these.
You can select and insert records into the same table.
If I understood your use case correctly,
INSERT INTO user_role (role_id, <other columns>)
SELECT <new_role_id>, <other columns>
FROM
user_role
WHERE
role_id = 4.
This will insert all the existing records in user_role table with role_id as 4, into the same table with a different role_id. HTH.