5

For example, say I have something like

CREATE TABLE COMPANY(id int not null primary key, ...);

CREATE TABLE DEPARTMENT(id int not null primary key, company_id int not null,
 CONSTRAINT FK_DEP_COMPANY_ID FOREIGN KEY(company_id) REFERENCES COMPANY(id),...);

CREATE TABLE EMPLOYEE(id int not null primary key, department_id int not null, 
  username varchar(30) NOT NULL, ...,
 CONSTRAINT FK_EMPLOYEE_DEP_ID FOREIGN KEY(department_id) REFERENCES DEPARTMENT(id));

I have to implement uniqueness of EMPLOYEE.username within one company.

I see 2 ways.
1. Using AFTER statement level trigger on EMPLOYEE table (something similar to http://asktom.oracle.com/pls/asktom/ASKTOM.download_file?p_file=6551198119097816936 , case1)
2. Just adding company_id to EMPLOYEE

Neither of them looks ideal to me, and I wonder what is the proper way to implement uniqueness in such case.

Thanks.

a1ex07
  • 9,000
  • 3
  • 24
  • 40
  • 3
    I would go for 2.Just adding company_id to EMPLOYEE. Add company_id and create composite unique key for username,company_id. Why do you think it it is not ideal? – Atilla Ozgur Jan 27 '12 at 11:35
  • I think it adds extra path from user to company, so I have to ensure that company in users is the same as in departments… I believe the right way involves redesigning schema (and implementing of Party-Role-Relationship), but I cannot do that at the moment, so I 'm trying to find alternatives which require minimal changes to the model. – a1ex07 Jan 27 '12 at 14:06
  • 1
    i think in an entity relationship model departement is a "weak entity", the primary key of the departement is the a pair consisting of a company_id and another id-column disinguishing the different departements of a company. This pair is contained in the employee table. I think uniqueness constraint shall be implemented by unqiue keys therefore you need a table containing the company key and the employee name – miracle173 Jan 29 '12 at 16:40
  • Solution 2 is fine if you want to keep your solution simple (and avoid the indexed view). See this solution to a similar problem. – Nick Chammas Jan 30 '12 at 19:11
  • Perhap a unique function based index could be used. Here is an example: http://stackoverflow.com/questions/6702367/oracle-function-based-index-selective-uniqueness – Brian Jan 27 '12 at 02:07
  • Thanks for suggestion, but as far as I remember, function has to be deterministic – a1ex07 Jan 27 '12 at 04:05
  • except for the fact that the link contains the word 'uniqueness' you did not give any clue why a function based index should be of any use. – miracle173 Jan 29 '12 at 18:06
  • Without experimenting, I'd guess this makes the most sense. If the function spat out, for instance, the concatination of the username and the company ID (w/ or w/o a delimiter depending on how clean you want this to be, or even as a type instead) and the index specified uniqueness, then every row in the table would need to provide a unique output to that function. It's hardly the most elegant solution, but it seems to me it would work and would be worth further investigation. – Rob Jan 30 '12 at 01:05
  • @Rob: I might have misunderstood something, but this function needs to read value from parent table (department) to get company_id. Such a function is not deterministic by nature; however, function based index requires function to be deterministic. – a1ex07 Jan 30 '12 at 14:45
  • @Brian link only answers don't really help further the site. I just wanted to annotate why this was turned into a comment instead of an answer. – jcolebrand Jan 30 '12 at 20:19

2 Answers2

2

Mostly just for my own amusement, you can do this with a materialized view which has a unique index:

CREATE MATERIALIZED VIEW LOG ON COMPANY
WITH PRIMARY KEY, ROWID
INCLUDING NEW VALUES;

CREATE MATERIALIZED VIEW LOG ON DEPARTMENT
WITH PRIMARY KEY, ROWID (company_id)
INCLUDING NEW VALUES;

CREATE MATERIALIZED VIEW LOG ON EMPLOYEE
WITH PRIMARY KEY, ROWID (department_id, username)
INCLUDING NEW VALUES;

CREATE MATERIALIZED VIEW xcheck_mv
BUILD IMMEDIATE
REFRESH FAST ON COMMIT
AS SELECT c.id as company_id, lower(e.username) as username,
    c.rowid as c_rowid, d.rowid as d_rowid, e.rowid as e_rowid
from company c, department d, employee e
where d.company_id = c.id
and e.department_id = d.id;

CREATE UNIQUE INDEX xcheck_ind ON xcheck_mv(company_id, username);

Then attempting to insert an employee record for a different department with the same username gives a unique constraint violation, although not until you commit:

insert into company (id) values(1);

1 row created.

SQL> insert into department (id, company_id) values(1, 1);

1 row created.

SQL> insert into department (id, company_id) values(2, 1);

1 row created.

SQL> insert into employee (id, department_id, username) values(1,1,'Joe Bloggs');

1 row created.

SQL> insert into employee (id, department_id, username) values(2,2,'Joe Bloggs');

1 row created.

SQL> commit;
commit
*
ERROR at line 1:
ORA-12008: error in materialized view refresh path
ORA-00001: unique constraint (STACKOVERFLOW.XCHECK_IND) violated

Just for fun I put lower() in the MV definition to catch the most basic workaround - so trying to insert 'joe bloggs' also fails - but this model is never going to be very robust.

I'm not saying this is a good idea, just that it's possible...

Alex Poole
  • 2,575
  • 1
  • 18
  • 23
2

If you decide on Option 2, the Foreign Key Constraint should be changed. too:

CREATE TABLE EMPLOYEE(id int not null primary key, department_id int not null, 
  company_id not null,
  username varchar(30) NOT NULL, ...,
 CONSTRAINT FK_EMPLOYEE_DEP_ID FOREIGN KEY(department_id, company_id) 
     REFERENCES DEPARTMENT(id, company_id));

The department table may also need a Unique Key on (id, company_id) or changing the Primary Key:

CREATE TABLE DEPARTMENT(id int not null, company_id int not null,
 PRIMARY KEY (id, company_id),
 CONSTRAINT FK_DEP_COMPANY_ID FOREIGN KEY(company_id) REFERENCES COMPANY(id),...);
ypercubeᵀᴹ
  • 97,895
  • 13
  • 214
  • 305
  • Thanks for the answer; I know it will work, but I inclined to Alex Poole solution with materialized view. I realized that in addition to solving uniqueness problem , after some modifications it will also give me an option to query the view (at least for my typical queries), so it will serve 2 purposes... – a1ex07 Jan 31 '12 at 00:31