Oracle: function based index selective uniqueness - sql

I have to maintain history and so I am using is_deleted column which can have 'Y' or 'N'. But for any instance of is_deleted 'N' I should have uniwue entry for (a,b,c) composite columns.
When I am tryin to create function based unique index I am getting error.
CREATE UNIQUE INDEX fn_unique_idx ON table1 (CASE WHEN is_deleted='N' then (id, name, type) end);
ERROR at line 1:
ORA-00907: missing right parenthesis
Please help.
Thanks

You would need something like
CREATE UNIQUE INDEX fn_unique_idx
ON table1 (CASE WHEN is_deleted='N' THEN id ELSE null END,
CASE WHEN is_deleted='N' THEN name ELSE null END,
CASE WHEN is_deleted='N' THEN type ELSE null END);
An example of the constraint in action
SQL> create table table1 (
2 id number,
3 name varchar2(10),
4 type varchar2(10),
5 is_deleted varchar2(1)
6 );
Table created.
SQL> CREATE UNIQUE INDEX fn_unique_idx
2 ON table1 (CASE WHEN is_deleted='N' THEN id ELSE null END,
3 CASE WHEN is_deleted='N' THEN name ELSE null END,
4 CASE WHEN is_deleted='N' THEN type ELSE null END);
Index created.
SQL> insert into table1 values( 1, 'Foo', 'Bar', 'N' );
1 row created.
SQL> insert into table1 values( 1, 'Foo', 'Bar', 'Y' );
1 row created.
SQL> insert into table1 values( 1, 'Foo', 'Bar', 'Y' );
1 row created.
SQL> insert into table1 values( 1, 'Foo', 'Bar', 'N' );
insert into table1 values( 1, 'Foo', 'Bar', 'N' )
*
ERROR at line 1:
ORA-00001: unique constraint (SCOTT.FN_UNIQUE_IDX) violated
SQL> insert into table1 values( 1, 'Foo', 'Zee', 'N' );
1 row created.

CREATE UNIQUE INDEX fn_unique_idx ON table1
(CASE WHEN is_deleted='N' then (id||','|| name||','|| type) end);

Related

Oracle SQL create unique INDEX constraint based on status column and other 4 column

I need UNIQUE INDEX CONSTRAINT for below example:
CREATE TABLE DEMO(
COL_1 number,
COL_2 number,
COL_3 number,
COL_4 number,
STATUS number)
;
Insert into DEMO(COL_1,COL_2,COL_3,COL_4,STATUS) values (1,2,3,4,0); --Allow insert
Insert into DEMO(COL_1,COL_2,COL_3,COL_4,STATUS) values (1,2,3,4,1); --Allow insert
Insert into DEMO(COL_1,COL_2,COL_3,COL_4,STATUS) values (1,2,3,4,0); --Allow insert
Insert into DEMO(COL_1,COL_2,COL_3,COL_4,STATUS) values (1,2,3,4,1); --Not allow insert status 1 already exits!
This is the 12th pseudo constraint (based on constraint types) that you can impose on a database model, that doesn't require the use of the clause CONSTRAINT to define it.
You can create a partial unique index to enforce it. For example:
create unique index ix1 on demo (
case when status = 1 then col_1 end,
case when status = 1 then col_2 end,
case when status = 1 then col_3 end,
case when status = 1 then col_4 end,
case when status = 1 then status end
);
See running example at db<>fiddle.
You simply need a conditional index on the STATUS column. You can try below query -
CREATE UNIQUE INDEX UNQ_IDX_STATUS ON DEMO (CASE WHEN STATUS = 1 THEN STATUS ELSE NULL END);
This will only apply the constraint where the STATUS column will have the value as 1 else will have no impact on your other values in this column.
you could try this also, by creating a unique index which is a concenate of the columns.
CREATE TABLE DEMO(
COL_1 number(1),
COL_2 number(1),
COL_3 number(1),
COL_4 number(1),
STATUS number(1))
;
CREATE UNIQUE INDEX UQ_INDEX
ON DEMO(
( CASE STATUS WHEN 1 THEN COL_1 || COL_2 ||COL_3 ||COL_4 ||STATUS
ELSE
NULL
END));
Insert into DEMO(COL_1,COL_2,COL_3,COL_4,STATUS) values (1,2,3,4,0);
Insert into DEMO(COL_1,COL_2,COL_3,COL_4,STATUS) values (1,2,3,4,1);
Insert into DEMO(COL_1,COL_2,COL_3,COL_4,STATUS) values (1,2,3,4,0);
Insert into DEMO(COL_1,COL_2,COL_3,COL_4,STATUS) values (1,2,3,4,1);

SQL Constraint on more than one row

