Conditional Unique Constraint, Ignored for Same Foreign Key - sql

I have a situation where I need to maintain a unique constraint for two columns (FK_1, NAME) conditionally. The constraint should not apply for rows where a second foreign key is the same (FK_2).
ID FK_1 FK_2 NAME
1 2 3 'X01'
2 2 3 'X01-A'
3 2 3 'X01' --Accepted
4 2 4 'X01' --Violation
5 3 5 'X01' --Accepted
What I'm trying to accomplish is that rows 1, 2, and 3 are valid, because FK_2 is the same. They break the unique constraint, but the unique constraint is ignored because FK_2 is the same. Row 4 will violate the unique constraint for (FK_1,NAME) because FK_2 would have been new. And finally, row 5 is fine due to the normal function of a unique constraint on (FK_1,NAME).
It's trying to determine how to handle row 3 that is the complication.
I've seen several examples of how to address this same problem when the equivalent of FK_2 is hard-coded to some extent (Oracle: function based index selective uniqueness), but I'm not sure how I can address this for a foreign key, where I really don't have control over the value of FK_2.
I have tried implementing a function-based unique index, but it results in a ORA-04091 exception.
CREATE OR REPLACE FUNCTION UNQ_TEST_FUNCTION(var_fk_1 IN NUMBER, var_fk_2 IN NUMBER, var_name IN VARCHAR2) RETURN NUMBER DETERMINISTIC IS
result NUMBER;
BEGIN
result := null;
IF (var_fk_1 IS NULL OR var_fk_2 IS NULL OR var_name IS NULL ) THEN
result := null;
ELSE
SELECT COUNT(ID) INTO result FROM TEST1 WHERE
FK_2 != var_fk_2 AND FK_1 = var_fk_1 AND NAME = var_name;
IF (result <= 0) THEN
result := null;
ELSE
result := 1;
END IF;
END IF;
RETURN result;
END;
CREATE UNIQUE INDEX UNQ_TEST ON TEST1
(
UNQ_TEST_FUNCTION("FK_1","FK_2","NAME")
)
;
INSERT INTO TEST1 (ID, FK_1, FK_2, NAME)
VALUES (1, 2, 3, 'X01');
ORA-04091: table TEST1 is mutating, trigger/function may not see it
The exception makes perfect sense, but being unable to query the table puts me at a loss about where to go.

An interesting problem. I would approach this in the following way:
CREATE TABLE t123_fk(
FK_1 int,
FK_2 int,
CONSTRAINT t123_fk_pk PRIMARY KEY(FK_1, FK_2),
CONSTRAINT fk_2_is_new_constr_violated UNIQUE(FK_1)
);
CREATE TABLE t123(
ID int,
FK_1 int,
FK_2 int,
NAME varchar2(100),
constraint t123_fk FOREIGN KEY(FK_1, FK_2) REFERENCES t123_fk
);
CREATE OR REPLACE TRIGGER some_name
BEFORE INSERT OR UPDATE ON t123
FOR EACH ROW
BEGIN
INSERT INTO t123_fk( fk_1, fk_2 )
SELECT :new.FK_1, :new.FK_2 FROM dual
WHERE NOT EXISTS(
SELECT 1 FROM t123_fk
WHERE fk_1 = :new.fk_1 AND fk_2 = :new.fk_2
);
END;
/
Table t123 is the main table containing our data.
Table t123_fk and the trigger are auxiliary and they are used only to help us to force our constraint (btw, our constraint is named fk_2_is_new_constr_violated in the above code).
Here is a test - the fourth insert will be rejected by the database:
insert into t123( id, fk_1, fk_2, name) values(1,2,3,'X01');
insert into t123( id, fk_1, fk_2, name) values(2,2,3,'X01-A');
insert into t123( id, fk_1, fk_2, name) values(3,2,3,'X01');
insert into t123( id, fk_1, fk_2, name) values(4,2,4,'X01'); // this insert will be rejected
insert into t123( id, fk_1, fk_2, name) values(5,3,5,'X01');

Related

Create new records in one table-A and add as foreign key to table-B if the foreign key field in table-B is null -PostgresSQL

