Is it possible to have a unique constraint such that one particular column has a value only once?
For instance
-----------------------
name | pri
A normal way to do this is to extract a separate table to hold the default price :
CREATE TABLE price (
name VARCHAR(255),
price INT,
PRIMARY KEY (name, price)
) ;
CREATE TABLE defaultPrice (
name VARCHAR(255),
price INT,
PRIMARY KEY (name),
FOREIGN KEY(name, price) REFERENCES price(name, price)
);
Most people will advise introducing surrogate keys:
CREATE TABLE item (
id INT PRIMARY KEY,
name VARCHAR(255),
UNIQUE(name)
);
CREATE TABLE price (
itemId INT,
price INT,
PRIMARY KEY (itemId, price),
FOREIGN KEY (itemId) REFERENCES item (id)
) ;
CREATE TABLE defaultPrice (
itemId INT,
price INT,
PRIMARY KEY (itemId),
FOREIGN KEY (itemId, price) REFERENCES price (itemId, price)
);
I have solved this problem with a strange aproch, using TIMESTAMP
as a flag.
That`s usead as a "deleted" FLAG.
If IS NULL, then the register it's not deleted - Instead, if is deleted (<> NULL), the unique key will never conflict avoiding duplicate registers for non flaged as deleted registers.
For example in your case:
You will never have 2 rows with default: NULL, but you can have N with TIMESTAMP with they exclude time.
You could make a trigger that checks if there allready is a field with the 'TRUE' value, and if so take action.
Note that you cannot easily "reject" the update. (see e.g. : How to abort INSERT operation in MySql trigger? ).
You could for instance just insert it with false, and save your error somehow, by setting a flag.
Putting a simple unique constraint over all three columns would not work because it would allow the following:
name | price | default
-----------------------
XYZ | 20 | TRUE
XYZ | 30 | TRUE
XYZ | 40 | FALSE
ABC | 50 | FALSE
In other SQL engines, you could just create a check constraint that ensures that for every set of name
rows, the number of those rows with default = TRUE
is <= 1. However, MySQL does not support enforcing check constraints.
In MySQL you could implement this via insert
and update
triggers instead:
CREATE TRIGGER `before_insert` BEFORE INSERT ON `tableName`
FOR EACH ROW
BEGIN
DECLARE @sum INT
SELECT @sum = SUM(CASE WHEN [default] = TRUE THEN 1 ELSE 0 END)
FROM tableName
WHERE [name] = new.[name]
IF (@sum = 0 OR new.default = FALSE)
BEGIN
INSERT INTO tableName (name, price, default)
VALUES (new.[name], new.[price], new.[defaul]t)
END
END
And similarly for the update
.
Here is a workaround which might suit your needs. Consider the following table definition and unique constraint:
CREATE TABLE prices
(
id int NOT NULL AUTO_INCREMENT,
name varchar(255) NOT NULL,
price int,
default varchar(255)
)
ALTER TABLE prices ADD UNIQUE idx (id, name, default);
In your app layer, when you INSERT
records when default
is FALSE
, always leave the id
column NULL
. This will cause MySQL to always add a unique value for the id
, so that duplicate values for name
and default
may be entered (i.e. a default
value of FALSE
may occur multiple times for a given name
).
However, when you INSERT
a record with default
being TRUE
, you should always use the same id
value. This will ensure that a given name
can only appear once as TRUE
.