Uniqueness constraint on multiple columns causes issue with null values - oracle

According to oracle docs, a null cannot be equal or unequal to any value or to another null
This is evident in case of a uniqueness constraint on any column. But the behaviour is different if the uniqueness constraint in on multiple columns. eg:
CREATE TABLE table1 (
col1 NUMBER(2),
col2 NUMBER(2),
CONSTRAINT uniq_col1_col2 UNIQUE (col1, col2)
);
INSERT INTO table1 VALUES (1, NULL);
INSERT INTO table1 VALUES (1, NULL);
# ORA-00001: unique constraint (XYZ.UNIQ_COL1_COL2) violated
Why is this so? And how can I specify the constraint to ignore nulls?
EDIT
More specifically, if rows (null), (null) are unique, why are (1,null), (1,null) not unique? What is the rationale behind this?

That's what the documentation says (emphasis added):
To satisfy a unique constraint, no two rows in the table can have the same value for the unique key. However, the unique key made up of a single column can contain nulls. To satisfy a composite unique key, no two rows in the table or view can have the same combination of values in the key columns. Any row that contains nulls in all key columns automatically satisfies the constraint. However, two rows that contain nulls for one or more key columns and the same combination of values for the other key columns violate the constraint.
It's doing what it's supposed to do. With your two sample inserts, both (potential) rows contain null in one key column and the same value (1) in the other key column, so the constraint is violated.
Nothing else would really make sense; allowing both inserts to go ahead would leave you with two indistinguishable rows, in key terms anyway.
You asked:
More specifically, if rows (null), (null) are unique, why are (1,null), (1,null) not unique? What is the rationale behind this?
Because there is no other key column to enforce uniqueness.
As you said, null isn't equal to or not equal to anything. If your unique key was only on col1 and you had two rows with it set to null, which is allowed, then querying where col1 is null would find both - which is OK because is null isn't about equality. You can say that both rows matched the condition, but not that they are equal to null. With your two-column key the equivalent would be where col1 = 1 and col2 is null. Now equality does come into play.
In both cases the nulls are ignored, and whatever is left still has to be unique. With a single-column key there is nothing else to enforce uniqueness. With the two-column key if col2 is null then col1 is still there to be compared, and that on its own has to be unique, effectively.
You're also allowed to do:
INSERT INTO table1 VALUES (null, null);
INSERT INTO table1 VALUES (null, null);
The same thing applies; they nulls are effectively ignored, but now - as with the single-column-key - there is nothing left to enforce uniqueness against.

If you really want a constraint that ignores nulls and only prevents insert of complete duplicate keys then you can use a function-based unique index that only holds keys that are totally non-null:
create unique index uniq_col1_col2 on table1
( case when col1 is not null and col2 is not null then col1 end
, case when col1 is not null and col2 is not null then col2 end
);

Related

Oracle - Unique constraint while allowing null values

I'm a bit new to PL-SQL coming from T-SQL.
I have a requirement that only one phone number is allowed per user ID, but the phone number column can be null as many times as required.
So table is:
User ID
Phone Number
1
NULL
1
9735152122
1
NULL
2
NULL
3
NULL
1
2124821212
It's that last one I need to block, although the first three are fine. In this case I'm talking about the sample table I've posted, not the actual table order. I just need to allow the NULLs through but block if there are duplicate phone numbers per a given User ID.
I've read about functional indexes but not sure exactly how to apply them here.
CREATE UNIQUE INDEX my_index ON my_table (
CASE WHEN phone_number IS NULL THEN NULL ELSE user_id END,
phone_number
)
With this logic, if phone_number is NULL, then both values in the index will be NULL, so that row will be excluded from the index. If phone_number is not NULL, then the row will be included in the index with the actual values for user_id and phone_number, and uniqueness will be enforced.
P.S. This is not "PL/SQL", it is Oracle SQL. PL/SQL is the procedural language used to write such things as triggers, functions, etc.

Error on passing a Null into Oracle table's NOT NULLABLE column with DATA_DEFAULT set - Entity Framework

A column in Oracle DB - table has a NULLABLE = NO and DATA_DEFAULT = 'T'
Using EntifyFramework, When i'm trying to pass a NULL(this is a string property on the entity where NULL is a default value in it) into this field, i'm expecting it to insert a 'T' into this column but instead it is throwing an error.
"The COL_NAME field is required."
That is the expected behaviour. The default value is applied when that column is not referenced in the insert statement. When you do reference the column you are overriding the default - whether that is with null or some other not-null value.
From the documentation for insert:
If you omit one or more of the table's columns from this list, then the column value of that column for the inserted row is the column default value as specified when the table was created or last altered. If any omitted column has a NOT NULL constraint and no default value, then the database returns an error indicating that the constraint has been violated and rolls back the INSERT statement. Refer to CREATE TABLE for more information on default column values.
So you have to omit the column to pick up the default.
Default does not mean 'replace null with this'; it means 'use if not set at all'. Set to null is not the same as not set.
Bear in mind that if that wasn't the case, if you had a nullable column with a default value, it would be impossible to explicitly set it to null on insert.
However, you can override that, at least in later versions, as the create table documentation mentions:
DEFAULT
The DEFAULT clause lets you specify a value to be assigned to the column if a subsequent INSERT statement omits a value for the column.
...
ON NULL
If you specify the ON NULL clause, then Oracle Database assigns the DEFAULT column value when a subsequent INSERT statement attempts to assign a value that evaluates to NULL.
When you specify ON NULL, the NOT NULL constraint and NOT DEFERRABLE constraint state are implicitly specified. If you specify an inline constraint that conflicts with NOT NULL and NOT DEFERRABLE, then an error is raised.
Quick demo:
create table t42 (
id number,
col_a varchar2(1) default 'A' not null,
col_b varchar2 (1) default on null 'B'
);
Table T42 created.
insert into t42 (id) values (1);
1 row inserted.
-- this will fail - the scenario from the question
insert into t42 (id, col_a) values (2, null);
ORA-01400: cannot insert NULL into ("STACK"."T42"."COL_A")
insert into t42 (id, col_b) values (3, null);
1 row inserted.
insert into t42 (id, col_a, col_b) values (4, 'X', null);
1 row inserted.
insert into t42 (id, col_a, col_b) values (5, 'Y', 'Z');
1 row inserted.
select * from t42;
ID C C
---------- - -
1 A B
3 A B
4 X B
5 Y Z
The on null clause was added in 12cR1, so if you're still on an older version you won't be able to use it. But if your on a version that supports it, changing your table definition will allow your explicit null to do what you want.

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.