Table A and Table B look like shown below. The intention is to write an SQL script to run and update the tables. Table B has a foreign key to Table-A. If the field is null in Table B, then create a new record in Table-A and update Table B with that foreign key
It is expected to add two new records in Table A and add those foreign key in the Table B as per the above example. Thanks in advance
I wrote for you sample for do it.
But I don't know your business logic detail. You can change some solutions.
CREATE TABLE tablea (
id serial4 NOT NULL,
"name" varchar NULL,
CONSTRAINT tablea_pk PRIMARY KEY (id)
);
CREATE TABLE tableb (
id serial4 NOT NULL,
a_id int4 NULL,
"name" varchar NULL,
CONSTRAINT tableb_pk PRIMARY KEY (id)
);
INSERT INTO tableb (id, a_id, "name") VALUES(1, 100, 'b1');
INSERT INTO tableb (id, a_id, "name") VALUES(2, NULL, 'b2');
INSERT INTO tableb (id, a_id, "name") VALUES(3, NULL, 'b3');
INSERT INTO tableb (id, a_id, "name") VALUES(4, NULL, 'b4');
-- create function for inserting data into tablea and returning these id
CREATE OR REPLACE FUNCTION tablea_inserting()
RETURNS integer
LANGUAGE plpgsql
AS $function$
declare
ret int4;
begin
insert into tablea ("name") values ('test')
returning id into ret;
return ret;
end
$function$
;
-- After then you can update your tableb
update tableb set
a_id = tablea_inserting()
where a_id is null
select * from tableb;
Result:
id a_id name
1 100 b1
2 1 b2
3 2 b3
4 3 b4

How to declare a constraint to limit the number of holding foreign object?

If I have 2 table,
Owner(oid, name)
Dog(name, oid), where foreign key Dog (oid) references Owner (oid)
I want to know how to set a constraint so that Owner can only own at most 2 Dogs?
Here is an example of what you are looking for with a trigger which blocks insertion of a third dog for the same owner.
create table owner(
id int primary key,
name varchar(10) );
create table dog(
name varchar(10) primary key,
oid int ,
constraint fk_dog_owner foreign key
(oid) references owner(id) );
CREATE OR REPLACE TRIGGER dog_max_two
BEFORE INSERT ON dog
FOR EACH ROW
DECLARE
dogCount number(10);
BEGIN
Select count(name) INTO dogCount
From dog where oid = :NEW.oid;
IF( dogCount > 1 )
THEN
RAISE_APPLICATION_ERROR( -20001,
'maximum 2 dogs per person');
END IF;
END;
/
INSERT INTO owner VALUES (1,'Bill');
INSERT INTO dog VALUES ('Rover', 1);
INSERT INTO dog VALUES ('Boy', 1);
INSERT INTO dog VALUES ('Alfie', 1);
ORA-20001: maximum 2 dogs per person
ORA-06512: at "FIDDLE_HZTJGHLANPRDIFXWPMZS.DOG_MAX_TWO", line 8
ORA-04088: error during execution of trigger 'FIDDLE_HZTJGHLANPRDIFXWPMZS.DOG_MAX_TWO'
select * from dog;
NAME | OID
:---- | --:
Rover | 1
Boy | 1
db<>fiddle here

SQL Trigger Error ORA-04098

i have two tables named as childs and parent.
create table parent(
wallet_id NUMBER generated always as identity,
amount int,
customer_id int,
primary key(wallet_id),
Foreign key (customer_id) REFERENCES childs(customer_id)
);
create table childs(
customer_id number generated always as identity,
name varchar2 (255)
);
Now what i wanted to achieve was to get name from childs , and assign a wallet_id to childs in parent table as parent table has a foreign key to customer_id.
For this purpose i created a trigger.
create or replace
TRIGGER TRIGGER1
AFTER INSERT ON CHILDS
Declare
id int;
BEGIN
select MAX(customer_id) into id FROM childs;
insert into parent (customer_id ) values ( id );
END;
Now trigger is created but when i insert value in childs,
insert into childs(names) values ('jarral');
Now following error appears:
SQL Error: ORA-04098: trigger 'SYSTEM.TRG' is invalid and failed re-validation
04098. 00000 - "trigger '%s.%s' is invalid and failed re-validation"
*Cause: A trigger was attempted to be retrieved for execution and was
found to be invalid. This also means that compilation/authorization
failed for the trigger.
*Action: Options are to resolve the compilation/authorization errors,
disable the trigger, or drop the trigger.
How can i solve that?
I'm not entirely sure I follow your question, but is this what you are after ?
SQL> create table t1 ( id number generated as identity , x int);
Table created.
SQL> create table t2 ( id number generated as identity , t1_id int);
Table created.
SQL>
SQL> create or replace
2 trigger trg
3 after insert on t1
4 for each row
5 begin
6 insert into t2 (t1_id) values ( :new.id);
7 end;
8 /
Trigger created.
SQL> insert into t1 (x) values ( 0);
1 row created.
SQL> select * from t1;
ID X
---------- ----------
1 0
SQL> select * from t2;
ID T1_ID
---------- ----------
1 1

