sidgate
sidgate

Reputation: 15244

Mysql unique constraint allowing single row for a combination

Is it possible to have a unique constraint such that one particular column has a value only once?

For instance

-----------------------    
name | price | default
-----------------------
XYZ  |  20   | TRUE
-----------------------
XYZ  |  30   | FALSE
-----------------------
XYZ  |  40   | FALSE
-----------------------
ABC  | 50    | FALSE
-----------------------

So in above table, for a particular name value, default value can be TRUE only once. And will have a unique constraint on name & price columns.

Is this possible?

Upvotes: 5

Views: 1914

Answers (5)

RandomSeed
RandomSeed

Reputation: 29769

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)
);

Upvotes: 3

Ragen Dazs
Ragen Dazs

Reputation: 2170

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.

Upvotes: 2

Tim Biegeleisen
Tim Biegeleisen

Reputation: 521269

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.

Upvotes: 1

Daniel A. Thompson
Daniel A. Thompson

Reputation: 1924

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.

Upvotes: 1

Nanne
Nanne

Reputation: 64409

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.

Upvotes: 2

Related Questions