Creating a conditional index based on a column value - oracle

Table1
___________
User_ID
Location_ID
Type_ID
Level_ID
Levels
_________
1111 Manager
2222 Staff
There is an existing constraint on User_ID and Category_ID. I need to add a constraint to ensure that only one user
of level Manager can be added to a location for each type.
How do I go about creating a unique index on Location_ID, Type_ID and Level_ID where level_id=mypackage.get_level_id('Manager')?
I am trying something like that:
CREATE UNIQUE INDEX idx_mgr on Table1 (CASE WHEN Level_id=mypackage.get_level_id('Manager')
THEN (Location_ID,Type_ID)
ELSE NULL
END);
or
CREATE UNIQUE INDEX idx_mgr ON (DECODE(Level_ID,mypackage.get_level_id('Manager'),NULL);
How do I get something like this to work?

Defining a function index for the management level only (referencing teh constant directly - not via package) you get the required behaviour:
create unique index ix1 on table1
(case when level_id = 1111 then Location_ID end, case when level_id = 1111 then Type_ID end);
You can insert any number of stuff with the same location and type, but only one manager:
insert into table1(User_ID,Location_ID, Type_ID, Level_ID) values(1,1,1,2222);
insert into table1(User_ID,Location_ID, Type_ID, Level_ID) values(2,1,1,2222);
insert into table1(User_ID,Location_ID, Type_ID, Level_ID) values(3,1,1,1111);
insert into table1(User_ID,Location_ID, Type_ID, Level_ID) values(4,1,1,1111);
-- fails with ORA-00001: Unique Constraint violated

Related

How to insert different value in Oracle

Suppose I have a name column with UNIQUE Constraint on it and it has data ABC ,XYZ etc.And I want to insert more data.if the data are different then insert it.And take ABC as same as Abc,abc,abC etc.
if the word is present in the table and we want to insert same word with different case then it should throw an error that we cannot insert this data
You can use a unique function-based index to achieve this.
At the moment you have a unique constraint which is case-sensitive, e.g.:
create table your_table (name varchar2(30));
alter table your_table add constraint con_unique_name unique (name);
insert into your_table (name) values ('ABC');
insert into your_table (name) values ('XYZ');
commit;
That blocks an exact duplicate, but allows variations in case to be inserted:
insert into your_table (name) values ('ABC');
ORA-00001: unique constraint (STACK.CON_UNIQUE_NAME) violated
insert into your_table (name) values ('Abc');
1 row inserted.
insert into your_table (name) values ('abc');
1 row inserted.
rollback;
If you add a unique index that uses the upper-case version of the value (or lower-case; doesn't matter as long as it's consistent!) as well as, or instead of, your existing constraint then those would be blocked too:
create unique index idx_unique_name on your_table (upper(name));
insert into your_table (name) values ('ABC');
ORA-00001: unique constraint (STACK.CON_UNIQUE_NAME) violated
insert into your_table (name) values ('Abc');
ORA-00001: unique constraint (STACK.IDX_UNIQUE_NAME) violated
insert into your_table (name) values ('abc');
ORA-00001: unique constraint (STACK.IDX_UNIQUE_NAME) violated
Notice the reported constraint name is different for the first one - that is still hitting the original unique constraint, while the mixed-case ones are passing that constraint and then failing on the new index. If you dropped the original constraint then they would all fail on the new index.
You can do an insert-select, like
insert into yourtable(name)
select 'ABC'
from yourtable
group by ''
having count(*) > 0
where not exists (select 1 from yourtable yt where yt.name = yourtable.name);
(untested)
or you can wrap an if around the insert to see whether this name already exists.

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...)

SQL Foreign key to two different tables