Check if ID of parent table's record is contained in just one of the child tables

To preface this, I have 3 tables, there is table
Product
- id
- name
- availability
Then is has 2 child tables:
Tables
- id
- product_id (foreign key to product(id))
- size
Chairs
- id
- product_id (foreign key to product(id))
- color
What I want to do is everytime I insert a new record into the chairs/tables table, I want to check whether it is not already contained in one of them. Or in other words, one product cannot be chair and table at once. How do I do this?
Thank you.
You could use a CHECK constraint for this, which, in combination with some other constraints, may give you the required behaviour. There are some restrictions on check constraints, one of them being:
The condition of a check constraint can refer to any column in the
table, but it cannot refer to columns of other tables.
( see the documentation )
In the following example, the ID columns "chairs.id" and "tables.id" have been moved into the "products" table, which contains the CHECK constraint. The UNIQUE constraints enforce a one-to-one relationship as it were ( allowing only one value for each of the REFERENCED ids ). The DDL code looks a bit busy, but here goes:
Tables
create table products (
id number generated always as identity primary key
, name varchar2(128)
, availability varchar2(32)
);
-- product_id removed
create table tables (
id number primary key
, size_ number
) ;
-- product_id removed
create table chairs (
id number primary key
, color varchar2(32)
);
Additional columns and constraints
alter table products
add (
table_id number unique
, chair_id number unique
, check (
( table_id is not null and chair_id is null )
or
( table_id is null and chair_id is not null )
)
);
alter table tables
add constraint fkey_table
foreign key ( id ) references products ( table_id ) ;
alter table chairs
add constraint fkey_chairs
foreign key ( id ) references products ( chair_id ) ;
Testing
-- {1} Add a chair: the chair_id must exist in PRODUCTS.
insert into chairs ( id, color ) values ( 1000, 'maroon' ) ;
-- ORA-02291: integrity constraint ... violated - parent key not found
-- Each chair needs an entry in PRODUCTS first:
insert into products ( name, availability, chair_id )
values ( 'this is a chair', 'in stock', 1000 ) ;
insert into chairs ( id, color ) values ( 1000, 'maroon' ) ;
-- okay
-- {2} We cannot add another chair that has the same chair_id. Good.
insert into products ( chair_id ) values ( 1000 ) ;
-- ORA-00001: unique constraint ... violated
-- {3} Add a table.
insert into products ( name, availability, table_id )
values ( 'this is a table', 'unavailable', 1000 ) ;
-- okay
insert into tables ( id, size_ ) values ( 1000, 60 ) ;
-- {4} Is it possible to add another table, with the same table_id? No. Good.
insert into tables ( id, size_ ) values ( 1000, 60 ) ;
-- ORA-00001: unique constraint ... violated
insert into products ( name, availability, table_id )
values ('this is a table', 'unavailable', 1000 ) ;
-- ORA-00001: unique constraint ... violated
-- {5} We cannot add something that is a chair _and_ a table (at the same time).
insert into products ( name, availability, table_id, chair_id )
values ( 'hybrid', 'awaiting delivery', 2000, 2000 ) ;
-- ORA-02290: check constraint ... violated
Resultset
SQL> select * from products;
ID NAME AVAILABILITY TABLE_ID CHAIR_ID
21 this is a chair in stock NULL 1000
23 this is a table unavailable 1000 NULL
NOTE: The product ID (NOT the table_id or the chair_id) "identifies" a particular table/chair. The values in the TABLE_ID and CHAIR_ID columns are only used to "link" to the (unique) IDs in the child table(s).
Alternative: object-relational approach.
This solution may be more suitable for solving the problem. Here, we create a supertype (product_t) first, and then 2 subtypes (chair_t and table_t, respectively). This allows us to create a table PRODUCTS_, using product_t for storing the data we need.
Types and table
create or replace type product_t as object (
name varchar2(64)
, availability varchar2(64)
)
not final;
/
create or replace type chair_t under product_t (
color varchar2(64)
)
/
create or replace type table_t under product_t (
size_ number
)
/
create table products_ (
id number generated always as identity primary key
, product product_t
);
Testing
-- "standard" INSERTs
begin
insert into products_ ( product )
values ( chair_t( 'this is a chair', 'in stock', 'maroon' ) );
insert into products_ ( product )
values ( table_t( 'this is a table', 'not available', 60 ) );
end;
/
-- unknown types cannot be inserted
insert into products_ ( product )
values ( unknown_t( 'type unknown!', 'not available', 999 ) );
-- ORA-00904: "UNKNOWN_T": invalid identifier
insert into products_ ( product )
values ( product_t( 'supertype', 'not available', 999 ) );
-- ORA-02315: incorrect number of arguments for default constructor
-- object of SUPERtype can be inserted
insert into products_ ( product )
values ( product_t( 'supertype', 'not available' ) );
-- 1 row inserted.
Query
select
id
, treat( product as table_t ).name as name_of_table
, treat( product as chair_t ).name as name_of_chair
, case
when treat( product as table_t ) is not null
then 'TABLE_T'
when treat( product as chair_t ) is not null
then 'CHAIR_T'
when treat( product as product_t ) is not null
then 'PRODUCT_T'
else
'TYPE unknown :-|'
end which_type_is_it
from products_ ;
-- result
ID NAME_OF_TABLE NAME_OF_CHAIR WHICH_TYPE_IS_IT
1 NULL this is a chair CHAIR_T
2 this is a table NULL TABLE_T
3 NULL NULL PRODUCT_T -- neither a chair nor a table ...

