【问题标题】:INSERT from a SELECT making a column pair unique从 SELECT 中插入,使列对唯一
【发布时间】:2018-01-21 04:39:19
【问题描述】:

我有一张桌子(此处为简化示例):

CREATE TABLE msg
(
    msg_id VARCHAR(30) NOT NULL,
    user_id BIGINT NOT NULL,
    message TEXT,

    UNIQUE INDEX msg_index (msg_id, user_id);
);

我允许用户使用简单的 INSERT INTO 添加新消息,如下所示:

INSERT INTO msg (msg_id, user_id, message)
       VALUES ($_POST["id"], $user["id"], $_POST["message"]);

一旦消息存在,我允许用户创建一个副本。语句很简单,我可以这样做:

INSERT INTO msg (msg_id, user_id, message)
       SELECT CONCAT(msg_id, "_Copy") AS msg_id, user_id, message
          WHERE msg_id = $_POST["id"];

第一次效果很好,只有当用户再次尝试对同一条消息执行 Duplicate 命令时,它才会因为 UNIQUE INDEX 约束而失败。

有没有办法更改CONTACT() 语句以生成唯一标识符(即“_Copy1”、“_Copy2”、...)或者我是否必须测试INSERT 是否成功,如果没有,请尝试再次与下一个可能的条目? (我知道该怎么做,我只是想知道 MySQL 是否提供了令人难以置信的功能,例如自动使一组列唯一,就像魔术一样!)

【问题讨论】:

  • 是的,有一个很棒的功能叫做 AUTO_INCREMENT。也不要忘记转义您的用户输入。
  • @RolandStarke,事实上,我的实现知道$_POST 可能是有色的(即一个空的消息是无效的)。我也使用mysqli 来处理特殊字符等问题,因为我使用的是bind()。但是对于这里的例子,我没有看到让它过于复杂的意义。
  • 是的,我明白了。应该从你的分数中看到它......(我只是看了一眼你的个人资料,看到了 c++、c 和汇编,所以我想你可能会在 php 中写下你的第一行:D。

标签: php mysql unique-constraint


【解决方案1】:

由于“msg_id”是一个字符串,我可能会尝试使用以毫秒为单位的时间戳,因此您将获得开箱即用的时间戳、性能(无需搜索所有以前的副本)和“或多或少”的唯一性,如果您超过每 1 秒 1000 条消息副本的速率,则会违反此规定。

INSERT INTO msg (msg_id, user_id, message)
       SELECT CONCAT(msg_id, '_', ROUND(UNIX_TIMESTAMP(CURTIME(4)) * 1000)) AS msg_id,
              user_id,
              message
       WHERE msg_id = $_POST['id'];

注意

由于潜在的 SQL 注入,这种语法 msg_id = $_POST['id']; 非常容易受到攻击。我建议使用占位符,不要直接在查询中传递输入。

【讨论】:

  • 我宁愿有一个有意义的数字。就像您使用鼠标在文件系统中复制文件时一样(与我通常这样做的方式相反:在我的控制台中。)新文件通常具有诸如 Copy of <blah> 之类的名称,并且如果该文件名已被使用:Copy of <blah> (1) 等。使用日期最终会是一个非常大的数字,我的用户无法轻松编写。该 ID 也由用户编辑并在 URL 中重复使用......所以这也是我想避免使用序列号的部分原因。
【解决方案2】:

您可以,但它会受到竞争条件的影响。所以,它几乎可以一直工作——但不是在重负载下:

INSERT INTO msg (msg_id, user_id, message)
    SELECT (CASE WHEN COUNT(*) = 0 THEN v_msg_id
                 ELSE CONCAT_WS('_', SUBSTRING_INDEX(v_msg_id, '_', 1), COUNT(*) + 1) AS msg_id,
           v_user_id, v_message
    FROM msg
    WHERE msg_id = v_msg_id;

注意:这里假定msg_id 没有下划线。如果有可能,调整逻辑很容易。

更安全的选择是将 UUID 附加到末尾。但是,这很丑陋。

【讨论】:

  • 在我的例子中,这种情况只有在特定用户点击复制时才会发生,而且他们不可能一次点击多次,因此不应该存在竞争条件。
【解决方案3】:

您始终可以使用RAND() 函数来创建随机数(您可以搜索有关如何在 MySQL 中生成随机字符串的 SO),但我真的认为这不是一个好的解决方案。

您的 ID 应该是唯一的,并且可能会自动递增。
添加一个新列会更好,也许是“source_msg”,它将保存原始消息的 id(这样您就可以始终知道用户从哪个原始消息复制了当前消息)。

如果它的“原始”消息 - 只是放在那里 0。

表结构:

CREATE TABLE msg
(
    msg_id int NOT NULL AUTO_INCREMENT,
    source_msg INT NOT NULL,
    user_id BIGINT NOT NULL,
    message TEXT,
    PRIMARY KEY (ID),
    UNIQUE INDEX msg_index (msg_id, user_id);
);

新消息:

INSERT INTO msg (msg_id, source_msg, user_id, message)
    VALUES (NULL, 0, $user["id"], $_POST["message"]);

重复消息:

INSERT INTO msg (msg_id, source_msg, user_id, message)
    SELECT null, msg_id, user_id, message;

无论如何 - 阅读有关 boby 表的信息。

【讨论】:

  • 在我的情况下,我需要 msg_id 是一个字符串,并且在每个用户的基础上都是唯一的。添加另一列将使INSERT 语句起作用,但不是msg_id 的唯一性(即我需要用户能够通过其标识符引用消息。)
  • 我真的建议你不要这样做。你能解释一下这个要求的原因吗?
  • 用户将在我的系统以外的其他地方使用该标识符。我真的不明白这有什么问题......
猜你喜欢
  • 1970-01-01
  • 2012-07-23
  • 1970-01-01
  • 1970-01-01
  • 2019-08-31
  • 1970-01-01
  • 2021-11-09
  • 1970-01-01
  • 2019-01-28
相关资源
最近更新 更多