I have a table PROPERTY_RUNTIME having the columns of PROPERTY_NAME AND PROPERTY_VALUE
table PROPERTY_RUNTIME
I am adding a constraint to make the values of CNT_DISP_PREP1 and CNT_DISP_PREP2 not equal to 0 at the same time:
ALTER TABLE KLASSX.PROPERTY_RUNTIME
ADD (
CONSTRAINT CK_BOTH_LINE_CLOSE
CHECK (
(
CASE
WHEN
(
((PROPERTY_NAME = 'CNT_DISP_PREP1') AND (PROPERTY_VALUE = '1'))
OR
((PROPERTY_NAME = 'CNT_DISP_PREP2') AND (PROPERTY_VALUE = '1'))
)
THEN 1
ELSE 0
END
) = 1
)
DISABLE NOVALIDATE);
However, when I activate the constraint, and enter four combinations: 1/1, 1/0, 0/1, 0/0, all the four combinations violate the constraint, rather than that only 0/0 violates.
So I wonder if I have any logical mistake in the constraint?
Thanks in advance!
You can use a unique index to enforce that rule across multiples rows as follows:
FSITJA#db01> create table property_runtime (property_name varchar2(30), property_value varchar2(1));
Table created.
FSITJA#db01> create unique index ck_both_line_close on property_runtime (
2 case when property_name in ('CNT_DISP_PREP1', 'CNT_DISP_PREP2') and property_value = '0'
3 then 1 end)
4 ;
Index created.
FSITJA#db01> insert into property_runtime values ('CNT_DISP_PREP1', '1');
1 row created.
FSITJA#db01> insert into property_runtime values ('CNT_DISP_PREP2', '1');
1 row created.
FSITJA#db01> insert into property_runtime values ('CNT_DISP_PREP1', '0');
1 row created.
FSITJA#db01 2019-10-18 11:46:08> insert into property_runtime values ('CNT_DISP_PREP2', '0');
insert into property_runtime values ('CNT_DISP_PREP2', '0')
*
ERROR at line 1:
ORA-00001: unique constraint (FSITJA.CK_BOTH_LINE_CLOSE) violated
FSITJA#db01> insert into property_runtime values ('CNT_DISP_PREP1', '0');
insert into property_runtime values ('CNT_DISP_PREP1', '0')
*
ERROR at line 1:
ORA-00001: unique constraint (FSITJA.CK_BOTH_LINE_CLOSE) violated

Add multiple CHECK constraints on one column depending on the values of another column

Given a table with two columns col1 and col2, how can I use the Oracle CHECK constraint to ensure that what is allowed in col2 depends on the corresponding col1 value.
Specifically,
if col1 has A, then corresponding col2 value must be less than 50;
if col1 has B, then corresponding col2 value must be less than 100;
and if col1 has C, then corresponding col2 value must be less than 150.
Thanks for helping!
add check constraint using case statement
CREATE TABLE tbl
(
col1 varchar(10),
col2 numeric(4),
CONSTRAINT check_cols_ctsr
CHECK (CASE WHEN col1='A' THEN col2 ELSE 1 END <50 AND
CASE WHEN col1='B' THEN col2 ELSE 1 END <100 AND
CASE WHEN col1='C' THEN col2 ELSE 1 END <150)
);
You need to use a case statement, eg. something like:
create table test1 (col1 varchar2(2),
col2 number);
alter table test1 add constraint test1_chk check (col2 < case when col1 = 'A' then 50
when col1 = 'B' then 100
when col1 = 'C' then 150
else col2 + 1
end);
insert into test1 values ('A', 49);
insert into test1 values ('A', 50);
insert into test1 values ('B', 99);
insert into test1 values ('B', 100);
insert into test1 values ('C', 149);
insert into test1 values ('C', 150);
insert into test1 values ('D', 5000);
commit;
Output:
1 row created.
insert into test1 values ('A', 50)
Error at line 2
ORA-02290: check constraint (MY_USER.TEST1_CHK) violated
1 row created.
insert into test1 values ('B', 100)
Error at line 4
ORA-02290: check constraint (MY_USER.TEST1_CHK) violated
1 row created.
insert into test1 values ('C', 150)
Error at line 6
ORA-02290: check constraint (MY_USER.TEST1_CHK) violated
1 row created.
Commit complete.
CREATE TABLE MYDEPT (DEPT INT PRIMARY KEY CHECK(DEPT IN(10,20,30,40)),DEPTNAME VARCHAR(50) DEFAULT 'NOT GIVEN'
CHECK(DEPTNAME=UPPER(DEPTNAME)) UNIQUE NOT NULL, DEPTLOC VARCHAR(50) DEFAULT 'NOT GIVEN'
CONSTRAINT CHECK_DEPT CHECK (DEPTLOC IN ('BBSR','HYD','MUMBAI','PUNE')), CONSTRAINT CHECK_DEPT1 CHECK (DEPTLOC=UPPER(DEPTLOC)))
To add multiple check constraints in a single column, you have to define multiple user defined check constraints as shown above.

Change column type in table

