【问题标题】:INSERT and detect unique constraint error插入并检测唯一约束错误
【发布时间】:2017-08-31 15:34:52
【问题描述】:

给定下表:

CREATE TABLE IF NOT EXISTS devices_used_by_resource (
  id INT NOT NULL,
  sourcesId INT NOT NULL,
  name VARCHAR(45) NOT NULL,
  PRIMARY KEY (id, sourcesId),
  INDEX sourcesIdInx (sourcesId ASC),
  UNIQUE INDEX nameSourceUniqueInx (name ASC, sourcesId ASC),
  CONSTRAINT existingSourcesId
    FOREIGN KEY (sourcesId)
    REFERENCES sources (id)
    ON DELETE NO ACTION
    ON UPDATE NO ACTION)
ENGINE = InnoDB;

我希望以下查询:

INSERT INTO devices_used_by_resource(id,sourcesId,name) VALUES(123,321,'the name');

回复为:

  • 如果 123/321 的 id/sourcesId 不存在,则插入记录。
  • 如果 123/321 的 id/sourcesId 确实存在并且 name 等于“名称”,则忽略。
  • 如果 123/321 的 id/sourcesId 确实存在并且名称不等于“名称”,则抛出异常。

sourcesId 之前已被验证为存在。

这可能吗?如果可以,怎么做?

PS。是否需要索引 sourcesIdInx,如果需要,为什么?

【问题讨论】:

  • 基于唯一约束,您的第二个要求将失败并出现数据库异常。另外,我认为您的逻辑可能会颠倒过来,要求使用唯一的名称。我认为你应该在 2 和 3 之间切换忽略和抛出。
  • @RossBush 2 不应该是一个错误,因为没有任何改变。 3 应该是一个错误,因为发生了一些变化。我的意图是然后在另一个包含id.devices_used_by_resources 的表中执行插入,并需要确保它存在并且没有被更改..
  • 我误读了您的意图。但是,目前尚不清楚。为什么不将 id 字段添加到 nameSourceUniqueInx 唯一约束。这样可以保证表中的所有 id/sourceId/name 组合都是唯一的。
  • @RossBush 因为无论 id 值如何,名称对于给定的 sourcesId 都必须是唯一的。

标签: mysql sql exception mariadb


【解决方案1】:

无法按原样使用该语句。违反唯一键将引发错误。您必须使用insert ignore(不允许您对第三种情况做出反应)或on duplicate key 来捕获该错误。

您可以使用on duplicate key,如果满足您的条件,它将故意抛出错误。不幸的是,错误消息不会与违规直接相关,它只是“任何”异常(描述您的情况的异常无论如何都不存在)。在update-部分,检查名称是否不同。如果它没有改变,你什么都不做(“忽略”),如果它改变了,你可以设置一个无效的值,例如抛出not null-error:

INSERT INTO devices_used_by_resource(id,sourcesId,name) 
VALUES(123,321,'the name')
on duplicate key update id = if(name = values(name), id, null);

> 1 row(s) affected

INSERT INTO devices_used_by_resource(id,sourcesId,name) 
VALUES(123,321,'the name')
on duplicate key update id = if(name = values(name), id, null);

> 0 row(s) affected

INSERT INTO devices_used_by_resource(id,sourcesId,name) 
VALUES(123,321,'the name1')
on duplicate key update id = if(name = values(name), id, null);

> Error Code: 1048. Column 'id' cannot be null

您在(name, sourcedId) 上有第二个唯一索引,它也会触发on duplicate key。您没有指定如果插入违反此规则的行会发生什么,因此该语句将忽略它(违反(name, sourcedId) 不会更改名称,因此不会引发异常):

INSERT INTO devices_used_by_resource(id,sourcesId,name) 
VALUES(1,321,'the name')
on duplicate key update id = if(name = values(name), id, null);

> 0 row(s) affected

如果你也想抛出异常,你可以比较所有的值而不仅仅是名称,所以不同的id 也会引发错误:

INSERT INTO devices_used_by_resource(id,sourcesId,name) 
VALUES(123,321,'the name1')
on duplicate key update 
id = if(id = values(id) and sourcesId = values(sourcesId) 
        and name = values(name), id, null);

> Error Code: 1048. Column 'id' cannot be null

异常消息显然对错误不是很清楚,因为它引发了一个不相关的错误。如果您只想捕获该异常并知道它的实际含义,那很好。为了让它更时尚一点,您可以添加一个使用id=null 作为指示器的触发器来抛出自定义消息,例如

delimiter $$
create trigger trbu_devices_used_by_resource 
  before update on devices_used_by_resource
for each row
begin
  if new.id is null then 
    SIGNAL SQLSTATE '45000' 
    SET message_text = 'Inserted duplicate entry with different attributes!';
  end if;
end $$  
delimiter ;  

如果您在没有插入的情况下使用update devices_used_by_resource set id = null,它也会抛出该错误,但我想这种情况不会经常发生,或者您也可以找到一条涵盖该错误的消息 - 或者您在upsert 和触发器,例如将 idsourcesId 设置为 -812677 并在触发器中检查该值。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-11-15
    • 1970-01-01
    • 2018-11-24
    • 2022-09-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多