我的一般问题似乎得到了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;
答案 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,开始)也有帮助