Unique constraint on any combination of two columns - sql

I am trying to implement a unique constraint for a combination of two columns. I am running oracle 11g.
Specifically I have two columns A and B.
I have a row like below
A B
1 2
Then I want the following combinations to fail when inserted
A B
1 2
2 1
Is this possible to achieve with a unique index in Oracle?

Yes, it is possible(for example using generated columns):
CREATE TABLE tab(A INT NOT NULL, B INT NOT NULL);
ALTER TABLE tab ADD c1 AS (LEAST(A,B));
ALTER TABLE tab ADD c2 AS (GREATEST(A,B));
CREATE UNIQUE INDEX UQ_tab ON tab(c1,c2);
You could hide these columns if needed(Oracle 12c):
ALTER TABLE tab MODIFY c1 INVISIBLE;
ALTER TABLE tab MODIFY c2 INVISIBLE;
DBFiddle Demo
EDIT:
Even simpler approach:
CREATE UNIQUE INDEX UQ_tab ON tab(least(A,B), greatest(A,B));
DBFiddle Demo

You can use a UNIQUE INDEX with LEAST, GREATEST and COALESCE functions:
CREATE TABLE table_name (
a INT,
b INT
);
CREATE UNIQUE INDEX table_name__a__b__u ON TABLE_NAME(
COALESCE( LEAST( a, b ), a, b ),
GREATEST( a, b )
);
You need to include COALESCE in case one of the values is NULL.
DBFiddle
INSERT INTO table_name ( a, b ) VALUES ( 1, 2 );
1 rows affected
INSERT INTO table_name ( a, b ) VALUES ( 3, NULL );
1 rows affected
INSERT INTO table_name ( a, b ) VALUES ( 2, 1 );
ORA-00001: unique constraint (SCHEMA_NAME.TABLE_NAME__A__B__U) violated
INSERT INTO table_name ( a, b ) VALUES ( NULL, 3 );
ORA-00001: unique constraint (SCHEMA_NAME.TABLE_NAME__A__B__U) violated

Related

Insert single column return value into multiple columns of another table

I have a db setup like below
create table children_A (
id serial primary key,
value text not null
);
create table children_B (
id serial primary key,
value text not null
);
create table parent_C (
id serial primary key,
child_A_id int not null,
child_B_id int not null
);
and I have an insert query like
with
children_A_insert as ( -- upsert in children_A and return id
insert into children_A(value)
values ('John')
on conflict (value)
do nothing
returning id
),
children_B_insert as ( -- upsert in children_B and return id
insert into children_B(value)
values ('Terry')
on conflict (value)
do nothing
returning id
)
-- insert into parentC(child_A_id, child_B_id) how to write this insert and select query ??
select children.id as id -- how can I trasnlate returning array of intergers into different columns for parent_C table
from (
select id from children_A_insert -- either get newly inserted id
union all
select id from children_A where value = 'John' -- or get the existing id from the children table
union all
select id from children_B_insert
union all
select id from children_B where value = 'Terry'
)
which essentially a query to insert into multiple tables in a single sql command.
result of the query is something like
id
---------
10
12
the problem is I want to take children.id as id and insert the returning ids into the parent table like
id
---------
10
12
should get trasnlated to
child_A_id | child_B_id
10 | 12
in the parent table. Unfortunately, id being a single column can not insert values in parent_C's multiple columns.
Is there a way to use children.id (single column) and insert the values to parent_C's multiple columns.
First of all you must add some constraint in children_A table and children_B table for upsert. Now to achieve the above insert you can try like below:
with
children_A_insert as (
insert into children_A(value)
values ('John')
on conflict (value)
do
update set value=EXCLUDED.value
returning id
),
children_B_insert as (
insert into children_B(value)
values ('Terry')
on conflict (value)
do
update set value=EXCLUDED.value
returning id
)
insert into parent_c (child_A_id,child_B_id)
values((select id from children_A_insert),
(select id from children_B_insert))
DEMO
NOTE: Above will work if you are inserting only one value in each table at a time

How to insert values in table if there is a primary key on id field in it?

I have trouble, I have table named my_table)
id name
1 A
2 B
3 C
and I want to insert top 2 so, values have autoincrement automaticaly like this
insert into my_table (name) values (A), (B);
Is this possible in postgressql?
May be I should have count() + 1, then count() + 2 here
insert into my_table (id, name) values (count(*) + 1, A), (count(*) + 2, B);
or something like this
because my id has constraint
BIGSEREIAL PRIMARY KEY NOT NULL
And I cant add values without getting the last id in table.
You can do:
create table my_table (
id int not null generated always as identity,
name varchar(10) not null
);
insert into my_table (name) values ('A'), ('B');
Then:
select * from my_table;
Result:
id name
-- ----
1 A
2 B
See running example at db<>fiddle.

Only allow certain values in column B based on the value of column A

