Пожалуйста, включите JavaScript для просмотра этого сайта.

Скрипты Simple-Scada

История: Примеры скриптов > Работа с БД

Таблица рецептов в БД

Пред. Вверх След. Еще

Пусть имеется проект в Simple-Scada и мы настроили подключение к СУБД MySQL к нашей базе данных с именем "my_database". Теперь в эту базу данных сохраняются сообщения, тренды проекта и т.д. Мы хотим добавить в БД новую таблицу для хранения рецептов и реализовать редактирование этой таблицы через скаду. Допустим у нас есть три компонента: K1, K2 и K3 и нужно чтобы при выборе рецепта количество каждого компонента считывалось из рецепта и записывалось в соответствующие переменные которые затем будут использованы в тех. процессе. Сначала организуем интерфейс проекта. Достаточно двух страниц. На первой странице разместили мнемосхему:

На мнемосхеме мы расположили три резервуара с уровнями, которые отображают количество каждого компонента. В верхней части расположили компонент "Текст", в который будем выводить имя задействованного рецепта. Вторая страница используется для добавления, редактирования и удаления рецептов, а также для просмотра списка созданных ранее рецептов:

В верхней части расположен компонент таблица с именем "tblRecipes". Содержит 7 колонок. В эту таблицу будем выводить список всех рецептов и их свойства. У этой таблицы мы изменили количество строк на 1, а также включили свойство "Выделение строки". Под таблицей расположены пять полей и четыре кнопки. Поля будут использоваться для добавления нового или редактирования выделенного рецепта. Кнопка "Сохранить" для сохранения изменений в выделенном рецепте. Кнопка "Добавить" для добавления нового рецепта. Кнопка "Удалить" для удаления выделенного рецепта. Кнопка "Задействовать" будет извлекать из выделенного рецепта количество каждого компонента и подставлять значения во внешние переменные (обычно это переменные контроллера). Всего в проекте будет использоваться 8 переменных:

 

Переменная

Тип данных

Описание

vrK1, vrK2, vrK3

Мы выбрали тип данных Single (вещественное число) для этих переменных, т.к. в данном примере в них мы будем записывать только вещественные числа.

В данном примере эти переменные виртуальные, но вы должны использовать вместо vrK1, vrK2, vrK3 реальные внешние переменные. У вас их может быть не обязательно три. Именно в эти переменные будет выполняться подстановка количества компонента из выбранного пользователем рецепта.

edtName

String (строка)

Виртуальная переменная. Будет использоваться только для редактирования имени выбранного рецепта.

edtK1, edtK2, edtK3

Тип данных должен соответствовать переменным vrK1, vrK2, vrK3

Виртуальные переменные. Будут использоваться только для редактирования количества соответствующего компонента в выбранном рецепте.

edtComment

String (строка)

Виртуальная переменная. Будет использоваться только для редактирования описания выбранного рецепта.

 

Осталось только связать переменные с объектами проекта. На странице "Мнемосхема" уровни в трёх резервуарах связываем с переменными vrK1, vrK2, vrK3 соответственно. На странице "Управление рецептами" связываем поле "Имя" с переменной edtName, поля "Компонент 1", "Компонент 2" и "Компонент 3" с переменными edtK1, edtK2, edtK3. Поле "Описание" связываем с переменной edtComment.  

Интерфейс проекта создан, переменные определены. Теперь можно создать таблицу для хранения рецептов в БД. Создадим её через "MySQL Workbench". Запускаем MySQL Workbench и в левой части окна, в разделе "SCHEMAS" видим нашу БД с именем "my_database". Раскрываем её и делаем клик правой кнопкой мыши на разделе "Tables" и в раскрывшемся меню выбираем "Create Table", как показано ниже:

 

Теперь заполняем структуру таблицы. Каждый рецепт помимо трёх компонентов К1, К2 и К3 будет иметь уникальный "id" (обязательно), имя, время создания/изменения и описание. Назовём таблицу "recipes" и создадим столбцы как показано ниже:

