MySQL“如果不存在则插入”,防止“重叠”

时间:2019-02-19 23:16:28

标签: mysql sql

我的一般问题似乎得到了https://stackoverflow.com/a/3025332/3650835的回答,但是在阅读MySql文档后,我不完全了解它,并且想知道我的解决方案是否可以按我的预期工作,还想知道是否需要LIMIT 1。

目标:确保对于给定的user_id,开始和结束永远不会“重叠”。例如:

test_table

user_id   start   end
4         1       5
4         6       13
4         11      17     --> NOT allowed, bc 11 <= 13
2         1       9      --> allowed, user_id is different

我当前的解决方案

/* this should not insert anything, as it would cause an "overlap" of start
and end, based on row 2 having end = 13 */

INSERT INTO `test_table` (user_id, start, end) 
SELECT '4', '11', '17' FROM DUAL 
WHERE NOT EXISTS (SELECT * FROM `test_table` 
      WHERE user_id = '4' AND end >= '11')
LIMIT 1;

WHERE NOT EXISTS部分的意思是“仅在以下选择不返回的情况下才执行此插入操作吗?”

此外,链接的解决方案中有以下注释,但我不明白为什么基于MySql文档会如此。如果为true,则可以从解决方案中删除限制1:

  

如果您在第2行使用“ from dual”而不是“ from table”,那么您   不需要“限制1”子句

感谢您的时间。

编辑:这是所有用于测试/设置的SQL:

CREATE TABLE `test_table`(
    `id` INT PRIMARY KEY AUTO_INCREMENT,
    `user_id` INT,
    `start` INT,
    `end` INT
);


INSERT INTO `test_table` (user_id, start, end) 
SELECT '4', '1', '5' FROM DUAL 
WHERE NOT EXISTS (SELECT * FROM `test_table` 
      WHERE user_id = '4' AND end >= '1')
LIMIT 1;

INSERT INTO `test_table` (user_id, start, end) 
SELECT '2', '1', '9' FROM DUAL 
WHERE NOT EXISTS (SELECT * FROM `test_table` 
      WHERE user_id = '2' AND end >= '1')
LIMIT 1;

INSERT INTO `test_table` (user_id, start, end) 
SELECT '4', '6', '13' FROM DUAL 
WHERE NOT EXISTS (SELECT * FROM `test_table` 
      WHERE user_id = '4' AND end >= '6')
LIMIT 1;

/* this should not insert anything, as it would cause an "overlap" of start and end */
INSERT INTO `test_table` (user_id, start, end) 
SELECT '4', '11', '17' FROM DUAL 
WHERE NOT EXISTS (SELECT * FROM `test_table` 
      WHERE user_id = '4' AND end >= '11')
LIMIT 1;

4 个答案:

答案 0 :(得分:1)

NOT EXISTS表示获取内部查询的结果,并且如果返回任何行,则不将相关行包含在主查询中。 但是,您的子查询与主查询没有任何关系,这使我认为该查询可能无法生成正确的结果。

select * from tbl1 
where not exists (
   select 1 from tbl2 where tbl1.id = tbl2.id
)

上面的查询更有意义。这意味着对于tbl1中的每条记录,请检查tbl2,如果发现任何结果,请勿将其包括在查询结果中。

答案 1 :(得分:1)

如果这样重写查询,可能会更容易理解您的查询:

INSERT INTO `test_table` (user_id, start, end) 
SELECT user_id, start, end 
FROM ( SELECT 4 AS `user_id`, 6 AS `start`, 13 AS `end`) AS candidate
WHERE NOT EXISTS (
   SELECT * 
   FROM `test_table` AS t 
   WHERE t.user_id = candidate.user_id AND t.end >= candidate.`end`
)
;

此外,请注意,我删除了数字周围的单引号;在这种情况下可能不是问题,但在某些情况下可能会导致难以找到2> 11的错误(如果MySQL决定将t.end强制转换为char类型以与候选人比较。 )。

答案 2 :(得分:1)

DUAL中进行选择只会返回永远返回的一行,因此不需要LIMIT 1。但是,如果您使用表名,则查询将返回表中的所有行,或者不返回任何行,具体取决于EXISTS表达式返回true还是false。因此,在这种情况下,您将需要LIMIT 1

您对WHERE NOT EXISTS所做的解释是正确的。

如果仅按顺序插入(start,end)对,则您在end上进行的现有测试就足够了。但是如果它们可能倒退,例如(4, 1, 2), (4, 5, 6), (4, 3, 4),那么您应该在子查询中更改WHERE子句,以同时测试start的值,例如最后一个查询应写为

INSERT INTO `test_table` (user_id, start, end) 
SELECT '4', '11', '17' FROM DUAL 
WHERE NOT EXISTS (SELECT * FROM `test_table` 
      WHERE user_id = '4' AND 
            (start <= 11 AND end >= 11 OR start <= 17 AND end >= 17))

我做了一个小的demo on dbfiddle,以展示它们的工作原理。

答案 3 :(得分:1)

我改用触发器:

-- This is your original table create statement
DROP TABLE IF EXISTS `test_table`;
CREATE TABLE `test_table`(
    `id` INT PRIMARY KEY AUTO_INCREMENT,
    `user_id` INT,
    `start` INT,
    `end` INT
);

DELIMITER //
CREATE TRIGGER `TRG_test_table_before_insert` BEFORE INSERT ON `test_table` FOR EACH ROW BEGIN
    SELECT
        COUNT(*) INTO @cnt
    FROM
        test_table
    WHERE
            `user_id` = NEW.user_id
        AND `start` <= NEW.`end`
        AND `end` >= NEW.`start`
    ;

    IF(@cnt > 0) THEN
        SET @msg = CONCAT('TrgErr: overlapping, user_id = ', NEW.user_id, ', start = ', NEW.`start`, ', end = ', NEW.`end`);
        SIGNAL SQLSTATE '45000' SET message_text = @msg;
    END IF;
END//
DELIMITER ;

然后您将能够使用普通的插入语句:

INSERT INTO `test_table` (user_id, `start`, `end`) VALUES ('4', '1', '5');
INSERT INTO `test_table` (user_id, `start`, `end`) VALUES ('2', '1', '9');
INSERT INTO `test_table` (user_id, `start`, `end`) VALUES ('4', '6', '13');
INSERT INTO `test_table` (user_id, `start`, `end`) VALUES ('4', '11', '17'); -- this one will not be inserted

此外,您可以实施类似的更新设置。

P.S。您应该检查代码中的重叠逻辑,因为我不知道是否应该允许start = end。 P.P.S.索引(user_id,开始)也有帮助