在INSERT INTO ... ON DUPLICATE KEY UPDATE之前检查值是否存在

时间:2016-10-05 10:54:48

标签: mysql

背景

我有一个用户可以提交产品评论的应用程序。用户还可以编辑以前提交的评论或为同一产品提交另一个评论。

我正在实施一个“自动保存”功能,每隔X秒保存一次表单数据。如果页面意外关闭,用户可以恢复此“草稿”。

这是我桌子的简化版本:

CREATE TABLE `review_autosave_data` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `review_id` int(11) unsigned DEFAULT NULL,
  `product_id` int(11) unsigned NOT NULL,
  `user_id` int(11) unsigned NOT NULL,
  `review` blob,
  `name` varchar(100) DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `unique_index` (`product_id`, `user_id`),
  KEY `fk_review_autosave_data_review_id (`review_id`),
  KEY `fk_review_autosave_data_product_id (`product_id`),
  KEY `fk_review_autosave_data_user_id (`user_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4;

请记住,此表仅存储草稿 - 而非实际评论。如果我们正在修改评论,review_id将指向该评论。如果我们正在创建新审核,则此字段将为NULL

什么在起作用

这是我插入新草稿的查询:

INSERT INTO review_autosave_data (review_id, product_id, user_id, review)
VALUES (25, 50, 1, "lorem ipsum")
ON DUPLICATE KEY
UPDATE review = "lorem ipsum";

这适用于插入审核草稿。索引会阻止在已存在product_iduser_id组合的位置插入新行。

什么不起作用

我的问题是插入现有评论的草稿,其中review_id需要指向现有评论,因为理想情况下,此处的索引必须是{{1}的组合},product_id user_id。不幸的是,在我的情况下,以下适用:

  

UNIQUE索引允许可以包含NULL的列的多个NULL值

虽然有关于上述引用的问题和答案,但我并不一定对将null值作为唯一索引的一部分感兴趣 - 而是找到解决方法。

我想我可以先做一个select查询来检查上面的组合是否存在,如果没有继续主查询。但是如果可能的话,我想把所有这些都集成到一个查询中。想法?

感谢。

7 个答案:

答案 0 :(得分:3)

您可以创建两个表格,例如review_autosave_datareview_insert_drafts(一个用于新评论,一个用于评论更新),而不是review_update_drafts

CREATE TABLE `review_insert_drafts` (
  `product_id` int(11) unsigned NOT NULL,
  `user_id` int(11) unsigned NOT NULL,
  `review` blob,
  `name` varchar(100) DEFAULT NULL,
  PRIMARY KEY (`product_id`, `user_id`),
  CONSTRAINT FOREIGN KEY (`product_id`) REFERENCES `products` (`id`),
  CONSTRAINT FOREIGN KEY (`user_id`) REFERENCES `users` (`id`)
);

CREATE TABLE `review_update_drafts` (
  `review_id` int(11) unsigned NOT NULL,
  `review` blob,
  `name` varchar(100) DEFAULT NULL,
  PRIMARY KEY (`review_id`),
  CONSTRAINT FOREIGN KEY (`review_id`) REFERENCES `reviews` (`id`)
);

(不确定name列的适用对象。)

在您的应用程序中,您必须检查用户是在撰写新评论还是正在更新现有评论。

对于您要运行的新评论:

INSERT INTO review_insert_drafts (product_id, user_id, review)
VALUES (50, 1, "lorem ipsum")
ON DUPLICATE KEY
UPDATE review = "lorem ipsum";

REPLACE INTO review_insert_drafts (product_id, user_id, review)
VALUES (50, 1, "lorem ipsum");

对于您运行的评论更新:

INSERT INTO review_update_drafts (review_id, review)
VALUES (25, "lorem ipsum")
ON DUPLICATE KEY
UPDATE review = "lorem ipsum";

REPLACE INTO review_update_drafts (review_id, review)
VALUES (25, "lorem ipsum");

优点:您的设计清晰,具有清晰的唯一键和外键。

缺点:您有两个包含类似数据的表。所以你有两个不同的插入语句。如果要组合两个表(例如,显示用户的所有草稿),则需要UNION语句。

答案 1 :(得分:2)

不使用compare = 'LIKE',而是使用0表示尚未进行审核。

然后将NULL更改为DEFAULT NULL

与此同时,你也可以摆脱NOT NULL而只是id

还要删除任何作为PRIMARY KEY(product_id, user_id, review_id)

前缀的索引

为什么你有"评论"如PRIMARY KEY,大概是" TEXT"?

答案 2 :(得分:1)

只需制作第二张表即可保留已发布的评论。一旦用户发布评论,就会在那里移动数据。

这将消除您的问题并使事情更清晰。 您将拥有一个不应在任何地方发布的草稿的位置,以及一个将要在各个位置显示的已发布评论的表格。您当前的表名也表明它只保留自动保存数据而没有发布数据。

答案 3 :(得分:1)

INSERT INTO review_autosave_data (review_id, product_id, user_id, review)
SELECT 25, 50, 1, "lorem ipsum"
    WHERE NOT EXISTS 
        (SELECT review_id FROM review_autosave_data 
        WHERE product_id=50 AND user_id = 1 AND review_id IS NULL)
ON DUPLICATE KEY
UPDATE review = "lorem ipsum";

答案 4 :(得分:1)

您是否尝试插入没有review_id的新行?

INSERT INTO review_autosave_data (product_id, user_id, review)
VALUES (50, 1, "lorem ipsum")
ON DUPLICATE KEY
UPDATE review = "lorem ipsum";

答案 5 :(得分:1)

您可以尝试将product_iduser_id设为主键:

PRIMARY KEY(`product_id`, `user_id`)

并将review_id添加为UNIQUE KEY

答案 6 :(得分:1)

为什么不完全从DB中移出问题?

大概是当您的应用程序提交自动保存请求时#39;它会得到某种确认,它发生了吗?您可以发回“ID'然后,应用程序可以显式保存到该记录中 - 也就是说,第一次自动保存将不同,然后其余的和随后的自动保存将只更新特定的自动保存记录。

这完全避免了数据库层中的问题我认为......你将有两个查询,一个用于插入新草稿,另一个用于更新特定草稿,不需要花哨的密钥。