How to create unique index on fields with possible null values (Oracle 11g)? - sql

Here is the sample table with 3 columns (ID, UNIQUE_VALUE, UNIQUE_GROUP_ID)
I want below records can be allowed:
(1, NULL, NULL)
(2, NULL, NULL)
or
(3, NULL, 7)
(4, 123, 7)
or (Note: this condition is not allowed in unique index nor unique constraint)
(5, NULL, 7)
(6, NULL, 7)
and these can't be allowed:
(7, 123, 7)
(8, 123, 7)
I created a unique index on last 2 columns, but only the first 2 examples can be allowed.
Is it possible to let db check the uniqueness of these 2 columns only when both are not null?

You want to only enforce uniqueness on the rows where both UNIQUE_VALUE and UNIQUE_GROUP_ID are not null. To do this, you can use a unique function-based index:
CREATE UNIQUE INDEX func_based_index ON the_table
(CASE WHEN unique_value IS NOT NULL
AND unique_group_id IS NOT NULL
THEN UNIQUE_VALUE || ',' || UNIQUE_GROUP_ID
END);

you can use the nvl function to avoid nulls and place a different value instead ,
create unique index func_idx on TEST_TABLE (nvl(UNIQUE_VALUE,1), UNIQUE_GROUP_ID);
the disadvantage is that your index will be larger and if you would like to search for null values you will have to use the nvl function in order to avoid table_access_full.
also all of the null values will be located under one branch in the index , so make sure your histograms are updated.
I Hope this will help you :)

Related

Postgresql tuple constraints about NOT NULL

I am building a database in POSTGRESQL, and I would like to create NOT NULL constraints for my columns, where one and only one column would be NOT NULL.
I have two columns in my table, site_id and buffer_result_id. Only one of these columns will have values.
alter table dt.analysis_result
add constraint ar_check check (site_id NOT NULL OR buffer_result_id NOT NULL);
The above code is just some pseudo-code to show my idea. How can I achieve this function?
You could use XOR expressed as:
alter table dt.analysis_result
add constraint ar_check check (
(site_id IS NOT NULL OR buffer_result_id IS NOT NULL)
AND NOT(site_id IS NOT NULL AND buffer_result_id IS NOT NULL)
);
db<>fiddle demo
More info: Exclusive OR - Equivalences
Demo:
CREATE TABLE analysis_result(site_id INT, buffer_result_id INT);
INSERT INTO analysis_result VALUES (NULL, NULL);
-- ERROR: new row for relation "analysis_result" violates check constraint "ar_check"
INSERT INTO analysis_result VALUES (1, 2);
-- ERROR: new row for relation "analysis_result" violates check constraint "ar_check"
INSERT INTO analysis_result VALUES (NULL, 2);
INSERT INTO analysis_result VALUES (1, NULL);
SELECT * FROM analysis_result
In Postgres, you can do this with a check constraint. I think the simplest method is to count the number of not null values:
alter table dt.analysis_result add constraint ar_check
check ( (site_id is not null)::int + (buffer_result_id is not null)::int = 1
);

Uniqe constrain on condition psql

I required to implement unique constrain on psql table
i have columns,
1) date
2) employee
3) client_id
4) start_time
trying to add two constrain like,
1) check unique rule for date, employee, start_time, client_id this will simply work with unique constrain
BUT SECOND CONSTRAIN Is for condition where we already create entry with date,employee,start_time and client_id is False
-> so if someone try to create same entry we require to check constrain like,
does any entry already exist with fields "date, employee_id,start_time" AND client_id= False
in SIMPLE words
1) check if all 4 fields exist with unique constrain > display warning record exist
2) check if record with 3 fields and client_id = null exist > display warning record exist assign client_id
if anybody have little hint
it would be helpful
I think you need partial indexes. One will cover case when client_id is provided and other will deal with NULL client_id.
create table uni(val1 int, val2 text, val3 date, client_id int);
create unique index record_exists on uni(val1, val2, val3, client_id)
where client_id is not null;
create unique index record_exists_assign_client_id on uni(val1, val2, val3)
where client_id is null;
insert into uni values (1, 'test', current_date, 43), (2, 'test2', current_date, null);
--OK
insert into uni values (1, 'test', current_date, 43);
--duplicate key value violates unique constraint "record_exists"
insert into uni values (1, 'test', current_date, null);
--OK
insert into uni values (2, 'test2', current_date, null);
--duplicate key value violates unique constraint "record_exists_assign_client_id"