I want to allow allow only a set number of values to be inserted into column A, and depending on the value entered, allow only a certain number of values to be inserted into column B. For example
For example
If A = 1, B can be between 1 and 9
If A = 2, B can be between 10 AND 19
If A = 3, B can be between 20 AND 29
How can I achieve this?
I figured check constraints are the best place to start. A simple constraint will ensure only values 1-3 can be added to column A. Such as:
CREATE TABLE dbo.test (
col_a INT,
col_b INT,
CONSTRAINT ch_col_a_valid_range CHECK (col_a BETWEEN 1 AND 3)
)
GO
Then I fugured that using a scalar function to determine whether the col_b is valid, by passing in the value from col_a and col_b.
CREATE FUNCTION dbo.value_is_valid (
#a INT,
#b INT
)
RETURNS BIT
AS
BEGIN
IF (#a = 1 AND #b BETWEEN 1 AND 9) RETURN 1;
IF (#a = 2 AND #b BETWEEN 10 AND 19) RETURN 1;
IF (#a = 3 AND #b BETWEEN 20 AND 29) RETURN 1;
RETURN 0;
END
GO
Then add the constraint to the table, and call the function as part of the check.
CREATE TABLE dbo.test (
col_a INT,
col_b INT,
CONSTRAINT ch_col_a_valid_range CHECK (col_a BETWEEN 1 AND 3),
CONSTRAINT ch_col_b_valid_based_on_a CHECK(dbo.value_is_valid(col_a, col_b) = 1)
)
GO
However, the following insert fails, complaining about a conflict with the ch_col_b_valid_based_on_a constraint that was added.
INSERT INTO dbo.test (
col_a,
col_b
)
VALUES (1, 9)
The INSERT statement conflicted with the CHECK constraint "ch_col_b_valid_based_on_a". The conflict occurred in database " MyDB", table "dbo.test".
What can I do to work around this and achieve the result mentioned above?
On looking back, this approach of using a scalar function in a check constraint works exactly as expected.
One method is a check constraint:
CREATE TABLE dbo.test (
col_a INT,
col_b INT,
CONSTRAINT ch_col_a_valid_range CHECK (col_a BETWEEN 1 AND 3),
CONSTRAINT chk_col_a_colb
CHECK ( (col_a = 1 AND col_b BETWEEN 1 AND 9) OR
(col_a = 2 AND col_b BETWEEN 10 AND 19) OR
(col_a = 3 AND col_b BETWEEN 20 AND 29)
)
);
However, I might be inclined to created an AB_valid table with a list of valid pairs and use a foreign key constraint. That way, the list of valid values could be maintained dynamically rather than requiring modification to the table definition.
You could use some math to create a simpler constraint.
CREATE TABLE dbo.test (
col_a INT,
col_b INT,
CONSTRAINT ch_col_a_valid_range CHECK (col_a BETWEEN 1 AND 3),
CONSTRAINT ch_col_b_valid_based_on_a CHECK(col_b/10 + 1 = col_a)
);

Total Sum of two columns and show in the third Column

I have a Three columns in the SQL table,
I want to show the sum of the total of two columns in the third column. How can I show that using SQL query.
This is my table structure
Id int Unchecked
Col_A int Unchecked
Col_B int Unchecked
Total int Checked
There is no need to store the total in the table, you can simply calculate it as part of your SQL query as follows:
SELECT
Id,
Col_A,
Col_B,
Col_A + Col_B AS Total
FROM tablename
You can use comuted column for this:
CREATE TABLE [dbo].[Test](
[a] [INT] NULL,
[b] [INT] NULL,
[c] AS ([a]+[b])
) ON [PRIMARY]
GO
INSERT INTO dbo.Test
( a, b )
VALUES ( 1, -- a - int
2 -- b - int
)
SELECT * FROM dbo.Test
Results:
a b c
1 2 3
Here is my table where the colums are salary and variable.
Now i need the sum of basic and variable as total.
Here is the query for that-
select basic,variable,(basic+variable) as total from salary
or if you just want to display total:
Try the following -
ALTER TABLE TABLENAME ADD colC AS ColA * ColB

How to enforce an "ALL-TO-ALL" relationship?

Suppose I have the following structure (SQL Server syntax):
CREATE TABLE A (
key_A int NOT NULL PRIMARY KEY CLUSTERED,
info_A nvarchar(50) NULL
);
CREATE TABLE B(
key_B int NOT NULL PRIMARY KEY CLUSTERED,
info_B nvarchar(50) NULL
);
CREATE TABLE C(
key_C int NOT NULL PRIMARY KEY CLUSTERED,
key_A int NOT NULL,
key_B int NOT NULL,
info1 nvarchar(50) NULL,
info2 nvarchar(50) NULL,
info3 nvarchar(50) NULL
);
ALTER TABLE C WITH CHECK ADD CONSTRAINT FK_C_A FOREIGN KEY(key_A) REFERENCES A (key_A);
ALTER TABLE C WITH CHECK ADD CONSTRAINT FK_C_B FOREIGN KEY(key_B) REFERENCES B (key_B);
So, table C has two primary keys to table A and table B.
Table C has to have the cartesian product of table A and table B. That means all combinations, so when a new record is inserted in table A, we have to insert in table C several rows with the new reference in A by all the rows in B. And viceversa, in the case of insertion in B.
The question is, how can you enforce the integrity of such relationship in SQL Server, in which table C has to have all combinations of A and B? Or, if you consider such a structure a bad practice, what alternative tables do you recommend that do not add the hassle of having to do DISTINCT selects and such?
Thanks!
Link to Fiddle:
Fiddle
You need to have inserted into table A / B before you can reference this new entry in table C. The only way I know of would be to create a trigger on tables A and B to populate table C when a new entry was made to either of those tables. The issue there is what do you then put into the other fields? Since these are nullable I assume you're happy defaulting them to null? If not (i.e. you want the user to input valid values) the only way to do this would be in the logic of your application, rather than at database level (or by using stored procedures to populate these tables where those procs had suitable logic to create the appropriate entries in C in addition to A / B).
Trigger Code Example:
use StackOverflowDemos
go
if OBJECT_ID('TRG_C_DELETE','TR') is not null drop trigger TRG_C_DELETE
if OBJECT_ID('TRG_A_INSERT','TR') is not null drop trigger TRG_A_INSERT
if OBJECT_ID('TRG_B_INSERT','TR') is not null drop trigger TRG_B_INSERT
if OBJECT_ID('C','U') is not null drop table C
if OBJECT_ID('A','U') is not null drop table A
if OBJECT_ID('B','U') is not null drop table B
go
CREATE TABLE A
(
key_A int NOT NULL IDENTITY(1,1) CONSTRAINT PK_A PRIMARY KEY CLUSTERED,
info_A nvarchar(50) NULL
);
go
CREATE TABLE B
(
key_B int NOT NULL IDENTITY(1,1) CONSTRAINT PK_B PRIMARY KEY CLUSTERED,
info_B nvarchar(50) NULL
);
go
CREATE TABLE C
(
key_C int NOT NULL IDENTITY(1,1) CONSTRAINT PK_C PRIMARY KEY CLUSTERED,
key_A int NOT NULL CONSTRAINT FK_C_A FOREIGN KEY(key_A) REFERENCES A (key_A),
key_B int NOT NULL CONSTRAINT FK_C_B FOREIGN KEY(key_B) REFERENCES B (key_B),
info1 nvarchar(50) NULL,
info2 nvarchar(50) NULL,
info3 nvarchar(50) NULL
);
go
CREATE TRIGGER TRG_A_INSERT
ON A
AFTER INSERT
AS
BEGIN
--add new As to C
insert C (key_A, key_B)
select key_A, key_B
from inserted
cross join B
END;
go
CREATE TRIGGER TRG_B_INSERT
ON B
AFTER INSERT
AS
BEGIN
--add new As to C
insert C (key_A, key_B)
select key_A, key_B
from inserted
cross join A
END;
go
CREATE TRIGGER TRG_C_DELETE
ON C
AFTER DELETE
AS
BEGIN
DELETE
FROM B
WHERE key_B IN
(
SELECT key_B
FROM DELETED d
--ths row onwards are here to cover the circumstance that the record being deleted isn't the only instance of B in this table
WHERE key_B NOT IN
(
SELECT key_B
FROM C
WHERE C.key_C NOT IN
(
SELECT key_C
FROM deleted
)
)
)
DELETE
FROM A
WHERE key_A IN
(
SELECT key_A
FROM DELETED d
--ths row onwards are here to cover the circumstance that the record being deleted isn't the only instance of A in this table
WHERE key_A NOT IN
(
SELECT key_A
FROM C
WHERE C.key_C NOT IN
(
SELECT key_C
FROM deleted
)
)
)
END;
go
insert A select 'X'
select * from C --no results as no Bs yet
insert A select 'Y'
select * from C --no results as no Bs yet
insert B select '1'
select * from C --2 results; (X,1) and (Y,1)
insert A select 'Z'
select * from C --3 results; the above and (Z,1)
delete from A where info_A = 'Y'
select * from C --3 results; as above since the previous statement should fail due to enforced referential integrity
insert C (key_A, key_B, info1)
select a.key_A, b.key_B, 'second entry for (Y,1)'
from A
cross join B
where a.info_A = 'Y'
and b.info_B = '1'
select * from C --4 results; as above but with a second (Y,1), this time with data in info1
delete from C where info1 = 'second entry for (Y,1)'
select * from C --3 results; as above but without the new(Y,1)
select * from A --3 results
select * from B --1 result
delete from C where key_A in (select key_A from A where info_A = 'Y')
select * from C --2 results; (X,1) and (Z,1)
select * from A --2 results; X and Z
select * from B --1 result
delete from C where key_B in (select key_B from B where info_B = '1')
select * from C --0 results
select * from A --0 results
select * from B --0 result
SQL Fiddle demo here (NB: only SQL Fiddle only shows the outputs from table C; also the error demo has been commented out since this errors the whole thing rather than just the one error line).
http://sqlfiddle.com/#!3/34d2f/4