I have a problem like
Table A:
-- TableBCId
Table B:
-- Id
Table C:
-- Id
I am looking for a way to create a foreign key table A where an entry can be either in table B or table C
Example entries:
Table A:
-- TableBCId: 1
-- TableBCId: 2
Table B:
-- Id: 1
Table C:
-- Id: 2
I want to avoid if possible:
- Two columns in table A
- Default values
- Additional tables
- Creation of an base entity is not possible
Every idea welcome
The normal way to implement this requirement is with 2 columns, 2 foreign key constraints, and a check constraint to ensure exactly of of the columns populated (if this is a requirement):
create table a
( ...
, b_id references b
, c_id references c
, constraint <name> check ( (b_id is null and c_id is not null)
or (b_id is not null and c_id is null)
)
);
You could, if it helps your UI, create a view over that table that combines B_ID and C_ID into a single column.
But you have said you don't want 2 columns, why is that?
The reason why this is hard is because the data model is wrong. A foreign key references only one table. A table can have more than one foreign key but each is separate. Apart from anything else, how would you know whether bc_id referenced b.id or c.id?
One explanation for this scenario is that table A should really be two tables, BA referencing B and CA referencing C. Alternatively A should reference a super-type table, of which B and C are sub-types. Without knowing the actual business domain it's hard to be sure.
Anyway, the path of least change is two columns.
You can use a insert/update trigger on your Table_A.
Maybe something like this:
CREATE TRIGGER Table_a_trgr
BEFORE INSERT OR UPDATE
on Table_a
FOR EACH ROW
DECLARE
c_row NUMBER;
BEGIN
SELECT count(*)
INTO c_row
FROM (
SELECT ID FROM table_b WHERE id = :NEW.TableBCId
UNION ALL
SELECT ID FROM table_c WHERE İd = :NEW.TableBCId
)
;
IF c_row < 2 THEN
raise_application_error(-20000, 'Error, Foreign Key');
END IF;
END;
/
If you don't want to make a new column in table A.
Then you can make a parent table to B and C.
tableBC:
id
And then create a 1-1 relation with tables B and C to table BC.
tableB:
id,
parent -- 1-1 foreign key to tableBC
tableC:
id,
parent -- 1-1 foreign key to tableBC
Now, in table A
tableA:
id,
TableBCId -- foreign key to tableBC
We have solved a similar problem using this approach.

Filtering child table in Db Adapter on Oracle Service Bus

I have a self-related table containing both active and historical data (field status holding 'A'(ctive) or 'H'(istorical) )
I need to create a service returning active records with all their active children.
I may add a condition to the main query but can not affect the "many" part of one-to-many relation: historical records are also retrieved. Is it possible to implement it without creating a pipeline looping through the service based on table with no relation? In pure eclipselink this may be achieved by utilizing DescriptorCustomizer, but I don't know whether this is valid solution for OSB.
Also I can not create a database view containing only active records.
BTW I'm on 12.2.1.1
Example table structure and data (for Oracle):
create table SELF_REL_TAB
(
ID number not null,
PARENT_ID number,
STATUS varchar2(1)
);
comment on column SELF_REL_TAB.ID
is 'Primary key';
comment on column SELF_REL_TAB.PARENT_ID
is 'Self reference';
comment on column SELF_REL_TAB.STATUS
is 'Status A(ctive) H(istorical)';
alter table SELF_REL_TAB
add constraint SRT_PK primary key (ID);
alter table SELF_REL_TAB
add constraint SRT_SRT_FK foreign key (PARENT_ID)
references SELF_REL_TAB (ID);
alter table SELF_REL_TAB
add constraint srt_status_chk
check (STATUS IN ('A','H'));
INSERT INTO SELF_REL_TAB VALUES (1, NULL, 'A');
INSERT INTO SELF_REL_TAB VALUES (2, 1, 'A');
INSERT INTO SELF_REL_TAB VALUES (3, 1, 'H');
Maybe you solved it, but you can use connect by clause to do that.
select
lpad(' ', 2*level) || id
from
self_rel_tab
where status = 'A'
start with
parent_id is null
connect by
prior id=parent_id
JP

Oracle Insert all data from another table contain duplicates how to avoid

have two tables A and B both same structure except B has one addition extra column inserting as "null". I need to Retain all data from A in B when I insert like below query it is inserting duplicate values because of that getting "primary Key violation error" when I try to create the "CONSTRAINT PK_Details_A PRIMARY KEY" Please help on this to avoid duplicate values while inserting the records.
Thanks in advance.
Insert into tableB(
id, effectiveDate, endDate
,startDate, Type, salary
,baseSalary, Amount, Amount1
,currency, Percentage, Salary
,Notional
)
select id, effectiveDate, endDate
,startDate, Type, salary
,baseSalary, Amount, Amount1
,currency, Percentage, Salary,null
from tableA;
EDIT
Primary key definition for B copied from comment below:
ALTER TABLE B
ADD CONSTRAINT PK_B
PRIMARY KEY ( oid)
USING INDEX ( CREATE UNIQUE INDEX PK_B ON B ( oid )

Resources