web-dev-qa-db-ja.com

3つの異なる列に固有の制約がありますが、3番目の列に特定の値を指定すると、重複した行エントリが許可されます

列のある「ユーザー」テーブルがありますuser_emailuser_company_idおよびuser_statususer_status列は、値が「1」または「0」の列挙型で、ユーザーがactiveまたはであることを表します非アクティブoneunique、activeのユーザーのメールのみを許可するように、これらの3つの列に一意の制約を適用する方法はありますか?特定の会社ですが、非アクティブなメールの重複したエントリはいくつありますか?

例:次のエントリを持つ「ユーザー」テーブルを考えます

CREATE TABLE users(
  user_id BIGINT(10) PRIMARY KEY AUTO_INCREMENT, 
  user_email VARCHAR(255) NOT NULL, 
  user_companyid BIGINT(10) NOT NULL, 
  user_status enum('1', '0'))

INSERT INTO users(user_id, user_email, user_companyid, user_status) 
  VALUES (1,'[email protected]','555','1');
INSERT INTO users(user_id, user_email, user_companyid, user_status) 
  VALUES (2,'[email protected]','555','1');
INSERT INTO users(user_id, user_email, user_companyid, user_status) 
  VALUES (3,'[email protected]','777','1');

SELECT * FROM users;


user_id | user_email      | user_companyid | user_status
------: | :-------------- | -------------: | :----------
      1 | [email protected] |            555 | 1          
      2 | [email protected] |            555 | 1          
      3 | [email protected] |            777 | 1           

特定の会社の既存のアクティブなメールを2回追加することはできません。以下は失敗するはずです:

INSERT INTO users(user_id, user_email, user_companyid, user_status) 
 VALUES (4,'[email protected]','555','1'); 

アクティブユーザーのいずれかのステータスを「0」(非アクティブ)に更新すると、以前のメールステータスが非アクティブであるため、同じメールを再度挿入できるはずです。以下は成功するはずです:

UPDATE users SET user_status = '0' WHERE user_id = 1;

INSERT INTO users(user_id, user_email, user_companyid, user_status) 
  VALUES (4,'[email protected]','555','1');

user_id | user_email      | user_companyid | user_status
------: | :-------------- | -------------: | :----------
      1 | [email protected] |            555 | 0          
      2 | [email protected] |            555 | 1          
      3 | [email protected] |            777 | 1          
      4 | [email protected] |            555 | 1          

また、制約により、非アクティブなユーザーの電子メールの重複エントリが許可されます。これも成功するはずです:

UPDATE users SET user_status = '0' WHERE user_id = 4;

SELECT * FROM users;

user_id | user_email      | user_companyid | user_status
------: | :-------------- | -------------: | :----------
      1 | [email protected] |            555 | 0          
      2 | [email protected] |            555 | 1          
      3 | [email protected] |            777 | 1          
      4 | [email protected] |            555 | 0
1
Jay

コメントで言ったように、ヨイはBEFORE INSERTトリガーを作成していません

CREATE TABLE users(
  user_id BIGINT(10) PRIMARY KEY AUTO_INCREMENT, 
  user_email VARCHAR(255) NOT NULL, 
  user_companyid BIGINT(10) NOT NULL, 
  user_status enum('1', '0'))
INSERT INTO users(user_id, user_email, user_companyid, user_status) 
  VALUES (1,'[email protected]','555','1');
INSERT INTO users(user_id, user_email, user_companyid, user_status) 
  VALUES (2,'[email protected]','555','1');
INSERT INTO users(user_id, user_email, user_companyid, user_status) 
  VALUES (3,'[email protected]','777','1');
✓
 
✓
 
✓
SELECT * FROM users;
 user_id | user_email | user_companyid | user_status 
 ------:| :----------- -------------:| :---------- 
 1 | [email protected] | 555 | 1 
 2 | [email protected] | 555 | 1 
 3 | [email protected] | 777 | 1 
CREATE TRIGGER users_before_insert
BEFORE INSERT
   ON users FOR EACH ROW

BEGIN

   DECLARE vUser varchar(50);

   -- Find username of person performing INSERT into table
   IF EXISTS(SELECT 1 
            FROM users 
            WHERE 
             user_email = NEW.user_email
             AND user_companyid = NEW.user_companyid
             AND user_status = 1) THEN
     signal sqlstate '45000' 
     SET MESSAGE_TEXT = 'User already activated';

  END IF;

END; 
INSERT INTO users( user_email, user_companyid, user_status) 
  VALUES ('[email protected]','555','1');
ユーザーはすでにアクティブ化されています
SELECT * FROM users;
 user_id | user_email | user_companyid | user_status 
 ------:| :-------------- | -------------:| :---------- 
 1 | [email protected] | 555 | 1 
 2 | [email protected] | 555 | 1 
 3 | [email protected] | 777 | 1 

db <> fiddle ---(ここ

0
nbk

Auto_incrementカラムは生成されたカラムから参照できないため、これは機能しませんでしたが、便利なテクニックを示しているため、とにかく追加します。生成された列を使用するという考え方です。user_status= 0の場合、一意であることが保証されているもの(主キー)にマップされ、それ以外の場合は定数にマップされます。次に、この列をUNIQUE制約に、条件のもとで一意である必要がある列と一緒に含めることができます。

CREATE TABLE users
( user_id BIGINT PRIMARY KEY  -- auto_increment had to be removed
, user_email VARCHAR(255) NOT NULL
, user_companyid BIGINT NOT NULL
, user_status enum('1', '0')
, gencol BIGINT GENERATED ALWAYS as 
    ( CASE WHEN user_status = 1
           THEN -1
           ELSE user_id
      END
    ) NOT NULL
);

ALTER TABLE users ADD CONSTRAINT ak1
    UNIQUE (user_email, user_companyid, gencol);

INSERT INTO users(user_id, user_email, user_companyid, user_status) 
  VALUES (1,'[email protected]','555','1');
INSERT INTO users(user_id, user_email, user_companyid, user_status) 
  VALUES (2,'[email protected]','555','1');
INSERT INTO users(user_id, user_email, user_companyid, user_status) 
  VALUES (3,'[email protected]','777','1');

-- Fails    
-- INSERT INTO users(user_id, user_email, user_companyid, user_status) 
-- VALUES (4,'[email protected]','555','1'); 

UPDATE users SET user_status = '0' WHERE user_id = 1;

-- Succeeds
INSERT INTO users(user_id, user_email, user_companyid, user_status) 
VALUES (4,'[email protected]','555','1'); 
0
Lennart