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.