如何使用外键执行此操作?

时间:2018-10-17 09:43:02

标签: mysql foreign-keys

我有一个名为categories的表,其中包含以下字段:

  • id(主键,标识类别)
  • user_id(外键users.id,当前类别的创建者)
  • category_id(外键categories.id,父类别ID的ID)

在此表中,我有一些可供所有人访问的记录。

对于这些记录,user_idcategory_id字段均为NULL

此外,用户可以创建自己的记录(其中user_id字段不是NULL,但是每个用户只能访问自己创建的记录。

每条记录必须满足以下条件:

  • user_id值必须有效
  • 如果给出了category_id,那么它必须是有效的categories.id,并且该记录必须由给定的用户创建
  • category_id的值可以是categoies.id,其中user_idNULL

我该怎么做?我想我需要更多的外键,但是不确定如何做。

一些示例可以帮助您理解我的问题:

让我们说我在categories表中有以下记录:

+--------+-------------+-----------------+
|   id   |   user_id   |   category_id   |
+--------+-------------+-----------------+
|   1    |     NULL    |      NULL       |
+--------+-------------+-----------------+
|   2    |     NULL    |      NULL       |
+--------+-------------+-----------------+
|   3    |      1      |        1        |
+--------+-------------+-----------------+
|   4    |      2      |        1        |
+--------+-------------+-----------------+

并说我想插入这些记录:

+-------------+-----------------+----------------------------------------------------------------+
|   user_id   |   category_id   | is it insertable?                        
+-------------+-----------------+----------------------------------------------------------------+
|   1         |     NULL        | yes, because the value of the user_id is valid id              |
+-------------+-----------------+----------------------------------------------------------------+
|   1         |     1           | yes, because the record with id of 1 is created by NULL        |
+-------------+-----------------+----------------------------------------------------------------+
|   1         |     3           | yes, because the record with id of 3 is created by user #1     |
+-------------+-----------------+----------------------------------------------------------------+
|   1         |     4           | no, because the record with id of 4 is created by another user |
+-------------+-----------------+----------------------------------------------------------------+

类别表:

CREATE TABLE `categories` (
  `id` int(11) NOT NULL,
  `user_id` int(11) DEFAULT NULL,
  `category_id` int(11) DEFAULT NULL,
  `name` varchar(150) NOT NULL,
  `type` enum('income','expense') NOT NULL DEFAULT 'income',
  `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `updated_at` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00' ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

ALTER TABLE `categories`
  ADD PRIMARY KEY (`id`),
  ADD UNIQUE KEY `category_id` (`category_id`,`user_id`,`name`),
  ADD KEY `user_id` (`user_id`);

ALTER TABLE `categories`
  ADD CONSTRAINT `categories_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
  ADD CONSTRAINT `categories_ibfk_2` FOREIGN KEY (`category_id`) REFERENCES `categories` (`id`) ON DELETE CASCADE ON UPDATE CASCADE;

1 个答案:

答案 0 :(得分:1)

不幸的是,MySQL外键可以走得这么远。这是一些高级逻辑。我建议使用MySQL触发器吗?

DELIMITER $$
CREATE PROCEDURE `check_categories_user_id`(IN p_category_id INT(11), IN p_user_id INT(11))
BEGIN
    IF (p_category_id IS NOT NULL) THEN

        SET @other_user_id = (SELECT user_id
           FROM categories
           WHERE id = p_category_id);              

        IF (p_user_id <> @other_user_id) THEN
            SIGNAL SQLSTATE '45000'
                SET MESSAGE_TEXT = 'check constraint on categories.user_id failed';
        END IF;
   END IF;
END$$

CREATE TRIGGER `categories_before_update` BEFORE UPDATE ON `categories`
FOR EACH ROW
BEGIN
    CALL check_categories_user_id(new.category_id, new.user_id);
END$$


CREATE TRIGGER `categories_before_insert` BEFORE INSERT ON `categories`
FOR EACH ROW
BEGIN
    CALL check_categories_user_id(new.category_id, new.user_id);
END$$   
DELIMITER ;

请参见dbfiddle here。 (如果删除最后一个INSERT查询,则SELECT查询将按预期工作)

此外,为避免一些开销(触发器中的SELECT查询),当category_id不为NULL时,您可以对user_id强制执行NULL(使用上述触发器)。如果category_id不为NULL,则只需从父行(或根行(父级的父行...等),如果嵌套)中获取user_id。