Skip to Main Content
  • Questions
  • Add Unique column using other column of the table

Breadcrumb

Question and Answer

Chris Saxon

Thanks for the question, RP.

Asked: July 24, 2017 - 7:58 am UTC

Last updated: July 25, 2017 - 2:27 pm UTC

Version: 12c

Viewed 1000+ times

You Asked

We have a table with 100 columns and number of records are around 1.3 million in it. Also having around 40 indexes created on the table.

Subset of the table is as below

create table t (
username varchar2(100),
DOJ date,
recid varchar2(100),
--uniqueUserName varchar2(200),
 CONSTRAINT pkt primary key  (recid)
);


insert into t(UserName,DOJ,RecID) values('abc',sysdate,'asdfghjklzxcbnm');
insert into t(UserName,DOJ,RecID) values('xyz',sysdate,'asdfghjklzx2bnm');
insert into t(UserName,DOJ,RecID) values('abc',sysdate,'asdfghjklzxcb3m');
insert into t(UserName,DOJ,RecID) values('abc',sysdate,'asdfghjklzxcbn4');

select * from t;



We have requirement, there should be unique column which should be based on UserName column of the table. if userName is unique then we in this unique column should be same as userName, but if its not unique then it can be appended with _add<running number> to make it unique. This is done using below SQL


create or replace view v_t as
SELECT t.* ,
  CASE
    WHEN ROW_NUMBER() OVER (PARTITION BY username ORDER BY DOJ DESC)=1
    THEN username
    WHEN ROW_NUMBER() OVER (PARTITION BY username ORDER BY DOJ DESC)>1
    THEN username
      ||'_add'
      ||to_char(ROW_NUMBER() OVER (PARTITION BY username ORDER BY DOJ DESC)-1)
  END AS uniqueUserName 
from t;

select * from v_t;


This View is quit critical for our application,as its heavily used and in most of the SQL it is joined with other table is based on UniqueUserName.
As uniqueUserName is derived using formula also base tabel is quite big all of the queries are running quit slow, wherever we are using this view and joining on UniqueUserName

As a alternate we converted view into materialized view, using same sql. also created all the indexes.
This has improved the performance of all the SQL, but refresh of the view data is a problem as we are using analytical function it can not be refreshed fast, it should be always complete refresh. This full refresh is taking long, almost 30 min. Also when MV refresh happens, all associated queries start performing slow. Also data in the MV is not real time.
As a alternate should we write trigger which will be populating the uniqueUserName column, which we can add in base table i.e. t.

Please recommend what’s the best solution for this scenario. which can be developed easily and easy to maintain.

and Chris said...

I feel like you're approaching this backwards. If you need unique usernames, why not stick a unique constraint on this column? Then force the user to pick a new name when you have collisions.

Using row_number as in the view is a terrible idea. If you ever delete rows from this table existing uniqueUserNames could change! In fact, they will with the current ordering of doj desc. Each new entry will become the original and all older rows will increment by one. I hate to think how this affects your joins...

If for some reason you must stick with your current design, instead of adding version number, could you append a date? I'm assuming DOJ = date of joining.

Using this you could create a virtual column which appends this in year/month/day/hour/min/sec format. For example:

create table t (
  username         varchar2(100),
  doj              date,
  recid            varchar2(100),
  uniqueusername   varchar2(200) as ( 
    username || '_' || to_char(doj,'yyyymmddhh24miss') 
  ),
  constraint pkt primary key ( recid ),
  unique ( uniqueusername )
);

insert into t(UserName,DOJ,RecID) values('abc',sysdate,'asdfghjklzxcbnm');
insert into t(UserName,DOJ,RecID) values('xyz',sysdate,'asdfghjklzx2bnm');
insert into t(UserName,DOJ,RecID) values('abc',sysdate,'asdfghjklzxcb3m');
insert into t(UserName,DOJ,RecID) values('abc',sysdate,'asdfghjklzxcbn4');

ORA-00001: unique constraint (CHRIS.SYS_C0017328) violated

select * from t;

USERNAME  DOJ                   RECID            UNIQUEUSERNAME      
abc       24-JUL-2017 02:55:40  asdfghjklzxcbnm  abc_20170724025540  
xyz       24-JUL-2017 02:55:41  asdfghjklzx2bnm  xyz_20170724025541  
abc       24-JUL-2017 02:55:41  asdfghjklzxcb3m  abc_20170724025541  


This only allows you to use a username once per second. So you'll need to check your existing data to see if this is a problem. And there's a low risk of collisions for new users. But this is easily solved by waiting one second to retry (or asking the user to generate a new name).

Whatever you do, I'd store the unique username in the table. That way you can have a unique constraint to guarantee each value appears at most once. And prevent the "on-the-fly" recalculation of the unique names...

Rating

  (2 ratings)

Is this answer out of date? If it is, please let us know via a Comment

Comments

RP, July 24, 2017 - 10:58 am UTC

This situation came because we are migrating form old application to new application, so not having option of putting unique constraint on the column while data is getting inserted in the table. Also in this table most of the time we have unique data in the username column, only 1 % time this column is having duplicate data.
Requirement is, UniqueUserName should be same as UserName, it can be be append with _add<running number> if data is duplicate.


Chris Saxon
July 24, 2017 - 3:13 pm UTC

"so not having option of putting unique constraint on the column while data is getting inserted in the table"

Why not?

You can always create an unvalidated unique constraint to stop new duplicates while you clean the existing data. See "Unvalidated constraints" in:

https://blogs.oracle.com/sql/how-to-find-and-delete-duplicate-rows-with-sql

RP, July 24, 2017 - 6:22 pm UTC

When i said "so not having option of putting unique constraint on the column while data is getting inserted in the table"
this application where user enter the data is not in our control, we are the consumer of the data. as per business process application should be allowed to accept the duplicate in username column. In new application unique column is recid thats the key used for joining with other table. But when it is coming to our oracle12c database, data should be unique in our unique column as all our existing reports are developed using uniqueUsarName column for joining the tables.
Chris Saxon
July 25, 2017 - 2:27 pm UTC

"as per business process application should be allowed to accept the duplicate in username column"

Sounds like a disaster waiting to happen to me.

But if that's what you're stuck with...

Can you add a virtual column concatenating the recid and username?

More to Explore

Design

New to good database design? Check out Chris Saxon's full fundamentals class.