Partitioning a table refenceing other

I have a target table T1 which doesn't have a Date field. The current size is increasing rapidly. Hence I need to add a Date field and also perform table partitioning on this target table.
T1 has PRIMARY KEY (DOCID, LABID)
and has CONSTRAINT FOREIGN KEY (DOCID) REFERENCES T2
Table T2 is also complex table and has many rules in it.
T2 has PRIMARY KEY (DOCID)
My question is, as I need to partition T1. Is it possible NOT TO perform any step for T2 before T1 is partition? DBA told me that I need to partition T2 first before I touch T1??
There is no need to partition T2 before partitioning T1. Foreign key constraints do not care in the slightest bit about partitioning.
Best of luck.
You have – as proposed by others - two options. The first one is to add a redundant column DATE to the table T1 (child) and introduce the range partitioning on this column.
The second option is to use reference partitioning. Below is the simplified DDL for those options.
Range partitioning on child
create table T2_P2 /* parent */
(docid number not null,
trans_date date not null,
pad varchar2(100),
CONSTRAINT t2_p2_pk PRIMARY KEY(docid)
);
create table T1_P2 /* child */
(docid number not null,
labid number not null,
trans_date date not null, /** redundant column **/
pad varchar2(100),
CONSTRAINT t1_p2_pk PRIMARY KEY(docid, labid),
CONSTRAINT t1_p2_fk
FOREIGN KEY(docid) REFERENCES T2_P2(docid)
)
PARTITION BY RANGE(trans_date)
( PARTITION Q1_2016 VALUES LESS THAN (TO_DATE('01-APR-2016','DD-MON-YYYY'))
);
Reference partition
create table T2_RP /* parent */
(docid number not null,
trans_date date not null,
pad varchar2(100),
CONSTRAINT t2_rp_pk PRIMARY KEY(docid)
)
PARTITION BY RANGE(trans_date)
( PARTITION Q1_2016 VALUES LESS THAN (TO_DATE('01-APR-2016','DD-MON-YYYY'))
);
create table T1_RP /* child */
(docid number not null,
labid number not null,
pad varchar2(100),
CONSTRAINT t1_rp_pk PRIMARY KEY(docid, labid),
CONSTRAINT t1_rp_fk
FOREIGN KEY(docid) REFERENCES T2_RP(docid)
)
PARTITION BY REFERENCE(t1_rp_fk);
Your question is basically if the first option is possible, so the answer is YES.
To decide if the first option is preferable I’d suggest checking three criteria:
Migration
The first option requires a new DATE column in the child table that must be initialized during the migration (and of course correct maintained by the application).
Lifecycle
It could be that the lifecycle of both tables is the same (e.g. both parent and child records are kept for 7 year). In this case is preferable if both tables are partitioned (on the same key).
Access
For queries such as below you will profit from the reference partitioning (both tables are pruned - i.e. only the partitions with the accessed date are queried).
select * from T2_RP T2 join T1_RP T1 on t2.docid = t1.docid
where t2.trans_date = to_date('01012016','ddmmyyyy');
In the first option you will (probalbly) end with FTS on T2 and to get pruning on T1 you need to add predicate T2.trans_date = t1.trans_date
Having said that, I do not claim that the reference partition is superior. But I think it's worth to examine both options in your context and see which one is better.

How to add composite primary keys?

I have a table with three columns, [Id,QTY,Date]. out of these three, two columns [id and date], should be set as primary keys, because I need to fetch the record one by one, from this table, into a reference.
the data to be inserted into this table is
101,10,NULL
101,20,201220
101,7,201440
102,5,null
102,8,201352
date is in yyyyww format
How do I define two columns as composite primary keys when they have null values, duplicates?
alter table abc add constraint pk primary key (ID, DATE);
if I try to alter the table the error appears
Error report:
SQL Error: ORA-01449: column contains NULL values; cannot alter to NOT NULL
01449. 00000 - "column contains NULL values; cannot alter to NOT NULL"
*Cause:
*Action:
Using table level constraint, you can use this query
alter table your_table add constraint pkc_Name primary key (column1, column2)
but first you need to declare the columns NOT NULL. All parts of a primary key need to be NOT NULL.
The column name of your table is ID and it is still null and non-unique, how is it possible. If it is primary key of other table try adding a surrogate key column for this table and make it primary key.
In case of composite primary key, it should have atleast one not null value(For each row) in the combination of columns. And the combination of column must be unique at all case.
For further details check, http://docs.oracle.com/cd/B10500_01/server.920/a96524/c22integ.htm
Correction - If composite primary key is made up of 3 columns, then no column (among 3) can hold NULL value. And the combination of those 3 columns must be unique.
E.g. (1,2,2)
(1,2,1)
(2,2,1)
(1,2,2) - not valid

Resources