I have a datatable called deal in Oracle with four columns:
DealID: (PK)
LegID
OrigID
Description
The problem is that if I want to insert a deal with description = A, the attributes LegID and OrigID must be unique, otherwise, there is not problem. How can i make this check? I had thought a trigger after insert. There are more solutions?
Thanks in advance!!
You need a function based unique index :
create table tt (
DealID number(10) primary key,
LegID number(10),
OrigID number(10),
Description varchar2(200 char)
);
create unique index tt_leg_orig_dscr_uk on tt (
case when description = 'A' then description end,
case when description = 'A' then legid end,
case when description = 'A' then origid end
);
insert into tt values (1, 1, 1, 'A');
1 row(s) inserted.
insert into tt values (2, 1, 1, 'A');
ORA-00001: unique constraint (XXXXX.TT_LEG_ORIG_DSCR_UK) violated
insert into tt values (2, 1, 2, 'A');
1 row(s) inserted.
select * from tt;
DEALID LEGID ORIGID DESCRIPTION
-----------------------------------
1 1 1 A
2 1 2 A
2 rows returned in 0.01 seconds
insert into tt values (3, 1, 1, 'B');
1 row(s) inserted.
insert into tt values (4, 1, 1, 'B');
1 row(s) inserted.
select * from tt order by 1;
DEALID LEGID ORIGID DESCRIPTION
-----------------------------------
1 1 1 A
2 1 2 A
3 1 1 B
4 1 1 B
4 rows returned in 0.01 seconds
As you can see, the unique index work only with records with description = 'A', It allows to have non-unique records for different descriptions.

Conditional unique constraint with multiple fields in oracle db

I have this table:
XPTO_TABLE (id, obj_x, date_x, type_x, status_x)
I wanna create a unique constraint that applies to the fields (obj_x, date_x, type_x) only when status_x <> 5.
I have tried to create this one but Oracle says:
line 1: ORA-00907: missing right parenthesis
CREATE UNIQUE INDEX UN_OBJ_DT_TYPE_STATUS
ON XPTO_TABLE(
(CASE
WHEN STATUS_X <> 5
THEN
(OBJ_X,
TO_CHAR (DATE_X, 'dd/MM/yyyy'),
TYPE_X)
ELSE
NULL
END));
What's the correct syntax ?
#jamesfrj: it looks like you are trying to ensure that your table should contain only one record for which status <>5.
You can try creating a unique functional index by concatenating the columns, as given below
create table XPTO_TABLE (id number,
obj_x varchar2(20),
date_x date,
type_x varchar2(20),
status_x varchar2(20)
);
create unique index xpto_table_idx1 on XPTO_TABLE(case when status_x <>'5' THEN obj_x||date_x||type_x||STATUS_x ELSE null END);
Hope it helps
Vishad
Under Oracle 11, you can create a bunch of virtual columns that get non-NULL value only when STATUS_X is 5, and then make them unique:
CREATE TABLE XPTO_TABLE (
ID INT PRIMARY KEY,
OBJ_X INT,
DATE_X DATE,
TYPE_X VARCHAR2(50),
STATUS_X INT,
OBJ_U AS (CASE STATUS_X WHEN 5 THEN OBJ_X ELSE NULL END),
DATE_U AS (CASE STATUS_X WHEN 5 THEN DATE_X ELSE NULL END),
TYPE_U AS (CASE STATUS_X WHEN 5 THEN TYPE_X ELSE NULL END),
UNIQUE (OBJ_U, DATE_U, TYPE_U)
);
You can freely insert duplicates, as long as STATUS_X is not 5:
INSERT INTO XPTO_TABLE (ID, OBJ_X, DATE_X, TYPE_X, STATUS_X) VALUES (1, 1, '1-JAN-2014', 'foo', 4);
INSERT INTO XPTO_TABLE (ID, OBJ_X, DATE_X, TYPE_X, STATUS_X) VALUES (2, 1, '1-JAN-2014', 'foo', 4);
But trying to insert a duplicate when STATUS_X is 5 fails:
INSERT INTO XPTO_TABLE (ID, OBJ_X, DATE_X, TYPE_X, STATUS_X) VALUES (3, 1, '1-JAN-2014', 'foo', 5);
INSERT INTO XPTO_TABLE (ID, OBJ_X, DATE_X, TYPE_X, STATUS_X) VALUES (4, 1, '1-JAN-2014', 'foo', 5);
Error report -
SQL Error: ORA-00001: unique constraint (IFSAPP.SYS_C00139498) violated
00001. 00000 - "unique constraint (%s.%s) violated"
*Cause: An UPDATE or INSERT statement attempted to insert a duplicate key.
For Trusted Oracle configured in DBMS MAC mode, you may see
this message if a duplicate entry exists at a different level.
*Action: Either remove the unique restriction or do not insert the key.
Because the CREATE UNIQUE INDEX is expecting only a value you can concatenate the columns as follow
CREATE UNIQUE INDEX UN_OBJ_DT_TYPE_STATUS
ON XPTO_TABLE(
(CASE
WHEN STATUS_X <> 5
THEN OBJ_X || TO_CHAR (DATE_X, 'dd/MM/yyyy') || TYPE_X
ELSE
NULL
END));
CREATE UNIQUE INDEX UN_MYID_uniq_IDX
ON MYTABLE(
(CASE
WHEN MY_id > 1428923
THEN MY_INDEX_COLUMN
ELSE
NULL
END));
MY_INDEX_COLUMN value cannot be repeated if my_id is greater than 1428923.