提问人:MagicMatt 提问时间:7/8/2022 更新时间:7/8/2022 访问量:94
MySQL触发器的INSERT由于VALUES中的“SELECT *”导致基数冲突(...)
MySQL trigger's INSERT causing Cardinality violation due to 'SELECT *' in VALUES (...)
问:
由于触发器的写入方式,我无法在具有触发器的 MySQL 表上插入新记录。我收到错误:
Cardinality violation: 1241 Operand should contain 1 column(s)
它本质上是一个审核触发器,将新记录复制到包含其他数据的审核表中。我已经成功地证明了该问题特定于嵌套的“SELECT a.*...”在触发器的 Insert 的 VALUES() 部分中,因为当我将 SELECT 替换为“NEW.xyx”列的逐项列表时,它工作正常。示例如下:
表A:
CREATE TABLE `users` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(20) NOT NULL,
`email` varchar(50) NOT NULL,
`admin` tinyint(1) NOT NULL DEFAULT 0,
`date_updated` datetime NOT NULL DEFAULT current_timestamp()
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
ALTER TABLE `users`
ADD PRIMARY KEY (`id`);
表B:
CREATE TABLE `users_audit` (
`action` varchar(8) NOT NULL,
`revision` int(11) NOT NULL,
`id` int(11) NOT NULL,
`username` varchar(20) NOT NULL,
`email` varchar(50) NOT NULL,
`admin` tinyint(1) NOT NULL,
`date_updated` datetime NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
触发器(在插入到 TableA 时导致基数冲突):
CREATE TRIGGER users_ins AFTER INSERT ON users
FOR EACH ROW BEGIN
INSERT INTO users_audit
VALUES ('insert',
(SELECT IFNULL(MAX(a.revision), 0) + 1 FROM users_audit a where a.id = NEW.id),
(SELECT b.* FROM users b WHERE b.id = NEW.id LIMIT 1)
);
END
问题出在行 上。上面的行(第一个“select”语句)没有问题。我什至添加了“LIMIT 1”以确保只返回 1 行(它应该这样做,因为“id”是“users”的 PK)。我相信发生错误是因为第二个 SELECT 语句返回多个列(由于“SELECT b.*...”),并且它们不能以逗号分隔,以便“VALUES”中的项数与“users_audit”中的列数匹配。(SELECT b.* FROM user_accounts b WHERE b.id = NEW.id LIMIT 1)
我尝试从第二个选择中删除括号,如下所示:
CREATE TRIGGER users_ins AFTER INSERT ON users
FOR EACH ROW BEGIN
INSERT INTO users_audit
VALUES ('insert',
(SELECT IFNULL(MAX(a.revision), 0) + 1 FROM users_audit a where a.id = NEW.id),
SELECT b.* FROM users b WHERE b.id = NEW.id LIMIT 1
);
END
但这甚至不能在myPhpAdmin中运行(我收到语法错误)。
真的没有办法做到这一点而不必手动写出每一列吗?作为记录,我已经让以下内容起作用,但对于我要添加审核的所有表,这将需要大量额外的时间(必须手动写出它们的每一列)。
工作触发器,但必须单独列出 NEW 列太冗长:
CREATE TRIGGER users_ins AFTER INSERT ON users
FOR EACH ROW BEGIN
INSERT INTO users_audit
VALUES ('insert',
(SELECT IFNULL(MAX(a.revision), 0) + 1 FROM users_audit a where a.id = NEW.id),
NEW.id, NEW.username, NEW.email, NEW.admin, NEW.date_updated
);
END
我真的很想避免像那样写出列。肯定有某种方法可以做我正在寻找的事情吗?感谢您的帮助!
答:
一般来说,使用选择 * 和插入而不使用列列表的列顺序是一个非常糟糕的主意,因此我建议只单独列出新列。在本例中,我看到您有两个密切相关的表,并且您可以期望它们在其中保持同步。但是,如果触发器在添加新列时触发,并且它们在一个表中,但尚未在另一个表中,则可能会出现错误。也就是说,您应该能够使用 INSERT...SELECT 而不是 INSERT...值:users
users_audit
INSERT INTO users_audit
SELECT
'insert',
(SELECT IFNULL(MAX(a.revision), 0) + 1 FROM users_audit a where a.id = NEW.id),
b.*
FROM users b WHERE b.id = NEW.id
评论