【问题标题】:Check if values exist before INSERT INTO ... ON DUPLICATE KEY UPDATE检查在 INSERT INTO ... ON DUPLICATE KEY UPDATE 之前是否存在值
【发布时间】:2016-10-14 19:37:04
【问题描述】:

背景

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

我正在实现一个“自动保存”功能,它每 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 需要指向现有评论,因为理想情况下,此处的索引需要是product_id、@987654329 的组合@ review_id。不幸的是,在我的情况下,以下适用:

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

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

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

谢谢。

【问题讨论】:

  • 这个问题已经很长了,所以我尝试尽可能地修剪它。如果有什么我遗漏或需要澄清的地方,请告诉我。

标签: mysql


【解决方案1】:

除了review_autosave_data,您还可以创建两个表,例如review_insert_draftsreview_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 语句。

【讨论】:

  • 是的,我想过这个,这可能是最好的解决方案。谢谢。
  • 感谢您的意见!我决定采用这种方法,因为以简单的方式进行似乎更合理。因此,我已将此答案标记为“已接受”。但是,由于它不能完全解决基于提供的数据库结构的问题,因此我将赏金奖励给了另一个做得更好的答案。
【解决方案2】:

不要使用NULL,而是使用 0 表示还没有评论。

然后将DEFAULT NULL更改为NOT NULL

同时,你也可以去掉id,而直接拥有PRIMARY KEY(product_id, user_id, review_id)

同时删除任何作为 PRIMARY KEY 前缀的索引。

为什么你的“评论”是BLOB,而它可能是“TEXT”?

【讨论】:

  • 是的,这是一个解决方案。不过,我必须删除外键。
  • 是否可以使用一个查询来查找唯一索引(product_id、user_id 和 review_id 的组合),如果这样的行不存在,则继续插入?我可以在单独的查询中执行此操作,但对我来说两者都比较棘手。
  • INSERT ... ON DUPLICATE KEY UPDATE... 不需要在运行之前需要检查。它要么更新或插入。
  • 对,但如果可能的话,我宁愿保留null。因此,在这种情况下,该键将永远不存在,因为具有 null 的列基本上不能成为此类唯一键的一部分。因此,与其像您建议的那样使用 0 (这仍然是一个不错的解决方案),我宁愿手动进行此检查。这有意义吗?
  • 由于 IODKU 必须有一个唯一的索引来检查,而 NULL 对你来说不是“正确”的,所以你被卡住了。
【解决方案3】:

只需制作第二张桌子来保存已发布的评论。一旦用户发表评论,就会将数据移到那里。

这将消除您的问题并使事情变得更清晰。 您将有一个地方存放不应该在任何地方发布的草稿,还有一个表格可以发布您想在不同地方显示的评论。您当前的表名也表明它仅包含自动保存数据而没有发布的数据。

【讨论】:

  • 感谢您的回答。我已经有这样的桌子了。我没有在这里添加它,因为它在很大程度上与问题无关。这里的难题是如何为新评论存储草稿,但“评论表”中尚不存在,以及如何为现有评论存储草稿。
  • 您也可以从我的表格描述中看到这一点。 review_id 是评论表的外键。我只提供商店草稿。
  • 对不起,我误解了你的问题。想一想,我不明白你为什么要保持草稿和现有评论之间的关系。如果需要,UserId 和 ProductId 应该足以加入它们。
  • 嗯,就是这样。用户可以为每个产品发布多个评论。无论如何,我需要考虑这样一个事实,即草稿在插入之前可能会或可能不会链接到现有评论。唯一索引有效,但在为新评论创建新草稿时无效 - 因为它不是用新的自动保存替换最后一个自动保存,而是创建一个新行。
【解决方案4】:
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";

【讨论】:

  • 谢谢。我会在星期一试一试,然后告诉你。
  • 这适用于INSERT 部分,但对ON DUPLICATE KEY 没有任何作用。不幸的是,这不起作用。
【解决方案5】:

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

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

【讨论】:

    【解决方案6】:

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

    PRIMARY KEY(`product_id`, `user_id`)
    

    并将 review_id 添加为您的UNIQUE KEY

    【讨论】:

      【解决方案7】:

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

      大概当您的应用程序提交自动保存“请求”时,它会得到某种确认它发生的正确性?您可以发回插入的“ID”,以便应用程序可以显式保存到该记录中 - 也就是,第一次自动保存会有所不同,然后其余的自动保存将只更新特定的自动保存记录。

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

      【讨论】:

        猜你喜欢
        • 2011-01-29
        • 1970-01-01
        • 2017-02-21
        • 1970-01-01
        • 2016-10-29
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多