hasura unique keys + bool condition - graphql

I'm trying to add some custom logic to one of my tables on Hasura Postgre.
id : PK
user_id : FK
tag_id : FK
is_active : bool
1
abc
xyz
true
2
abc
xyz
false
3
abc
xyz
false
Allow only one row with same pair of FK's but column is_active == true;
Allow multiple rows with same pair of FK's but column is_active == false;
...
How can I set Hasura up to in case of an insert or update, Hasura would update the old value with is_active = false and keep only the new one with is_active = true automatically.
Thanks!!!

thanks for the comments. ended up solving with an event trigger to my backend and managing everything from there.

Related

Procedure to remove duplicates in a table

Brief model overview:
I have a student and a course tables. As it's many to many relation there is also a junction table student_course (id_student, id_course), with unique constraint on both columns (composite).
The problem I want to solve:
On account of a mistake, there is no a unique constraint on the code column of the course table. It should as code column should uniquely identify a course. As a result there are two rows in the course table with the same value in the code column. I want to remove that duplicate, check that there is no other duplicates and add a unique constraint on the code column. Without loosing relations with student table.
My approach to solve the issue:
I have create a procedure that should do what I want.
CREATE OR REPLACE PROCEDURE REMOVE_COURSES
(
v_course_code IN VARCHAR2,
v_course_price IN VARCHAR2
)
AS
new_course_id NUMBER;
BEGIN
INSERT INTO course (CODE, PRICE) VALUES (v_course_code, v_course_price)
RETURNING ID INTO new_course_id;
FOR c_course_to_overwrite IN (SELECT *
FROM course
WHERE code = v_course_code AND id != new_course_id) LOOP
UPDATE student_course SET id_course = new_course_id WHERE id_course = c_course_to_overwrite.id;
DELETE FROM course WHERE id = c_course_to_overwrite.id;
END LOOP;
END REMOVE_COURSES;
/
Main problem I want to solve:
The procedure keeps giving me an error about unique constraint violation on student_course table. But I am really not sure how it's possible as I am using new_course_id, so there is no chance that in the junction table there are two rows with the same id_student, id_course. What do I need to fix ?
Miscellaneous:
I want to solve that issue using procedure only for learning purposes
EDITED:
CREATE TABLE student (
id NUMBER GENERATED BY DEFAULT ON NULL AS IDENTITY,
name VARCHAR2(150) NOT NULL,
PRIMARY KEY (id)
);
ALTER TABLE student MODIFY ID
GENERATED BY DEFAULT ON NULL AS IDENTITY (START WITH LIMIT VALUE);
CREATE TABLE course (
id NUMBER GENERATED BY DEFAULT ON NULL AS IDENTITY,
code VARCHAR2(255) NOT NULL,
PRIMARY KEY (id)
);
ALTER TABLE course MODIFY ID
GENERATED BY DEFAULT ON NULL AS IDENTITY (START WITH LIMIT VALUE);
CREATE TABLE student_course (
id_student NUMBER NOT NULL,
id_course NUMBER NOT NULL,
PRIMARY KEY (id_student, id_course),
CONSTRAINT student_fk FOREIGN KEY (id_student) REFERENCES student (id),
CONSTRAINT course_fk FOREIGN KEY (id_course) REFERENCES course (id)
);
insert into student (name) values ('John');
INSERT INTO course (ID, CODE) VALUES (1, 'C_13');
INSERT INTO course (ID, CODE) VALUES (2, 'C_13');
commit;
INSERT INTO STUDENT_COURSE (ID_STUDENT, ID_COURSE) VALUES (1, 1);
INSERT INTO STUDENT_COURSE (ID_STUDENT, ID_COURSE) VALUES (1, 2);
commit;
CALL REMOVE_COURSES('C_13');
[23000][1] ORA-00001: unique constraint (SYS_C0014983) violated ORA-06512: near "REMOVE_COURSES", line 8
Rather than removing one of the duplicate codes, you're creating a third course with the same code, and trying to move all students on either of the old courses onto the new one. The error suggests you have students who are already enrolled on both of the old courses.
Your cursor loop query is:
SELECT *
FROM course
WHERE code = v_course_code AND id != new_course_id
That will find all junction records for both old versions of the code, and the update then sets all of those junction records to the same new ID.
If there are any students listed against both old IDs for the code - which would be allowed by your composite unique key - then they will both be updated to the same new ID.
So say the courses you're looking at are [updated for your example code]:
ID CODE
-- ----
1 C_13
2 C_13
and you have junction records for a student for both courses, like:
ID_STUDENT ID_COURSE
---------- ---------
1 1
1 2
You are creating a new course:
ID CODE
-- ----
3 C_13
Your cursor loop looks for code = 'ABC' and ID != 3, which finds IDs 1 and 2. So in the first iteration of the loop up update the rows with ID 1, so now you have:
ID_STUDENT ID_COURSE
---------- ---------
1 3
1 2
Then in the second iteration you try to update the rows with ID 2, which would attempt to produce:
ID_STUDENT ID_COURSE
---------- ---------
1 3
1 3
which would break the unique constraint - hence the error.
You probably don't want to create a new course at all, but either way, you need to remove duplicate records from student_course - that is, rows which will become duplicates when updated. Basically you need to find students with entries for both existing course IDs, and delete either of them. If you don't care which this would do it:
delete from student_course sc1
where id_course in (
select id
from course
where code = 'C_13'
)
and exists (
select null
from student_course sc2
join course c on c.id = sc.id_course
where sc2.id_student = sc1.id_student
and sc2.id_course > sc1.id_course
and c.code = 'C_13'
);
but there are other (probably better) ways.
You then have the choice of updating all remaining junction records for both old IDs to your new ID; or to consolidate on one of the old IDs and remove the other.
(Your question implies you want to solve the overall task yourself, so I'll refrain from trying to provide a complete solution - this just hopefully helps you understand and resolve your main problem...)

Update with 2 joins

I'm trying to update data in 2 distinct tables in the same query with the following code:
UPDATE (SELECT s.passNumb, a.hour, a.minute, p.number, s.situation
FROM passwords s
JOIN atend a ON s.passNumb = a.passNumb
JOIN points p ON a.number = p.number
WHERE s.passNumb = 1 AND
p.number = 1)
SET a.hour = TO_CHAR(SYSDATE, 'HH24'),
a.minute = TO_CHAR(SYSDATE, 'MI'),
s.situation = 'F';
But I'm getting this error: Cannot modify a column which maps to a non key-preserved table. What am I doing wrong?
A view with a join (or an inline view containing a join in your case) must meet the following conditions to be updatable:
https://docs.oracle.com/cd/B28359_01/server.111/b28286/statements_8004.htm
If you want a join view to be updatable, then all of the following
conditions must be true:
The DML statement must affect only one table underlying the join.
For an INSERT statement, the view must not be created WITH CHECK
OPTION, and all columns into which values are inserted must come from
a key-preserved table. A key-preserved table is one for which every
primary key or unique key value in the base table is also unique in
the join view.
For an UPDATE statement, the view must not be created WITH CHECK
OPTION, and all columns updated must be extracted from a key-preserved
table.
The first condition is rather obvious: The DML statement must affect only one table underlying the join.
But what does it mean: "key preserved table" ?
A key-preserved table is one for which every primary key or unique
key value in the base table is also unique in the join view.
The key preserved table means that each row from this table will appear at most once in the result of a view.
Consider a simple example:
CREATE TABLE users(
user_id int primary key,
user_name varchar(100),
age int
);
insert into users values(1,'Tom', 22);
CREATE TABLE emails(
user_id int,
email varchar(100)
);
Insert into emails values( 1, 'tom#somedomain.com' );
Insert into emails values( 1, 'tom#www.example.org' );
commit;
And a join:
SELECT *
FROM users u
JOIN emails e ON u.user_id = e.user_id;
USER_ID USER_NAME AGE USER_ID EMAIL
---------- --------------- ---------- ---------- --------------------
1 Tom 22 1 tom#somedomain.com
1 Tom 22 1 tom#www.example.org
If you look at a result of this join, it is apparent that:
user_id, user_name and age come from non-key preserved table
email comes from key-preserved table
And now: this update is acceptable, because it updates a key preserved column (table) in this join:
UPDATE (
SELECT * FROM users u
JOIN emails e ON u.user_id = e.user_id
)
SET email = email || '.it' ;
USER_ID USER_NAME AGE USER_ID EMAIL
---------- --------------- ---------- ---------- -------------------------
1 Tom 22 1 tom#somedomain.com.it
1 Tom 22 1 tom#www.example.org.it
But this update cannot be done, since it touches a column from non-key preserved table:
UPDATE (
SELECT * FROM users u
JOIN emails e ON u.user_id = e.user_id
)
SET age = age + 2;
SQL Error: ORA-01779: cannot modify a column which maps to a non key-preserved table
01779. 00000 - "cannot modify a column which maps to a non key-preserved table"
*Cause: An attempt was made to insert or update columns of a join view which
map to a non-key-preserved table.
*Action: Modify the underlying base tables directly.
If you think a while .... Tom appears 2 times in the result of the join (but there is only one Tom in the users table).
When we are trying to update age = age + 2 in this join, then what should be a result of this update ?
Should Tom be updated only once ?
Should Tom be 22+2 = 24 years old after this update ?
Or maybe Tom should be updated twice (since it appears twice in the result of the join) so it should be 22 + 2 + 2 = 26 years old.
Another example - please tell me what should be an outcome of this update?:
UPDATE ( ....our join ... ) SET age = length( email );
There are very difficult questions :)
And because of this Oracle prevents from updating non-key preserved tables.
The error message gives this hint:
*Action: Modify the underlying base tables directly.
This means that we must update this table directly, using a separate UPDATE command:
UPDATE users SET age = age + 2