Имена колонок в дальнейшем мы будем использовать в SQL-запросах к таблице "recipes". Столбец "id" отмечен флажками PK (Primary Key - первичный ключ), NN (Not null - не нулевой) и AI (Auto increment - автоинкрементный, т.е. СУБД будет сама увеличивать его на единицу при добавлении новой записи в таблицу). Некоторые типы данных могут быть для вас новыми. В MySQL тип данных "Float" соответствует типу "Single", тип "Timestamp(0)" соответствует типу "DateTime", а "VARCHAR(60)" (число в скобках определяет максимальное количество символов) соответствует строковому типу данных "String". Теперь нажимаем кнопку "Apply" в нижней части окна. Откроется окно с кодом SQL-запроса, в котором снова нажимаем "Apply":

В финальном окне нажимаем "Finish". Теперь в списке таблиц БД можно увидеть только что созданную таблицу "recipes":

Далее все операции с этой таблицей будем выполнять из скады с помощью SQL-запросов. Закрываем MySQL Workbench и возвращаемся в редактор проектов. Сначала выделяем таблицу рецептов на странице "Управление рецептами". В скриптах мы будем неоднократно обращаться к этой таблице и нужно задать ей соответствующее имя. Мы дали ей имя "tblRecipes". У вас она должна называться также, иначе компиляция скриптов не пройдёт. На событие OnClick для таблицы "tblRecipes" пишем такой скрипт:

var
  aIndex: Integer;
  aCell: TM_TableCell;
begin
  aIndex := tblRecipes.RowIndex;
  { если пользователь выбрал рецепт, то отображаем
    параметры рецепта в полях под таблицей рецептов }
  if aIndex <> -1 then
  begin
    aCell := tblRecipes.GetCell(1, aIndex); // ячейка Имя
    edtName.Value := aCell.Text;
 
    aCell := tblRecipes.GetCell(3, aIndex); // ячейка К1
    edtK1.Value := StrToFloat(aCell.Text);
 
    aCell := tblRecipes.GetCell(4, aIndex); // ячейка К2
    edtK2.Value := StrToFloat(aCell.Text);
 
    aCell := tblRecipes.GetCell(5, aIndex); // ячейка К3
    edtK3.Value := StrToFloat(aCell.Text);
 
    aCell := tblRecipes.GetCell(6, aIndex); // ячейка Описание
    edtComment.Value := aCell.Text;
  end;
end.

В данном коде мы сначала получаем индекс выделенной строки в таблице через свойство таблицы RowIndex. Если ни одна строка не выбрана то индекс будет равен -1. Далее в коде, если строка выбрана, то мы получаем из этой строки некоторые параметры рецепта и подставляем их в поля под таблицей рецептов (а точнее в переменные, с которыми эти поля связаны). Колонки (как и строки) в таблице нумеруются с нуля, поэтому мы берём значение из колонок с номерами 1 (Имя), 3, 4, 5 (К1, К2. К3) и 6 (Описание). Для наглядности мы пронумеровали колонки с нуля и проиллюстрировали работу скрипта:

Теперь при выделении рецепта в таблице, данные рецепта будут подставляться в переменные с которыми связаны поля под таблицей рецептов. Переходим к кнопкам. Сначала реализуем работу кнопки "Добавить". При нажатии этой кнопки скада должна взять значения из пяти полей под таблицей рецептов и записать их в БД в таблицу "recipes". Т.к. эти поля связаны с виртуальными переменными "edtName", "edtK1", "edtK2", "edtK3", "edtComment", то в скриптах можно работать напрямую с этими переменными, как и в предыдущем скрипте. Выделяем кнопку "Добавить" и на событие OnClick пишем следующий код:

var
  aQuery: string;
