使用 MySQL 8.0 触发器自动生成内部产品编号

Posted

技术标签:

【中文标题】使用 MySQL 8.0 触发器自动生成内部产品编号【英文标题】:Automate Internal Product Number Generation with MySQL 8.0 Triggers 【发布时间】:2021-07-04 05:31:59 【问题描述】:

我对 SQL 的模式方面不是很有经验,我必须创建一个系统来在将新行输入数据库时​​生成内部产品编号。我决定使用 SQL 触发器是最好的方法,而不是外部编程,因为它在 SQL 内部看起来更简洁和可行。

内部产品编号示例:101-001

内部产品编号有两部分,我分别调用group_idproduct_idgroup_idcategory_id 相关(它是如何存储在数据库中的),并且我正在使用一个表来连接它们,PartCategoryproduct_id 在每个 group_id 中都是递增的,但是如果删除了一行,它们应该会继续,就像没有删除一样,我认为 MAX 是合适的。

这是我正在使用的两个表的示例:

DROP TABLE IF EXISTS Part;
CREATE TABLE Part (
    id INT AUTO_INCREMENT PRIMARY KEY,
    category_id INT,
    internalPartNumber VARCHAR(255),
    name VARCHAR(255),
    createdate DATETIME,
    group_id INT,
    product_id INT
);

DROP TABLE IF EXISTS PartCategory;
CREATE TABLE PartCategory (
    id INT AUTO_INCREMENT PRIMARY KEY,
    category_id INT,
    name VARCHAR(255),
    internal_id INT,
    last_product_id INT
);


INSERT INTO PartCategory (internal_id, category_id, name) VALUE (101, 2, 'category1');
INSERT INTO PartCategory (internal_id, category_id, name) VALUE (102, 3, 'category2');

INSERT INTO Part (category_id, name, createdate, group_id, product_id) VALUE (2,   'screw', NOW(), 101, 1);
INSERT INTO Part (category_id, name, createdate, group_id, product_id) VALUE (2, 'spanner', NOW(), 101, 2);
INSERT INTO Part (category_id, name, createdate, group_id, product_id) VALUE (3,  'hammer', NOW(), 102, 1);
INSERT INTO Part (category_id, name, createdate, group_id, product_id) VALUE (3,   'paint', NOW(), 102, 2);

DELIMITER //
CREATE TRIGGER add_group_id BEFORE INSERT ON Part
FOR EACH ROW
BEGIN
    UPDATE Part
    LEFT JOIN PartCategory
    ON Part.category_id = PartCategory.category_id
    SET NEW.group_id = PartCategory.internal_id
    WHERE Part.group_id IS NULL;
    
    UPDATE Part
    SET NEW.product_id = MAX(Part.product_id) OVER(PARTITION BY Part.category_id ORDER BY createdate)
    WHERE NEW.product_id IS NULL;
    
    SET NEW.internalPartNumber = CONCAT(RIGHT(NEW.group_id+1000, 3),'-',RIGHT(NEW.product_id+1000, 3))
    WHERE NEW.internalPartNumber IS NULL;
END; //
DELIMITER ;

INSERT INTO Part (category_id, name, createdate) VALUE (2, 'bolt', NOW());
INSERT INTO Part (category_id, name, createdate) VALUE (3, 'metal', NOW());

SELECT internalPartNumber, name FROM Part
ORDER BY internalPartNumber;

但是在触发器内部,我不能使用表 Part,因为它调用了触发器。我不知道如何做到这一点。前六个插入代表通过脚本输入数据库的数据,这些我可以设置任何行,最后两个代表将由用户输入的数据(触发器需要处理的数据)。

从这个例子我想看看:

internalPartNumber | name
101-001            | screw
101-002            | spanner
101-003            | bolt
102-001            | hammer
102-002            | paint
102-003            | metal

【问题讨论】:

【参考方案1】:
CREATE TRIGGER tr_bi_Part
BEFORE INSERT
ON Part
FOR EACH ROW
SET NEW.internalPartNumber = CONCAT( ( SELECT internal_id 
                                       FROM PartCategory 
                                       WHERE category_id = NEW.category_id ),
                                     '-',
                                     ( SELECT LPAD(1 + COALESCE(COUNT(*), 0), 3, '0')
                                       FROM Part
                                       WHERE category_id = NEW.category_id ) );

https://dbfiddle.uk/?rdbms=mysql_8.0&fiddle=58fd1bc356c0a2f771d8160235be3c0c

【讨论】:

@harrygdt 考虑到代码不检查:类别存在,内部编号过大。 是的,我明白了。在我的情况下,这不应该是一个问题,但我会为下一个人在数据库中使用它做一个注释。 这有一个竞争条件。插入的两个并发会话将看到相同的COUNT(*),因此分配相同的内部部件号。

以上是关于使用 MySQL 8.0 触发器自动生成内部产品编号的主要内容,如果未能解决你的问题,请参考以下文章

Mysql 8.0 OCP认证考试原题题库整理-第4题(CUUG内部资料)

虚拟化docker创建mysql镜像,docker内部操作mysql

架构运维篇:MySQL 8.0启用BinLog 支持

MySQL:触发器的使用

MySQL 8.0配置总结for Windows10

MYSQL:基础——触发器