Implementing Oracle Database Triggers

Below is the code snippet which I need to improve.
CREATE OR REPLACE T_CHANGE AFTER ---Trigger Created for After insert/update option
INSERT OR
UPDATE OF QTY ON ABC BEGIN
IF INSERTING THEN
UPDATE XYZ D SET FLAG_CHG = 1
WHERE EXISTS
(
SELECT 1 FROM XYZ D WHERE
:NEW.PRODUCT = D.PRODUCT AND
:NEW.LOCATION = D.LOCATION
);
IF UPDATING THEN
UPDATE XYZ D SET FLAG_CHG = 1
WHERE EXISTS
(
SELECT 1 FROM XYZ D WHERE
:OLD.PRODUCT = D.PRODUCT AND
:OLD.LOCATION = D.LOCATION `enter code here`
);
END IF
END T_CHANGE;
The two mentioned tables are as follow:
CREATE TABLE XYZ (
PRODUCT VARCHAR2(50),
LOCATION VArchar2(50),
FLAG_CHG BOOLEAN DEFAULT 0,
CONSTRAINT XYZ_PK PRIMARY KEY (PRODUCT,LOCATION)
)
CREATE TABLE ABC (
PRODUCT VARCHAR2(50),
LOCATION VArchar2(50),
QTY NUMBER,
CONSTRAINT ABC_PK PRIMARY KEY (PRODUCT,LOCATION)
)
What are you trying to achieve ?
1)If the QTY in ABC is updated or inserted the FLAG_CHG in XYZ should be updated to 1.
I have few queries on this
1.) Will the above code work? :P
2.) If works, will it have performance issues ?
3.) How can I enhance this code to improve the performance ?
4.) Please advice a better approach ,if any,to fulfill the requirement?
Thanks in Advance.
It won't work because SQL won't recognize what you are going to do there, I guess.
The better approch for it is to create some unique fields in your table, and then do query INSERT .... ON DUPLICATE KEY UPDATE ...
Reference
UPDATE on DUPLICATE KEY ORACLE