begin
  { Если имя рецепта не задано, то прерываем выполнение скрипта
    и выдаём соответствующее сообщение }
  if edtName.AsStr.Trim = '' then
  begin
    ShowMessageClient(GetClientName, 'Введите имя!''Введите имя рецепта.');
    Exit;
  end;
 
  { Формируем запрос на вставку данных в таблицу `recipes` }
  aQuery := 'INSERT IGNORE INTO `recipes` (`name`, `timestamp`, `k1`, `k2`, `k3`, `comment`)' +
            'VALUES (' +
              QuotedStr(edtName.AsStr) + ', ' +
              MySQLDateTime(Now, dttMillisecond) + ', ' +
              QuotedStr(edtK1.AsStr) + ', ' +
              QuotedStr(edtK2.AsStr) + ', ' +
              QuotedStr(edtK3.AsStr) + ', ' +
              QuotedStr(edtComment.AsStr) +
            ')';
 
  { Отправляем запрос на выполнение с тегом = 1 }
  RunSQL(aQuery, nil1);
end.

Код очень прост. В верхней части кода проходит проверка имени рецепта. Если пользователь не ввёл имя рецепта, то скрипт прерывается и выдаётся сообщение о том что нужно ввести имя рецепта. Далее формируется запрос на вставку новой строки в БД в таблицу "recipes". Сначала в скобках через запятую перечисляются имена колонок в которые будет проходить вставка данных. Нужно отметить, что столбец "id" мы не указали, т.к. при создании таблицы сделали его автоинкрементным, т.е. СУБД будет автоматически наращивать его на единицу каждый раз. Затем после слова "VALUES (" в том же порядке и через запятую перечисляются данные которые будут вставлены в эти колонки. Чтобы исключить ошибки данные для вставки должны быть взяты в одинарные кавычки. Функция QuotedStr решает эту задачу, т.е. просто берёт текст в одинарные кавычки. Для вставки текущего времени в колонку "timestamp" используется функция MySQLDateTime которая автоматически конвертирует дату/время в подходящий для MySQL формат и с одинарными кавычками. В конце скрипта мы отправляем сформированный запрос на исполнение с помощью процедуры RunSQL с тегом равным 1 (тег пригодится нам позже).

Кнопка "Удалить" должна удалять из таблицы выделенный рецепт. Код на событие OnClick для кнопки "Удалить" должен быть таким:

var
  aQuery: string;
  aCell: TM_TableCell;
begin
  { получаем первую ячейку в выделенной строке таблицы tblRecipes }
  aCell := tblRecipes.GetCell(0, tblRecipes.RowIndex);
  { если ячейку не удалось получить, значит пользователь не выбрал строку
    с рецептом. Прерываем выполнение и выдаем предупреждение }
  if aCell = nil then
  begin
    ShowMessageClient(GetClientName, 'Рецепт не выбран!''Сначала выберите рецепт который нужно удалить.');
    Exit;
  end;
 
  { Формируем запрос на удаление рецепта из таблицы "recipes" }
  aQuery := 'DELETE FROM `recipes` WHERE `id`=' + QuotedStr(aCell.Text);
 
  { Отправляем запрос на выполнение }
  RunSQL(aQuery, nil2);
end.

Сначала с помощью функции "GetCell(0, tblRecipes.RowIndex)" мы пытаемся получить из таблицы tblRecipes ячейку из колонки "ID" в выделенной строке (не забывайте, что колонки и строки в таблице нумеруются с нуля, поэтому для колонки "ID" мы указываем индекс равный нулю). Далее идёт проверка на nil. Если скада не сможет получить ячейку с заданными "координатами", то вместо ячейки она вернёт значение равное nil. А это может произойти только в том случае, если пользователь ещё не выделил строку в таблице, или таблица пуста. В этом случае мы прерываем выполнение скрипта и выдаем окно о необходимости выбора рецепта. Если же удалось получить ячейку из колонки "ID" в выделенной строке, то можно выполнить удаление рецепта из таблицы БД используя id рецепта. Формируем запрос на удаление из таблицы "recipes" рецепта с данным ID и отправляем запрос на исполнение с помощью процедуры RunSQL с тегом равным 2.

Кнопка "Сохранить" должна менять параметры ранее добавленного рецепта. Код на событие OnClick для кнопки "Сохранить":

var
  aQuery: string;
  aCell: TM_TableCell;