can a unique constraint column have 2 or more null values? (oracle)

Is it possible to have 2 or more null values in unique constraint column?
Easy to check: (The answer is YES)
create table t1 (col1 number unique);
Table T1 created.
insert into t1 values (1);
1 row inserted.
insert into t1 values (null);
1 row inserted.
insert into t1 values (null);
1 row inserted.
select rownum, col1 from t1;
ROWNUM COL1
---------- ----------
1 1
2
3
3 rows selected.
Edit: While what I show above is the answer when only one column is involved in a unique constraint, one can also have composite unique keys (constraints defined at the table level, rather than column level - involving two or more columns). In that case, if say the unique key is on (col1, col2, col3), then (1, 1, 0) and (1, 1, 3) are not duplicates, because they aren't identical in every position. In this case, (1, 1, null) is allowed, but only once. The correct "generalization" of null "value" in a column, however, is for ALL values in ALL THREE columns to be null. In that regard, rows with "null values" in the unique key columns are still allowed any number of times.
That is: While (1, 1, null) is allowed, but not more than once, a row with values (null, null, null) in the three columns that make up the unique key are allowed any number of times - just like in the single-column case.

TSQL Multi Column Unique Constraint That Also Allows Multiple Nulls

I am currently doing some migration from MS Access to SQL Server. Access allows multiple Nulls in unique indexes where as SQL Server does not... I've been handling the migration by removing the indexes in SQL Server and adding filtered indexes:
CREATE UNIQUE NONCLUSTERED INDEX idx_col1_notnull
ON tblEmployee(col1)
WHERE col1 IS NOT NULL;
The problem I am having is that I am not sure how to implement a composite or multi-column "filtered" indexes... or if this is really possible as I've found no examples in researching it.
I do have an idea to implement it by creating filtered indexes like so:
CREATE UNIQUE NONCLUSTERED INDEX idx_col1col2_notnull
ON tblEmployee (col1, col2)
WHERE col1 IS NOT NULL
And then adding a second filtered index:
CREATE UNIQUE NONCLUSTERED INDEX idx_col2col1_notnull
ON tblEmployee (col1, col2)
WHERE col2 IS NOT NULL
But I'm not sure if this would even work let alone be the best method. Guidance in the right direction would be greatly appreciated.
You can add the following index to index only non nullable columns:
create table tblEmployee(col1 int, col2 int)
go
create unique nonclustered index idx_col1col2_notnull ON tblEmployee(col1,col2)
where col1 is not null and col2 is not null
go
--This Insert successeds
insert into tblEmployee values
(null, null),
(null, null),
(1, null),
(1, null),
(null, 2),
(null, 2)
--This Insert fails
insert into tblEmployee values
(3, 4),
(3, 4)
Also you can combine several indexes to keep unique resctrictions in all fields where a nullable field is null
CREATE TABLE MyTable (
[Idx_1] [int] NOT NULL,
[idx_2] [int] NOT NULL,
[idx_3] [int] NULL,
[no_index_field] [nvarchar](50) NULL
) ON [PRIMARY]
GO
CREATE UNIQUE NONCLUSTERED INDEX idx_3_fields
ON MyTable (Idx_1, Idx_2, Idx_3)
WHERE Idx_3 IS NOT NULL;
CREATE UNIQUE NONCLUSTERED INDEX idx_2_fields
ON MyTable (Idx_1, Idx_2)
WHERE Idx_3 IS NULL;

SQL can I have a "conditionally unique" constraint on a table?