What is the correct order in which tables should be joined

I have three tables
TABLE 1
PLANS -> Has all the plans information
COLUMNS:
PLAN_ID
PLAN_NAME
OTHER_DETAILS
(PLAN_ID is the PRIMARY KEY)
TABLE 2
REGISTER ->
COLUMNS:
RUN_ID
PLAN_ID
REGISTER_DETAILS
(RUN_ID AND PLAN_ID) is the primary key
TABLE 3
ELECTION ->
Columns
RUN_ID
PLAN_ID
ELECTION_DETAILS
(RUN_ID AND PLAN_ID) is the primary key
PLAN_ID could be present in either REGISTER (or) ELECTION (or) BOTH tables.
For an input RUN_ID , I need to pick rows in the below format such that if a plan has only register details only REGISTER_DETAILS is picked.
If a plan has both REGISTER_DETAILS and ELECTION_DETAILS then both the details should get returned.
Report Format:
RUN_ID PLAN_ID REGISTER_DETAILS ELECTION_DETAILS
Solution
I tried by joining the tables in below format:
SELECT
..
FROM
PLANS A
LEFT JOIN REGISTER B
ON (A.PLAN_ID = B.PLAN_ID
AND B.RUN_ID = 'Input Run Id')
LEFT JOIN ELECTION C
ON (A.PLAN_ID = C.PLAN_ID
AND C.RUN_ID = 'Input Run Id')
But this is also returning plans that are not present in REGISTER and ELECTION tables.
Can someone please tell what is wrong with the query?
Add
WHERE B.PLAN_ID IS NOT NULL OR
C.PLAN_ID IS NOT NULL
to the end of your query.
Best of luck.

Delete Sets Same Table FK's to NULL

I have a table in SQL Server:
CREATE TABLE [dbo].[Account]
(
[AccountID] NVARCHAR (20) NOT NULL,
[ParentID] NVARCHAR (20) NULL
);
Also there is the same table FK ParentID-->AccountID
ParentID is either null or contains the parent node.
In SQL Server the refrential integrity works correctly: It doesn't allow deleting the parent record if a child record exists. In my Entity Framework model which I created from the database when I try to delete the parent row EF first sets the child's ParentIDs to NULL and then deletes the parent row:
Account acc = new Account();
acc = (Account)accountListBox.SelectedItem;
_context.DeleteObject(acc);
_context.SaveChanges();
This is obviously not what I would have expected. Is there something wrong with my model? How can I enforce the ref. integrity in this case?

Resources