begin
  aCell := tblRecipes.GetCell(0, tblRecipes.RowIndex);
  if aCell = nil then
  begin
    ShowMessageClient(GetClientName, 'Рецепт не выбран!''Сначала выберите рецепт в ' +
      'котором нужно сохранить изменения.');
    Exit;
  end;
 
  if edtName.AsStr.Trim = '' then
  begin
    ShowMessageClient(GetClientName, 'Введите имя!''Введите имя рецепта.');
    Exit;
  end;
 
  { Формируем запрос на обновление записи рецепта с
    заданным "id" в таблице "recipes" }
  aQuery := 'UPDATE `recipes` SET ' +
    '`timestamp`=' + MySQLDateTime(Now, dttMillisecond) + ', ' +
    '`name`=' + QuotedStr(edtName.AsStr) + ', ' +
    '`k1`=' + QuotedStr(edtK1.AsStr) + ', ' +
    '`k2`=' + QuotedStr(edtK2.AsStr) + ', ' +
    '`k3`=' + QuotedStr(edtK3.AsStr) + ', ' +
    '`comment`=' + QuotedStr(edtComment.AsStr) +
    ' WHERE `id`=' + QuotedStr(aCell.Text);
 
  { Отправляем запрос на выполнение }
  RunSQL(aQuery, nil3);
end.

В этом скрипте, как и в предыдущих, сначала проверяем что рецепт выбран и имя рецепта не пустое. Затем формируем запрос на обновление существующего рецепта в таблице "recipes". В запросе после слова "SET" нужно описать для каждой колонки (кроме "id") новое значение. В конце запроса после "WHERE" указывается, что обновить данные нужно только для рецепта с заданным "id". Запрос отправляется на исполнение процедурой RunSQL с тегом равным 3.

Кнопка "Задействовать" должна просто брать данные рецепта из полей под таблицей рецептов и подставлять их в реальные внешние переменные (в нашем случае это vrK1, vrK2, vrK3). Код для события OnClick кнопки "Задействовать":

begin
  { Если имя рецепта не задано, то прерываем выполнение скрипта
    и выдаём соответствующее сообщение }
  if edtName.AsStr.Trim = '' then
  begin
    ShowMessageClient(GetClientName, 'Введите имя!''Введите имя рецепта, или выберите рецепт в таблице.');
    Exit;
  end;
  
  txtRecipeName.Text := edtName.AsStr;
  vrK1.Value := edtK1.Value;
  vrK2.Value := edtK2.Value;
  vrK3.Value := edtK3.Value;
end.

В коде есть обращение к компоненту "txtRecipeName". Это компонент текст расположенный в верхней части на странице "Мнемосхема". В него мы выводим имя задействованного рецепта. Далее в скрипте выполняется подстановка значений из виртуальных переменных edtK1, edtK2, edtK3 (которые хранят параметры выбранного рецепта) во внешние переменные vrK1, vrK2, vrK3.

Скрипты для кнопок готовы, но мы не предусмотрели вывод рецептов в таблицу рецептов. Также таблица должна обновляться после добавления, редактирования или удаления рецептов. Переходим в меню "Проект -> Скрипты" и создаем новый скрипт с типом события "Выполнен SQL-запрос". Код скрипта должен быть таким:

begin
  case DataSet.Tag of
    123:
      tblRecipes.RunSQL('SELECT * FROM `recipes`', tsSaveFixRow);
  end;
end.

В предыдущих скриптах мы выполняли запрос на добавление рецепта с тегом равным 1. Запрос на удаление с тегом равным 2. Запрос на обновление с тегом равным 3. Поэтому в данном скрипте мы проверяем тег и если он равен 1, 2 или 3 (т.е. в таблицу "recipes" был добавлен рецепт, либо удалён, либо обновлён), то обновляем таблицу рецептов считывая из БД все данные из таблицы "recipes".

Также нужно один раз обновить таблицу рецептов после запуска проекта. Для этого в меню "Проект -> Скрипты" создаем новый скрипт с типом события "Полностью запущен" с тем же кодом на обновление таблицы рецептов:

begin
  tblRecipes.RunSQL('SELECT * FROM `recipes`', tsSaveFixRow);
end.

Проект готов.