Mysql unique constraint allowing single row for a combination

为君一笑 提交于 2019-12-04 04:27:15

问题


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?


回答1:


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



回答2:


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.




回答3:


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.




回答4:


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.




回答5:


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.



来源:https://stackoverflow.com/questions/38016280/mysql-unique-constraint-allowing-single-row-for-a-combination

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!