How to constrain a database table so only one row can have a particular value in a column? - oracle

Using Oracle, if a column value can be 'YES' or 'NO' is it possible to constrain a table so that only one row can have a 'YES' value?
I would rather redesign the table structure but this is not possible.
[UDPATE] Sadly, null values are not allowed in this table.

Use a function-based index:
create unique index only_one_yes on mytable
(case when col='YES' then 'YES' end);
Oracle only indexes keys that are not completely null, and the CASE expression here ensures that all the 'NO' values are changed to nulls and so not indexed.

This is a kludgy hack, but if the column allows NULLs, then you could use NULL in place of "NO" and use "YES" just as before. Apply a unique key constraint to that column, and you'll never get two "YES" values, but still have many NOs.
Update: #Nick Pierpoint: suggested adding a check constraint so that the column values are restricted to just "YES" and NULL. The syntax is all worked out in his answer.

You will want to check a Tom Kyte article with exactly this question being asked and his answer:
http://tkyte.blogspot.com/2008/05/another-of-day.html
Summary: don't use triggers, don't use autonomous transactions, use two tables.
If you use an Oracle database, then you MUST get to know AskTom and get his books.

It doesn't work on the table definition.
However, if you update the table using a trigger calling a stored procedure, you could make sure that only one row contains "YES".
Set all rows to "NO"
Set the row you want to YES

Following on from my comment to a previous answer by yukondude, I'd add a unique index and a check constraint:
create table mytest (
yesorno varchar2(3 char)
);
create unique index uk_mytest_yesorno on mytest(yesorno);
alter table mytest add constraint ck_mytest_yesorno check (yesorno is null or yesorno = 'YES');

Does Oracle support something like filtered indices (last week I heard that e.g. MSSQL2008 does)? Maybe you can define a unique key which applies only to rows with the value "Yes" in your column.

I guess I'd use a second table to point to the appropriate row in your current table. That other table could be used to store values of other variables too too.

Related

how to create a unique constraint

I have a programming situation in which I have to check the uniqueness of a column of the table.
Say My table is employee table and I have added a column that is code. Now I have to prevent the insertion of MGR and mgr.
So that means I can not have 2 rows having case insensitive values.
How to add the check constraints ?
If you are using Oracle, you may add a unique index on the lowercase of your column.
create unique index <index_name> on <tablename>(lower(<column_name>))

How to make sure random key generated in trigger complies to unique constraint

Happy new year! :)
I have a table and upon insert I fill the column random_key with a random string using a trigger.
This column has a unique constraint, but theoretically it is possible that the random string the trigger generates is not unique, and the record will not be inserted.
Is there a way to avoid this in the trigger? Or do I have to create an insert procedure?
edit: (copied from comments and elaborated)
I have a normal id in this table, but for external use (the internet) I require a completely random id, so that the id of the next row in the table cannot be guessed.
Salting with the id + constant might work. I'll think it over.
Judging by the answers so far it appears there is no simple solution to avoid duplication in the trigger. That is an answer too. :)
you can use the SYS_GUID function. this will return a unique string.
SQL> select sys_guid() from dual;
SYS_GUID()
--------------------------------
D24FC8257402951BE0401AA2C9997E18

Function-based index when not using index access

I'm using a function-based index with a user-defined function for the first time, and have stumbled across a performance problem when the index can't be used.
Internally, a function-based index seems to generate a hidden table column (of type varchar2(4000), since my function returns a varchar2), and indexes that. That works fine when the index is used, but sometimes we have to do a full table scan using the function as a filter, and in that case I see a performance degradation by a factor of 6. Seems in that case, Oracle does not use the hidden column, but recomputes the function for each row, make the query CPU-bound instead of IO-bound.
Is there a way to make Oracle use that hidden column also for filtering? I wonder if I'm missing some rewrite options or something along those lines.
If not, I'll have to define the column myself and using a trigger to keep it up to date. I'd prefer using the function-based index for transparency and easier maintanance.
Which version of Oracle are you using? If it's 11g you should try using a virtual column. This is a column whose value is derived from an expression or a literal. They're defined as part of the table, so they have a visibility in a table DESC (unlike a function-based index). We can build indexes on virtual columns. And they are maintained automatically, without the need for a trigger.
So you can add a virtual column to your table using the same expression as your function based index. Perhaps like this:
create table t23
(id number
, col_a varchar2(10)
, vcol_a as (upper(substr(col_a, 1, 1)))
)
/
Note that we cannot insert or update a virtual column. So you need to specify the projection of the insert statement:
insert into t23 (id, col_a) values (1, 'this is a test');
Then you can build a regular index on the virtual column:
create index t23_vc_i on t23(vcol_a)
/
Don't forget to drop your function based index!
There is no general way a function-based index can be used in a table scan.
The assumption I made in my question, namely "Internally, a function-based index seems to generate a hidden table column ...", is simply wrong: the results of the function are not stored in a table column, but only in the index.
So, unless there is a way to access the index when performing the scan (the only way I can think of is if it's a combined index starting with the key column(s)), the precomputed function result can't be used.
The 11g "virtual column" feature does not help either, as the column is not stored in the table, but computed on-the-fly, similar to using the function in a view.
In summary: if you can't rule out table scans, and your function call is expensive (slow), use a real column in combination with a "before insert or update" trigger. A function-based index won't do.
(Note: Added this answer because I did not want to let this question stand as unanswered. The credit for the answer belongs to thilo, who pointed out that the column is never materialized).

Using JPA/Oracle can I have a unique constraint that ignores string case?

I have a db table with a column that is a String. I do not consider the case to be significant (e.g. "TEST == "test"). Unfortunately, it appears that JPA2 does, because both values are inserted into my table; I would like the second one to be rejected.
Is there a generic way to annotate an "ignore-case" unique constraint on a string column?
As an alternative, I could also consider putting a unique "ignore-case" constraint on the actual db column. Is that possible in Oracle 10?
What I don't want to do is write code, because this occurs often in this particular db.
All help is greatly appreciated.
you can achieve this with a function-based unique index
create unique index <index_name> on <table_name> (UPPER(<column_name>));
for Example
create table t111( col varchar2(10));
create unique index test_idx on t111 (UPPER(col));
insert into t111 values('test');
insert into t111 values ('TEST');

How to see contents of Check Constraint on Oracle

I did not create the database I'm working with, but I would like to see the details of a check constraint.
I know a check constraint on a column is enforcing a set of specific values, and I'd like to know what those values are. For example, if a check constraint is enforcing the character 'Y' and 'N', I want to be able to query the database and see that the accepted values are 'Y' and 'N.'
Is this possible to do through a query?
select constraint_name,search_condition
from all_constraints
where table_name='NAME_OF_YOUR_TABLE'
and constraint_type='C';
Will list the check and the constraint name of all check constraints on a specific table.
Don't forget that the columns in the all_constraints table are case-sensitive. If your select statement returns nothing, that may be why.
(If I had enough rep to comment, on DBA's answer, this would go there.)

Resources