Недавно мы добавили поддержку базы данных MS Access в jOOQ 3.3 . Возможно, это была наша самая сложная интеграция. База данных MS Access имеет свои способы, и многие из них тоже. Но, к счастью, возможности внутреннего преобразования jOOQ в SQL уже очень продвинуты, как мы показали ранее в посте блога об эмуляции предикатов IN выражения-значения строки .
В этом посте мы хотим показать вам, как мы можем прозрачно эмулировать многозначный оператор INSERT с помощью различных шагов, пока он не будет работать и для MS Access.
Рассмотрим следующий стандартный оператор SQL-92 INSERT
:
INSERT INTO books (author, title) VALUES ('George Orwell', '1984'), ('Leo Tolstoy', 'War and Peace');
С помощью jOOQ мы можем написать это так:
DSL.using(configuration) .insertInto(BOOKS, AUTHOR, TITLE) .values("George Orwell", "1984") .values("Leo Tolstoy", "War and Peace") .execute();
Смотрите руководство для более подробной информации . Вышеприведенный INSERT
синтаксис нескольких записей поддерживается различными базами данных, но не этими:
- жар-птица
- Энгр
- MS Access
- оракул
- SQLite
- Sybase Adaptive Server Enterprise
Но, к счастью, приведенный выше синтаксис можно эмулировать с помощью INSERT .. SELECT
INSERT INTO books (author, title) SELECT 'George Orwell', '1984' UNION ALL SELECT 'Leo Tolstoy', 'War and Peace';
Обратите внимание, что для некоторых баз данных FROM
в большинстве операторов SQL требуется условие. Как и MS Access. Простой способ эмулировать то, что Oracle называет DUAL с помощью MS Access:
INSERT INTO books (author, title) SELECT 'George Orwell', '1984' FROM (SELECT count(*) FROM MSysResources) AS DUAL UNION ALL SELECT 'Leo Tolstoy', 'War and Peace'; FROM (SELECT count(*) FROM MSysResources) AS DUAL
Для простоты, давайте просто предположим, что DUAL действительно существует:
INSERT INTO books (author, title) SELECT 'George Orwell', '1984' FROM DUAL UNION ALL SELECT 'Leo Tolstoy', 'War and Peace'; FROM DUAL
Но этот синтаксис не поддерживается, опять же, MS Access, как вы можете видеть в руководстве .
Это гласит:
Синтаксис
Запрос на добавление нескольких записей:INSERT INTO target [(field1[, field2[, …]])] [IN externaldatabase] SELECT field1[, field2[, …] FROM tableexpressionтабличное выражение: имя таблицы или таблиц, из которых вставляются записи. Этот аргумент может быть одним именем таблицы или составным элементом, полученным в результате операции INNER JOIN, LEFT JOIN, RIGHT JOIN или сохраненного запроса.
В предложениях явно нет места UNION ALL
, но мы можем использовать «сохраненный запрос» в FROM
предложении. Учитывая наше первоначальное намерение, это примерно соответствует:
INSERT INTO books (author, title) SELECT * FROM ( SELECT 'George Orwell', '1984' FROM DUAL UNION ALL SELECT 'Leo Tolstoy', 'War and Peace'; FROM DUAL )
… или вы можете подумать. К сожалению, вышеупомянутая попытка приводит к этому сообщению об ошибке:
SELECT * нельзя использовать в запросе INSERT INTO, если таблица источника или назначения содержит многозначное поле
Поэтому нам нужно явно выбрать каждый столбец из нашей вновь созданной производной таблицы. Но эти столбцы не имеют имен (пока). Стандартный способ присвоения имен столбцам производной таблицы заключается в использовании списков производных столбцов, которые переименовывают таблицу и все ее столбцы за один раз. Ранее мы писали об этом здесь .
Для нашего оператора SQL это означает:
INSERT INTO books (author, title) SELECT a, b FROM ( SELECT 'George Orwell', '1984' FROM DUAL UNION ALL SELECT 'Leo Tolstoy', 'War and Peace'; FROM DUAL ) t(a, b)
К настоящему времени вы, возможно, догадались, что наша одиссея преобразования SQL еще не закончена. MS Access не поддерживает производные списки столбцов, и это в хорошей компании. Эти базы данных также не поддерживают их:
- H2
- Энгр
- MariaDB
- MS Access
- MySQL
- оракул
- SQLite
Это означает, что нам нужно эмулировать еще одну функцию SQL, которая приводит к следующему запросу:
INSERT INTO books (author, title) SELECT a, b FROM ( -- This subselect defines column names SELECT '' AS a, '' AS b FROM DUAL WHERE 1 = 0 UNION ALL -- This subselect provides our data SELECT * FROM ( SELECT 'George Orwell', '1984' FROM DUAL UNION ALL SELECT 'Leo Tolstoy', 'War and Peace'; FROM DUAL ) t ) t
И мы настроены. Почти. Давайте подставим реальное выражение DUAL обратно в запрос
INSERT INTO books (author, title) SELECT a, b FROM ( -- This subselect defines column names SELECT '' AS a, '' AS b FROM (SELECT count(*) FROM MSysResources) AS DUAL WHERE 1 = 0 UNION ALL -- This subselect provides our data SELECT * FROM ( SELECT 'George Orwell', '1984' FROM (SELECT count(*) FROM MSysResources) AS DUAL UNION ALL SELECT 'Leo Tolstoy', 'War and Peace'; FROM (SELECT count(*) FROM MSysResources) AS DUAL ) t ) t
Разве это не красота !?
Сценарий реальной жизни
В реальной жизни вы, вероятно, так или иначе обойдете это ограничение, потому что никто не пишет (или не читает и не поддерживает) такой SQL-код вручную. Вы, вероятно, прибегаете к множественному выполнению INSERT
операторов с одной записью , к пакетной обработке или как угодно. Но в реальной жизни вы бы также поддерживали SQL Server или Oracle или какую-либо другую базу данных в дополнение к MS Access, и вы постоянно сталкивались с такими проблемами, которые вам пришлось бы исправлять вручную. Это может очень расстраивать!
Если, конечно, вы не используете jOOQ и забудете о вышеупомянутых деталях, чтобы просто написать следующий, интуитивно понятный, стандартный оператор SQL:
DSL.using(configuration) .insertInto(BOOKS, AUTHOR, TITLE) .values("George Orwell", "1984") .values("Leo Tolstoy", "War and Peace") .execute();
… Который работает именно так во всех 16 поддерживаемых СУБД jOOQ. Чего же ты ждешь?