I have a table foo that has only two fields: fooIdA and fooIdB (both of the same type). Those are composite primary keys, so:
primary key (fooIdA, fooIdB)...
Considering this, how can I make all permutations of the keys to be the same?
That is, (fooIdA, fooIdB) = (fooIdB, fooIdA).
Depending on your DBMS, you can create a unique index on an expression that prevents inserting (1,2) and (2,1)
In Postgres and Oracle you can do this:
create unique index unique_combinations
on the_table (least(fooida, fooidb), greatest(fooida, fooidb));
You don't specify your RDBMS. You can also add calculated fields with MAX, MIN values and add UNIQUE constraint on these calculated fields. Here is the MSSQL example:
CREATE TABLE ATest (id1 int, id2 int);
ALTER TABLE ATest ADD idMax AS (CASE WHEN id1>=id2 THEN id1 ELSE id2 END);
ALTER TABLE ATest ADD idMin AS (CASE WHEN id1>=id2 THEN id2 ELSE id1 END);
ALTER TABLE ATest ADD CONSTRAINT UniqueConstCalc UNIQUE(idMax,idMin);
insert into ATest values (1,1);
insert into ATest values (1,2);
insert into ATest values (2,1);
(1 row(s) affected)
(1 row(s) affected)
Msg 2627, Level 14, State 1, Line 3
Violation of UNIQUE KEY constraint 'UniqueConstCalc'.
Cannot insert duplicate key in object 'dbo.ATest'.
The duplicate key value is (2, 1).
The statement has been terminated.
Related
I need to ensure that the values in a column from a table are unique as part of a larger process.
I'm aware of the UNIQUE constraint, but I'm wondering if there is a better way to do the check.
I'm running the queries using psycopg2 so adding that tag on the off chance there's something in there that can help with this.
If the column is unique I can add a constraint. If the column is not unique adding the constraint will return an error.
If there is already a constraint of the same name a useful error is returned. in this case would prefer to just check for the existing constraint.
If the column is the primary key, the unique constraint can be added without error but in this case it would be preferable to just recognize that the column must be unique based on the primary key.
Code examples of this below.
DROP TABLE IF EXISTS unique_test;
CREATE TABLE unique_test (
pkey INT PRIMARY KEY,
unique_yes CHAR(1),
unique_no CHAR(1)
);
INSERT INTO unique_test (pkey, unique_yes, unique_no)
VALUES(1, 'a', 'a'),
(2, 'b', 'a');
CREATE UNIQUE INDEX CONCURRENTLY u_test_1 ON unique_test (unique_yes);
ALTER TABLE unique_test
ADD CONSTRAINT unique_target_1
UNIQUE USING INDEX u_test_1;
-- the above runs no problem
-- check what happens when column is not unique
CREATE UNIQUE INDEX CONCURRENTLY u_test_2 ON unique_test (unique_no);
ALTER TABLE unique_test
ADD CONSTRAINT unique_target_2
UNIQUE USING INDEX u_test_2;
-- returns:
-- SQL Error [23505]: ERROR: could not create unique index "u_test_2"
-- Detail: Key (unique_no)=(a) is duplicated.
CREATE UNIQUE INDEX CONCURRENTLY u_test_1 ON unique_test (unique_yes);
ALTER TABLE unique_test
ADD CONSTRAINT unique_target_1
UNIQUE USING INDEX u_test_1;
-- returns
-- SQL Error [42P07]: ERROR: relation "unique_target_1" already exists
-- test what happens if adding constrint to primary key column
CREATE UNIQUE INDEX CONCURRENTLY u_test_pkey ON unique_test (pkey);
ALTER TABLE unique_test
ADD CONSTRAINT unique_target_pkey
UNIQUE USING INDEX u_test_pkey;
-- this runs no problem but is inefficient.
If all you want to do is verify that values are unique, then use a query:
select unique_no, count(*)
from unique_test
group by unique_no
having count(*) > 1;
If it needs to be boolean output:
select not exists (
select unique_no, count(*)
from unique_test
group by unique_no
having count(*) > 1
);
If you just want a flag, you can use:
select count(*) <> count(distinct uniq_no) as duplicate_flag
from unique_test;
DELETE FROM
zoo x
USING zoo y
WHERE
x.animal_id < y.animal_id
AND x.animal = y.animal;
I think this is simpler, https://kb.objectrocket.com/postgresql/delete-duplicate-rows-in-postgresql-762 for reference
I am building a database in POSTGRESQL, and I would like to create NOT NULL constraints for my columns, where one and only one column would be NOT NULL.
I have two columns in my table, site_id and buffer_result_id. Only one of these columns will have values.
alter table dt.analysis_result
add constraint ar_check check (site_id NOT NULL OR buffer_result_id NOT NULL);
The above code is just some pseudo-code to show my idea. How can I achieve this function?
You could use XOR expressed as:
alter table dt.analysis_result
add constraint ar_check check (
(site_id IS NOT NULL OR buffer_result_id IS NOT NULL)
AND NOT(site_id IS NOT NULL AND buffer_result_id IS NOT NULL)
);
db<>fiddle demo
More info: Exclusive OR - Equivalences
Demo:
CREATE TABLE analysis_result(site_id INT, buffer_result_id INT);
INSERT INTO analysis_result VALUES (NULL, NULL);
-- ERROR: new row for relation "analysis_result" violates check constraint "ar_check"
INSERT INTO analysis_result VALUES (1, 2);
-- ERROR: new row for relation "analysis_result" violates check constraint "ar_check"
INSERT INTO analysis_result VALUES (NULL, 2);
INSERT INTO analysis_result VALUES (1, NULL);
SELECT * FROM analysis_result
In Postgres, you can do this with a check constraint. I think the simplest method is to count the number of not null values:
alter table dt.analysis_result add constraint ar_check
check ( (site_id is not null)::int + (buffer_result_id is not null)::int = 1
);
H2 (started with MODE=MYSQL on) supports INSERT ON DUPLICATE KEY UPDATE statement only with VALUES clause, while throws a "Unique index or primary key violation" error when using INSERT SELECT statement.
Here is an example:
-- creating a simple table
CREATE TABLE test_table1 (
id INT NOT NULL,
value VARCHAR(255) NOT NULL,
PRIMARY KEY (id))
ENGINE = InnoDB;
-- inserting a value
INSERT INTO test_table1
VALUES (1, 'test1');
-- trying to insert on duplicate key update: it works!
INSERT INTO test_table1
VALUES (1, 'test2')
ON DUPLICATE KEY UPDATE value='test2';
-- trying using INSERT SELECT: it throws Unique index or primary key violation: "PRIMARY KEY ON PUBLIC.TEST_TABLE1(ID)"
INSERT INTO test_table1
SELECT 1, 'test2'
FROM test_table1
ON DUPLICATE KEY UPDATE value='test2';
I'm using H2 db version 1.4.192.
Is it a bug? Or is there something wrong with my code?
Thank you
On you H2 console, if you have 'HIBERNATE_SEQUENCES' table make sure to check what is the NEXT_VAL for SEQUENCE_NAME = 'default'.
In my case, I had 2 row (insert statement) in my /src/main/resources/data.sql and the NEXT_VAL was 2 which was causing problems. I changed to 3 with update statement, and it now works fine.
Is there something wrong with my code?
Yes, there is. Why are you inserting into an auto-increment column? You should be specifying the columns with non-autogenerated data. So:
INSERT INTO test_table1(value)
VALUES ('test1');
And:
INSERT INTO test_table1(value)
SELECT 'test2'
FROM test_table1
ON DUPLICATE KEY UPDATE value = VALUES(value);
Your are getting the error because ON DUPLICATE KEY resets value, but that has nothing to do with the primary key on the table.
I have a data structure, where I have to store pairs of elements. Each pair has exactly 2 values in it, so we are employing a table, with the fields(leftvalue, rightvalue....).
These pairs should be unique, and they are considered the same, if the keys are changed.
Example: (Fruit, Apple) is the same as (Apple, Fruit).
If it is possible in an efficient way, I would put a database constraint on the fields, but not at any cost - performance is more important.
We are using MSSQL server 2008 currently, but an update is possible.
Is there an efficient way of achieving this?
Two solutions, both really about changing the problem into an easier one. I'd usually prefer the T1 solution if forcing a change on consumers is acceptable:
create table dbo.T1 (
Lft int not null,
Rgt int not null,
constraint CK_T1 CHECK (Lft < Rgt),
constraint UQ_T1 UNIQUE (Lft,Rgt)
)
go
create table dbo.T2 (
Lft int not null,
Rgt int not null
)
go
create view dbo.T2_DRI
with schemabinding
as
select
CASE WHEN Lft<Rgt THEN Lft ELSE Rgt END as Lft,
CASE WHEN Lft<Rgt THEN Rgt ELSE Lft END as Rgt
from dbo.T2
go
create unique clustered index IX_T2_DRI on dbo.T2_DRI(Lft,Rgt)
go
In both cases, neither T1 nor T2 can contain duplicate values in the Lft,Rgt pairs.
If you always store the values in order but store the direction in another column,
CREATE TABLE [Pairs]
(
[A] NVarChar(MAX) NOT NULL,
[B] NVarChar(MAX) NOT NULL,
[DirectionAB] Bit NOT NULL,
CONSTRAINT [PK_Pairs] PRIMARY KEY ([A],[B])
)
You can acheive exaclty what you want with one clustered index, and optimize your lookups too.
So when I insert the pair 'Apple', 'Fruit' I'd do,
INSERT [Pairs] VALUES ('Apple', 'Friut', 1);
Nice and easy. Then I insert 'Fruit', 'Apple',
INSERT [Pairs] VALUES ('Apple', 'Fruit', 0); -- 0 becuase order is reversed.
The insert fails because this is a primary key violation. To further illustrate, the pair 'Coconuts', 'Bananas' would be stored as
INSERT [Pairs] VALUES ('Bananas', 'Coconuts', 0);
For additional lookup performance, I'd add the index
CREATE NONCLUSTERED INDEX [IX_Pairs_Reverse] ON [Pairs] ([B], [A]);
If you can't control inserts to the table, it may be necessary to ensure that [A] and [B] are inserted correctly.
CONSTRAINT [CK_Pairs_ALessThanB] CHECK ([A] < [B])
But this may be an unnecessary performance hit, depending on how controlled your inserts are.
One way would be to create a computed column that combines the two values and put a unique constraint upon it:
create table #test (
a varchar(10) not null,
b varchar(10) not null,
both as case when a > b then a + ':' + b else b + ':' + a end persisted unique nonclustered
)
so
insert #test
select 'apple', 'fruit'
insert #test
select 'fruit', 'apple'
Gives
(1 row(s) affected)
Msg 2627, Level 14, State 1, Line 3
Violation of UNIQUE KEY constraint 'UQ__#test_____55252CB631EC6D26'. Cannot insert duplicate key in object 'dbo.#test'.
The statement has been terminated.
Unique constraint on two/more fields is possible but on their opposite no...
SQL Server 2005 Unique constraint on two columns
Unique constraint on multiple columns
How do I apply unique constraint on two columns SQL Server?
http://www.w3schools.com/sql/sql_unique.asp
I have a situation where i need to enforce a unique constraint on a set of columns, but only for one value of a column.
So for example I have a table like Table(ID, Name, RecordStatus).
RecordStatus can only have a value 1 or 2 (active or deleted), and I want to create a unique constraint on (ID, RecordStatus) only when RecordStatus = 1, since I don't care if there are multiple deleted records with the same ID.
Apart from writing triggers, can I do that?
I am using SQL Server 2005.
Behold, the filtered index. From the documentation (emphasis mine):
A filtered index is an optimized nonclustered index especially suited to cover queries that select from a well-defined subset of data. It uses a filter predicate to index a portion of rows in the table. A well-designed filtered index can improve query performance as well as reduce index maintenance and storage costs compared with full-table indexes.
And here's an example combining a unique index with a filter predicate:
create unique index MyIndex
on MyTable(ID)
where RecordStatus = 1;
This essentially enforces uniqueness of ID when RecordStatus is 1.
Following the creation of that index, a uniqueness violation will raise an arror:
Msg 2601, Level 14, State 1, Line 13
Cannot insert duplicate key row in object 'dbo.MyTable' with unique index 'MyIndex'. The duplicate key value is (9999).
Note: the filtered index was introduced in SQL Server 2008. For earlier versions of SQL Server, please see this answer.
Add a check constraint like this. The difference is, you'll return false if Status = 1 and Count > 0.
http://msdn.microsoft.com/en-us/library/ms188258.aspx
CREATE TABLE CheckConstraint
(
Id TINYINT,
Name VARCHAR(50),
RecordStatus TINYINT
)
GO
CREATE FUNCTION CheckActiveCount(
#Id INT
) RETURNS INT AS BEGIN
DECLARE #ret INT;
SELECT #ret = COUNT(*) FROM CheckConstraint WHERE Id = #Id AND RecordStatus = 1;
RETURN #ret;
END;
GO
ALTER TABLE CheckConstraint
ADD CONSTRAINT CheckActiveCountConstraint CHECK (NOT (dbo.CheckActiveCount(Id) > 1 AND RecordStatus = 1));
INSERT INTO CheckConstraint VALUES (1, 'No Problems', 2);
INSERT INTO CheckConstraint VALUES (1, 'No Problems', 2);
INSERT INTO CheckConstraint VALUES (1, 'No Problems', 2);
INSERT INTO CheckConstraint VALUES (1, 'No Problems', 1);
INSERT INTO CheckConstraint VALUES (2, 'Oh no!', 1);
INSERT INTO CheckConstraint VALUES (2, 'Oh no!', 2);
-- Msg 547, Level 16, State 0, Line 14
-- The INSERT statement conflicted with the CHECK constraint "CheckActiveCountConstraint". The conflict occurred in database "TestSchema", table "dbo.CheckConstraint".
INSERT INTO CheckConstraint VALUES (2, 'Oh no!', 1);
SELECT * FROM CheckConstraint;
-- Id Name RecordStatus
-- ---- ------------ ------------
-- 1 No Problems 2
-- 1 No Problems 2
-- 1 No Problems 2
-- 1 No Problems 1
-- 2 Oh no! 1
-- 2 Oh no! 2
ALTER TABLE CheckConstraint
DROP CONSTRAINT CheckActiveCountConstraint;
DROP FUNCTION CheckActiveCount;
DROP TABLE CheckConstraint;
You could move the deleted records to a table that lacks the constraint, and perhaps use a view with UNION of the two tables to preserve the appearance of a single table.
You can do this in a really hacky way...
Create an schemabound view on your table.
CREATE VIEW Whatever
SELECT * FROM Table
WHERE RecordStatus = 1
Now create a unique constraint on the view with the fields you want.
One note about schemabound views though, if you change the underlying tables you will have to recreate the view. Plenty of gotchas because of that.
For those still searching for a solution, I came accross a nice answer, to a similar question and I think this can be still useful for many. While moving deleted records to another table may be a better solution, for those who don't want to move the record can use the idea in the linked answer which is as follows.
Set deleted=0 when the record is available/active.
Set deleted=<row_id or some other unique value> when marking the row
as deleted.
If you can't use NULL as a RecordStatus as Bill's suggested, you could combine his idea with a function-based index. Create a function that returns NULL if the RecordStatus is not one of the values you want to consider in your constraint (and the RecordStatus otherwise) and create an index over that.
That'll have the advantage that you don't have to explicitly examine other rows in the table in your constraint, which could cause you performance issues.
I should say I don't know SQL server at all, but I have successfully used this approach in Oracle.
Because, you are going to allow duplicates, a unique constraint will not work. You can create a check constraint for RecordStatus column and a stored procedure for INSERT that checks the existing active records before inserting duplicate IDs.