Conditional unique constraint with multiple fields in oracle db - sql

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.

Related

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

Create a unique constraint with CHECK in PostgreSQL

Consider that there is the following table:
create table table_1(
id serial
PRIMARY KEY,
col1 varchar(50),
col2 varchar(50),
status varchar(1) -- A=active P=pending D=Deleted
);
now what I want is to create a unique constraint on (col1,col2) but it should not consider those with status ='D' i.e Consider there is 2 rows in the table:
INSERT INTO table_1(col1,col2,status) VALUES ('row1', 'row1', 'A');
INSERT INTO table_1(col1,col2,status) VALUES ('row2', 'row2', 'D');
Then I should NOT be able to add the following row:
INSERT INTO table_1(col1,col2,status) VALUES ('row1', 'row1', 'A');
But I SHOULD be able to add the following row:
INSERT INTO table_1(col1,col2,status) VALUES ('row2', 'row2', 'A');
You can do this with a partial unique index:
create unique index id_table1_col1_col2_status on table_1(col1, col2, status)
where status <> 'D';
Here is a SQL Fiddle that you can use to see it work.

PostgreSQL connections constraint

I have table with two columns, id1 and id2.
If i have for example foo-bar respectively in these columns,I need a constraint that forbids entry bar-foo.
Thanks!
CREATE TABLE mytable(
id1 integer,
id2 integer
);
CREATE UNIQUE INDEX ON mytable(least(id1, id2), greatest(id1, id2));
This should ddo the trick:
test=> INSERT INTO mytable VALUES (1, 2);
INSERT 0 1
test=> INSERT INTO mytable VALUES (1, 3);
INSERT 0 1
test=> INSERT INTO mytable VALUES (2, 1);
ERROR: duplicate key value violates unique constraint "mytable_least_greatest_idx"
DETAIL: Key ((LEAST(id1, id2)), (GREATEST(id1, id2)))=(1, 2) already exists.

Unique constraint on multiple columns - allow for single null

I found this: Unique constraint on multiple columns
SQL> CREATE TABLE t (id1 NUMBER, id2 NUMBER);
Table created
SQL> ALTER TABLE t ADD CONSTRAINT u_t UNIQUE (id1, id2);
Table altered
SQL> INSERT INTO t VALUES (1, NULL);
1 row inserted
SQL> INSERT INTO t VALUES (1, NULL);
INSERT INTO t VALUES (1, NULL)
ORA-00001: unique constraint (VNZ.U_T) violated
I want to create a constraint that allows to enter several (X,null) values, so that the constraint only kicks in when BOTH values that the constraint is about are not null. Is this possible?
Note that you can insert multiple (NULL,NULL), but not multiple (1,NULL). This is how indexes work in Oracle; when all columns are null, then there is no entry in the index.
So rather than building a normal index on (id1,id2) we must build a function index that makes both values null when at least one is null. We need deterministic functions for this. First DECODE to check for null. Then GREATEST, making use of it resulting in null when at least one value is null:
create unique index idx_t_unique on t
(
decode(greatest(id1,id2),null,null,id1),
decode(greatest(id1,id2),null,null,id2)
);
EDIT (after acceptance :-) I just see, you don't need deterministic functions, but can also use case constructs. Maybe that was always the case, maybe not, I don't know. However, you can also write the index as follows, if you find it more readable:
create unique index idx_t_unique on t
(
case when id1 is null or id2 is null then null else id1 end,
case when id1 is null or id2 is null then null else id2 end
);
You need a CHECK constraint in this case:
ALTER TABLE t ADD CONSTRAINT chk_t CHECK (id1 is null or id2 is null);
If you need a unique constraint behaviour you may try this:
drop table t1;
create table t1 (n number, m number);
create unique index t_inx on t1(case when n is null then null when m is null then null else n || '_' || m end);
insert into t1 values (1, null);
insert into t1 values (1, null);
insert into t1 values (null, 1);
insert into t1 values (null, 1);
insert into t1 values (1, 1);
insert into t1 values (1, 1);
insert into t1 values (1, 2);
A unique function based index

Oracle: function based index selective uniqueness

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