I've had this come up a couple times in my career, and none of my local peers seems to be able to answer it. Say I have a table that has a "Description" field which is a candidate key, except that sometimes a user will stop halfway through the process. So for maybe 25% of the records this value is null, but for all that are not NULL, it must be unique.
Another example might be a table which must maintain multiple "versions" of a record, and a bit value indicates which one is the "active" one. So the "candidate key" is always populated, but there may be three versions that are identical (with 0 in the active bit) and only one that is active (1 in the active bit).
I have alternate methods to solve these problems (in the first case, enforce the rule code, either in the stored procedure or business layer, and in the second, populate an archive table with a trigger and UNION the tables when I need a history). I don't want alternatives (unless there are demonstrably better solutions), I'm just wondering if any flavor of SQL can express "conditional uniqueness" in this way. I'm using MS SQL, so if there's a way to do it in that, great. I'm mostly just academically interested in the problem.
If you are using SQL Server 2008 a Index filter would maybe your solution:
http://msdn.microsoft.com/en-us/library/ms188783.aspx
This is how I enforce a Unique Index with multiple NULL values
CREATE UNIQUE INDEX [IDX_Blah] ON [tblBlah] ([MyCol]) WHERE [MyCol] IS NOT NULL
In the case of descriptions which are not yet completed, I wouldn't have those in the same table as the finalized descriptions. The final table would then have a unique index or primary key on the description.
In the case of the active/inactive, again I might have separate tables as you did with an "archive" or "history" table, but another possible way to do it in MS SQL Server at least is through the use of an indexed view:
CREATE TABLE Test_Conditionally_Unique
(
my_id INT NOT NULL,
active BIT NOT NULL DEFAULT 0
)
GO
CREATE VIEW dbo.Test_Conditionally_Unique_View
WITH SCHEMABINDING
AS
SELECT
my_id
FROM
dbo.Test_Conditionally_Unique
WHERE
active = 1
GO
CREATE UNIQUE CLUSTERED INDEX IDX1 ON Test_Conditionally_Unique_View (my_id)
GO
INSERT INTO dbo.Test_Conditionally_Unique (my_id, active)
VALUES (1, 0)
INSERT INTO dbo.Test_Conditionally_Unique (my_id, active)
VALUES (1, 0)
INSERT INTO dbo.Test_Conditionally_Unique (my_id, active)
VALUES (1, 0)
INSERT INTO dbo.Test_Conditionally_Unique (my_id, active)
VALUES (1, 1)
INSERT INTO dbo.Test_Conditionally_Unique (my_id, active)
VALUES (2, 0)
INSERT INTO dbo.Test_Conditionally_Unique (my_id, active)
VALUES (2, 1)
INSERT INTO dbo.Test_Conditionally_Unique (my_id, active)
VALUES (2, 1) -- This insert will fail
You could use this same method for the NULL/Valued descriptions as well.
Thanks for the comments, the initial version of this answer was wrong.
Here's a trick using a computed column that effectively allows a nullable unique constraint in SQL Server:
create table NullAndUnique
(
id int identity,
name varchar(50),
uniqueName as case
when name is null then cast(id as varchar(51))
else name + '_' end,
unique(uniqueName)
)
insert into NullAndUnique default values
insert into NullAndUnique default values -- Works
insert into NullAndUnique default values -- not accidentally :)
insert into NullAndUnique (name) values ('Joel')
insert into NullAndUnique (name) values ('Joel') -- Boom!
It basically uses the id when the name is null. The + '_' is to avoid cases where name might be numeric, like 1, which could collide with the id.
I'm not entirely aware of your intended use or your tables, but you could try using a one to one relationship. Split out this "sometimes" unique column into a new table, create the UNIQUE index on that column in the new table and FK back to the original table using the original tables PK. Only have a row in this new table when the "unique" data is supposed to exist.
OLD tables:
TableA
ID pk
Col1 sometimes unique
Col...
NEW tables:
TableA
ID
Col...
TableB
ID PK, FK to TableA.ID
Col1 unique index
Oracle does. A fully null key is not indexed by a Btree in index in Oracle, and Oracle uses Btree indexes to enforce unique constraints.
Assuming one wished to version ID_COLUMN based on the ACTIVE_FLAG being set to 1:
CREATE UNIQUE INDEX idx_versioning_id ON mytable
(CASE active_flag WHEN 0 THEN NULL ELSE active_flag END,
CASE active_flag WHEN 0 THEN NULL ELSE id_column END);