Mysql «вставить, если не существует», предотвращая «перекрытие» - PullRequest
0 голосов
/ 20 февраля 2019

Кажется, на мой общий вопрос отвечает 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 это было бы верно,Если это правда, я мог бы удалить Предел 1 из моего решения:

Если вы используете «из двойного» в строке 2 вместо «из таблицы», то вам не нужно условие «предел 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 голосов
/ 20 февраля 2019

Вместо этого я бы использовал триггеры:

-- 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

Кроме того, вы можете реализовать аналогичные настройки для обновлений.

PS Вы должны проверить логику перекрытия в моем коде, так как я не знаю, следует ли разрешать start = end или нет.PPS Index (user_id, begin) также поможет

0 голосов
/ 20 февраля 2019

Может быть легче понять, что делает ваш запрос, если он будет переписан так:

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, чтобы сравнить сандидат. конец).

0 голосов
/ 20 февраля 2019

Выбор из DUAL будет возвращать только когда-либо одну строку, поэтому LIMIT 1 не требуется.Однако если вы используете имя таблицы, ваш запрос вернет либо столько строк, сколько в таблице, либо ни одной, в зависимости от того, вернет ли выражение EXISTS истину или ложь.Так что в этом случае вам понадобится 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))

Iсделал маленькую демонстрацию на dbfiddle , чтобы показать, как они работают.

0 голосов
/ 20 февраля 2019

NOT EXISTS означает, что получают результаты для внутреннего запроса и, если он возвращает какие-либо строки, не включают эту связанную строку в основной запрос. Но , ваш подзапрос не имеет никакого отношения к вашему основному запросу, что заставляет меня думать, что запрос может не давать правильных результатов.

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

приведенный выше запрос делает намного большесмысл.Это означает, что для каждой записи в tbl1 проверьте tbl2 и, если какой-либо результат найден, не включайте его в результат запроса.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...