Проблема
Сегодня экспериментировал с DBExpress, многие в сети ругают данную технологию из-за однонаправленных курсоров, в частности. То есть, обойти это можно, использовав кэширующие компоненты TClientDataSet и TSimpleDataSet – в принципе, для большинства задач годится.
Но вот что мне откровенно не понравилось – невозможность выполнять скрипты SQL из нескольких инструкций. Ни TSQLQuery, ни TSQLConnection никак не хотели выполнять инструкции, если их было больше 1 штуки ((( Ошибка была примерно следующей, на примере создания таблиц в БД…
Итак, что я сделал – в программе Worbench оттестировал и выгрузил рабочий скрипт для создания БД и таблиц в БД. Если всё запускать из консоли MySQL.exe – всё прекрасно работает. Если тот же самый скрипт отдать компонентам Delphi TSQLQuery или TSQLConnection как тут же вылезает эта ошибка…
Оказывается, в технологии DBExpress нельзя вот так просто скормить скрипт целиком компонентам…((( TSQLQuery, ни TSQLConnection и др. понимают только единичные sql инструкции, то есть только до символа delimiter, по умолчанию при установке он равняется точке с запятой.
Решение
Разминки ради, я решил обойти эту ошибку, создав код, который будет “скармливать” компонентам Delphi эти инструкции по 1, но, согласитесь, это крайне неудобно )))
Алгоритм такой…
-Создать скрипт (в workbench или ручками…);
-Сохранить его в текстовом файле
-Поставить свой разделитель (или не ставить – в конце я привожу улучшенный код, этот шаг можно пропустить)
-Прочитать скрипт с помощью кода, указанного в данном посте ниже
Итак, для простоты сохраним скрипт в txt файле…, предварительно расставив между инструкциями какой-нибудь знак разделитель, типа правой фигурной скобки… “}”
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 |
CREATE SCHEMA IF NOT EXISTS `test_db` DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;} USE `test_db`;} CREATE TABLE IF NOT EXISTS `test_db`.`Departments` ( `Primary_key` INT NOT NULL AUTO_INCREMENT ,`Name` VARCHAR(45) NOT NULL , PRIMARY KEY (`Primary_key`) ) ENGINE = InnoDB; } CREATE TABLE IF NOT EXISTS `test_db`.`Employee` ( `Primary_key` INT NOT NULL AUTO_INCREMENT , `Name` VARCHAR (45) NOT NULL DEFAULT 'Default_Name' , `Family` VARCHAR(45) NOT NULL DEFAULT 'Default_Family' , `Age` INT NOT NULL DEFAULT 32 , `Salary` VARCHAR(45) NOT NULL DEFAULT '50000' , `Department_key` INT NOT NULL , PRIMARY KEY (`Primary_key`) , INDEX `FKDapartment_idx` (`Department_key` ASC) , CONSTRAINT `FKDapartment` FOREIGN KEY (`Department_key` ) REFERENCES `test_db`.`Departments` (`Primary_key` ) ON DELETE NO ACTION ON UPDATE NO ACTION) ENGINE = InnoDB; } START TRANSACTION;} INSERT INTO `test_db`.`Departments` (`Primary_key`, `Name`) VALUES (1, 'Accounting');} INSERT INTO `test_db`.`Departments` (`Primary_key`, `Name`) VALUES (2, 'Marketing'); } INSERT INTO `test_db`.`Departments` (`Primary_key`, `Name`) VALUES (3, 'Production');} INSERT INTO `test_db`.`Departments` (`Primary_key`, `Name`) VALUES (4, 'Sales');} INSERT INTO `test_db`.`Departments` (`Primary_key`, `Name`) VALUES (5, 'Quality_control'); } COMMIT; } START TRANSACTION;} INSERT INTO `test_db`.`Employee` (`Primary_key`, `Name`, `Family`, `Age`, `Salary`, `Department_key`) VALUES (1, 'Simon', 'Kelly', 32, '50000', 1); } INSERT INTO `test_db`.`Employee` (`Primary_key`, `Name`, `Family`, `Age`, `Salary`, `Department_key`) VALUES (2, 'Sally', 'Ocran', 35, '43000', 3);} INSERT INTO `test_db`.`Employee` (`Primary_key`, `Name`, `Family`, `Age`, `Salary`, `Department_key`) VALUES (3, 'Ofelia', 'Oreiro', 37, '42000', 5);} INSERT INTO `test_db`.`Employee` (`Primary_key`, `Name`, `Family`, `Age`, `Salary`, `Department_key`) VALUES (4, 'Ontario', 'Anapa', 40, '41000', 1); } INSERT INTO `test_db`.`Employee` (`Primary_key`, `Name`, `Family`, `Age`, `Salary`, `Department_key`) VALUES (5, 'Maxim', 'Petrunin', 41, '40000', 2); } COMMIT; } |
Что касается Delphi, то внутри процедуры FormCreate, я создал такой код…(это отрывок кода… для иллюстрации идеи…)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
var DB:TStringlist; sqlstring:string; bufstring:string; f:textfile; command:string; ///////Создаем таблицы для тестовой БД... /////Читаем текстовый файл скрипта в строку... sqlstring:=''; AssignFile(f,'C:\Users\Stas\YandexDisk\7 DELPHI\MyStudyProjects\46 MySQL Connection\TablesCreation2.txt'); reset(f); while not EOF(f) do begin readln(f,bufstring); sqlstring:=sqlstring+bufstring; end; db:=tstringgrid.create; //Чистим переменную db:tstringgrid и command:tstring; db.Clear; command:=''; //Нарезаем строку sql отдельными инструкциями... for i := 1 to length(sqlstring) do begin if (sqlstring[i]<>#125) then command:=command+sqlstring[i]; if sqlstring[i]=#125 then begin db.Add(command); command:=''; end; end; //"Скармливаем инструкции", создаем таблицы... for i := 0 to db.Count-1 do begin sqlquery1.SQL.Clear; sqlquery1.SQL.Add(db[i]); sqlquery1.ExecSQL; // ExecSQL, а не Open, так как сервер ничего не должен возвращать в наших запросах end; |
Проверяем работу программы
В Delphi, я оформил всё следующим образом…
Улучшенное решение
Чтобы не мудрить с вставкой своего разделителя, я оптимизировал код под разделитель -точка с запятой, потому что он стандартный в mysql, теперь не нужно в текстовом файле проставлять свои разделители… получилось так…
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
/////Читаем текстовый файл скрипта в строку... sqlstring:=''; AssignFile(f,'C:\Users\Stas\YandexDisk\7 DELPHI\MyStudyProjects\47 MySQL Connection\TablesCreation3.txt'); reset(f); while not EOF(f) do begin readln(f,bufstring); sqlstring:=sqlstring+bufstring; end; //Чистим переменную db:tstringgrid и command:tstring; db.Clear; command:=''; //Нарезаем строку sql отдельными инструкциями... for i := 1 to length(sqlstring) do begin if (sqlstring[i]<>';') then command:=command+sqlstring[i]; if sqlstring[i]=';' then begin db.Add(command); command:=''; end; end; //Добавляем украденный разделитель... for i := 0 to db.Count-1 do begin db[i]:=db[i]+';'; end; //"Скармливаем инструкции", создаем таблицы... for i := 0 to db.Count-1 do begin sqlquery1.SQL.Clear; sqlquery1.SQL.Add(db[i]); sqlquery1.ExecSQL; // ExecSQL, а не Open, так как сервер ничего не должен возвращать в наших запросах end; |
1 |
Вывод - в дальнейшем, идею можно развивать, например написать компонент, который будет "проглатывать несколько инструкций", а не как сейчас - создавать отдельный текстовый файл... но это уже другая история. |