Unique constraint on multiple columns - allow for single null

I found this: Unique constraint on multiple columns
SQL> CREATE TABLE t (id1 NUMBER, id2 NUMBER);
Table created
SQL> ALTER TABLE t ADD CONSTRAINT u_t UNIQUE (id1, id2);
Table altered
SQL> INSERT INTO t VALUES (1, NULL);
1 row inserted
SQL> INSERT INTO t VALUES (1, NULL);
INSERT INTO t VALUES (1, NULL)
ORA-00001: unique constraint (VNZ.U_T) violated
I want to create a constraint that allows to enter several (X,null) values, so that the constraint only kicks in when BOTH values that the constraint is about are not null. Is this possible?
Note that you can insert multiple (NULL,NULL), but not multiple (1,NULL). This is how indexes work in Oracle; when all columns are null, then there is no entry in the index.
So rather than building a normal index on (id1,id2) we must build a function index that makes both values null when at least one is null. We need deterministic functions for this. First DECODE to check for null. Then GREATEST, making use of it resulting in null when at least one value is null:
create unique index idx_t_unique on t
(
decode(greatest(id1,id2),null,null,id1),
decode(greatest(id1,id2),null,null,id2)
);
EDIT (after acceptance :-) I just see, you don't need deterministic functions, but can also use case constructs. Maybe that was always the case, maybe not, I don't know. However, you can also write the index as follows, if you find it more readable:
create unique index idx_t_unique on t
(
case when id1 is null or id2 is null then null else id1 end,
case when id1 is null or id2 is null then null else id2 end
);
You need a CHECK constraint in this case:
ALTER TABLE t ADD CONSTRAINT chk_t CHECK (id1 is null or id2 is null);
If you need a unique constraint behaviour you may try this:
drop table t1;
create table t1 (n number, m number);
create unique index t_inx on t1(case when n is null then null when m is null then null else n || '_' || m end);
insert into t1 values (1, null);
insert into t1 values (1, null);
insert into t1 values (null, 1);
insert into t1 values (null, 1);
insert into t1 values (1, 1);
insert into t1 values (1, 1);
insert into t1 values (1, 2);
A unique function based index