Введите значения, которых еще нет в таблице - PullRequest
0 голосов
/ 28 декабря 2018

Я хочу создать таблицу и затем инициализировать ее с некоторыми значениями, как можно более кратко.

Однако этот сценарий выполняется каждый раз, когда запускается мое приложение, поэтому вставка должна выполняться только для элементов.которые еще не были добавлены ранее.

Я не хочу использовать директиву IGNORE в INSERT IGNORE INTO, потому что не хочу игнорировать непредвиденные ошибки.

По какой-то причине INSERT INTOзавершается с ошибкой «Ошибка SQL (1136): счетчик столбцов не совпадает со счетчиком значений в строке 1», хотя следующий выбор дает значения, которые необходимо добавить.

Вот код ошибки:

START TRANSACTION;

CREATE TABLE IF NOT EXISTS `privileges` (
    `id` TINYINT NOT NULL AUTO_INCREMENT,
    `label` VARCHAR(25) UNIQUE,
    PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

INSERT INTO `privileges` (`label`)
SELECT `label` FROM (
SELECT NULL AS `label`
UNION VALUES
    ('item1'),
    ('item2')
) X
WHERE `label` IS NOT NULL
AND `label` NOT IN (SELECT `label` FROM `privileges`)

COMMIT;

В настоящее время я решаю эту проблему, сначала вставляя значения во временную таблицу, а затем выполняю выборку для этого.Но почему вышеприведенное не работает и есть более краткий способ сделать то, что я пытаюсь сделать?

Я использую MariaDB 10.3.9, добавлено отсутствующее уникальное ограничение

Редактировать 2: Спасибо LukStorms за выяснение, что ошибка была связана с AUTO_INCREMENT, кажется, что передача NULL для столбца AUTO_INCREMENT решает проблему следующим образом:

INSERT INTO `privileges` (id, label)
WITH ITEMS(label) AS (VALUES
    ('users:read'),('users:create'),
    ('clients:read'),('clients:write'),
    ('catalog:read'),('catalog:write'),
    ('cart:read'),('cart:write'),
    ('orders:read'),('orders:write'), ('test1')
) SELECT NULL, label FROM ITEMS i
WHERE label NOT IN (SELECT label FROM `privileges`);

Ответы [ 2 ]

0 голосов
/ 28 декабря 2018

В MariaDb 10.3+ использование CTE с выражением VALUES позволяет вам присвоить ему имя столбца.

with ITEMS(label) as
(VALUES 
 ('item1')
,('item2'))
select i.label 
from ITEMS i
where not exists (select 1 from privileges p where p.label = i.label)

Но почему-то выдает ошибку при вставке в таблицу с полемс AUTO_INCREMENT.Для меня это похоже на ошибку.

Однако, когда вы вставляете NULL в поле AUTO_INCREMENT, NULL игнорируется.Но вы сами обнаружили это поведение.

Так что это работает:

INSERT INTO privileges (id, label)
WITH ITEMS(label) as (
 VALUES ('item1'), ('item2')
)
SELECT null, i.label
FROM ITEMS i
WHERE NOT EXISTS (SELECT 1 FROM privileges p WHERE p.label = i.label);

Тест по db <> fiddle здесь

Использование объединенного выбора также работает, хотя,

INSERT INTO privileges (label)
SELECT label
FROM (
 SELECT 'item1' as label UNION ALL 
 SELECT 'item2'
) i
WHERE NOT EXISTS (SELECT 1 FROM privileges p WHERE p.label = i.label);

db <> fiddle здесь

Возможно, другой способ - использовать временную таблицу (которая исчезнет после истечения сеанса)

CREATE TEMPORARY TABLE tmp_items (label VARCHAR(25) NOT NULL PRIMARY KEY);

INSERT INTO tmp_items (label) VALUES 
 ('item1')
,('item2');

INSERT INTO privileges (label)
SELECT label
FROM tmp_items i
WHERE label NOT IN (SELECT DISTINCT label FROM privileges);

Тест по дБ <> скрипка здесь

0 голосов
/ 28 декабря 2018

Во-первых, ваше приложение пытается дважды вставить значения.Это вероятно не должно делать этого (хотя я могу вспомнить несколько допустимых вариантов использования).Попробуйте сделать так, чтобы он не пытался добавить данные, которые уже были добавлены ранее.Если у вас нет легкого доступа к состоянию между экземплярами, вытяните текущий список из базы данных при запуске, прежде чем решить, что вставлять.

Во-вторых, если вы хотите, чтобы метки были уникальными, почему нетуникальный ключ в поле label?На данный момент INSERT IGNORE даже не будет работать, потому что в вашей схеме нет ничего, что препятствует дублированию значений меток.Я хотел бы спросить себя, зачем вам нужно автоинкрементное ID: почему бы просто не иметь label и сделать его первичным ключом?

Затем, если вам все еще нужно выполнить это дублирование вНа уровне SQL вы можете использовать ON DUPLICATE KEY, чтобы высосать избыточные вставки существующего первичного ключа:

INSERT INTO `privileges` (`label`)
VALUES
    ('item1'),
    ('item2')
)
ON DUPLICATE KEY UPDATE `label` = `label`

Это решение сложно реализовать с помощью вашего ID ключа автоинкремента,потому что ваше приложение, вероятно, не знает, каким будет идентификатор.Еще одна причина, по которой стоит отказаться от него.

К сожалению, нет ON DUPLICATE KEY IGNORE.

Если вы хотите сохранить ключ идентификатора и не хотите, чтобы ваше приложениечтобы выполнить шаг чтения при запуске (возможно, по соображениям масштабируемости), тогда, если честно, INSERT IGNORE будет вашим лучшим выбором, хотя вам все равно потребуется хотя бы уникальный ключ на label, чтобы это работало.

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