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

Скрипты Simple-Scada

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

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

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

Важно! Пример проекта таблицы рецептов доступен для скачивания по ссылке.

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

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

 

Рассмотрим подробнее интерфейс управления рецептами. В верхней части расположена таблица с именем "tblRecipes", которая содержит семь колонок. В данную таблицу будем выводить список всех рецептов и их свойства. У таблицы установим свойство "Строки" = 1, а также включим свойство "Выделение строки". Под таблицей расположены четыре кнопки:

Изменить - изменить выделенный рецепт (назначим имя btnRecChange).

Добавить - добавить новый рецепт (назначим имя btnRecAdd).

Удалить - удалить выделенный рецепт (назначим имя btnRecDelete).

Задействовать - (назначим имя btnRecApply) извлечь из выделенного рецепта количество каждого компонента и записать значения во внешние переменные (обычно это переменные контроллера).

 

Далее, в редакторе переменных создадим четыре переменные:

Переменная

Тип данных

Описание

vrK1, vrK2, vrK3

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

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

frm0_100

Single

Внутренняя переменная. Используется только для ограничения ввода в полях в форме добавления / редактирования рецепта

 

Редактирование и добавление рецептов реализуем через формы ввода - это позволит множеству пользователей одновременно работать с рецептами не мешая друг другу. Создадим новое окно с именем "wndAddRecipe" для добавления рецепта, активируем у него свойство "Использовать как форму ввода", а также свойство "Модальное" для блокировки остального интерфейса на время заполнения рецепта. Данное окно укажем в свойстве "Окно" кнопки "Добавить" интерфейса управления рецептами.

При помощи компонента "Поле" создадим необходимые поля ввода. Полям нужно задать понятные имена, т.к. затем они будут использоваться в скриптах. В данном примере используются следующие имена:

Имя - fldRecipeName.

Компонент 1, 2, 3 - fldRecipeComp1, fldRecipeComp2, fldRecipeComp3.

Описание - fldRecipeDesc.

Также, создадим две кнопки:

Добавить, с именем btnAddRecipeDone. В свойстве "Действие" выберем "Отправить форму".

Отменить, с именем btnAddRecipeCancel. В свойстве "Действие" выберем "Закрыть окно".

Получим такое окно:

 

По аналогии создадим окно с именем "wndChangeRecipe" для редактирования рецепта. Активируем у него свойство "Использовать как форму ввода", а также свойство "Модальное" для блокировки остального интерфейса на время изменения данных рецепта.

При помощи компонента "Поле" создадим необходимые поля ввода с понятными именами, например:

Имя - fldRecipeNameCh.

ID-рецепта - fldRecipeId.

Компонент 1, 2, 3 - fldRecipeCompCh1, fldRecipeCompCh2, fldRecipeCompCh3.

Описание - fldRecipeDescCh.

Также, создадим две кнопки:

Изменить, с именем btnChangeRecipeDone. В свойстве "Действие" выберем "Отправить форму".

Отменить, с именем btnChangeRecipeCancel. В свойстве "Действие" выберем "Закрыть окно".

Получим окно:

 

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

Важно! Если в разделе "SCHEMAS" нет нужной БД, то необходимо создать таблицы БД через настройки проекта: "Проект -> Настройки -> База данных -> Другие действия -> Создать БД и таблицы".

 

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

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

 

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

 

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

var
  aIndex: Integer;
begin
  aIndex := tblRecipes.RowIndex;  // получаем индекс выделенной строки в таблице рецептов
  if aIndex > -1 then             // если строка выбрана
    btnRecChange.OnClickEvent;    // вызываем скрипт клика по кнопке "ИЗМЕНИТЬ" рецепт
end.

Данный скрипт позволит вызывать редактирование рецепта двойным кликом по строке таблицы (минуя нажатие кнопки "Изменить"), что повысит удобство работы с рецептами.

Теперь создадим новый скрипт с типом "Глобальный модуль" и объявим константы, которыми будем отмечать SQL-запросы (пригодится для удобства в дальнейшем):

interface
 
const
  RECIPE_ADD = 1;    // SQL-запрос для добавления рецепта
  RECIPE_DEL = 2;    // SQL-запрос для удаления рецепта
  RECIPE_EDIT = 3;   // SQL-запрос для изменения рецепта
 
implementation
 
end.

 

Кнопка "Добавить"

Далее перейдем к кнопкам. Сначала реализуем работу кнопки "Добавить". Выделим кнопку и проверим, что в свойстве "Окно" указана созданная ранее форма "wndAddRecipe". Теперь, при нажатии кнопки будет отображаться форма добавления нового рецепта. Когда пользователь заполнит данные и нажмет кнопку "Добавить" в форме добавления рецепта, необходимо сформировать запрос к БД на создание новой строки в таблице "recipes". Для этого нужно создать скрипт с типом "Заполнена форма" и следующим кодом:

// Данный скрипт вызывается когда пользователь заполнил
// форму добавления нового рецепта
var
  aQuery: string;
begin
  // если заполнена форма добавления нового рецепта
  if FormData.Window = wndAddRecipe then
  begin
    if FormData[fldRecipeName].AsStr = '' then     // если имя рецепта не задано
    begin
      FormData.SetError('Введите имя рецепта.');   // выдаём ошибку
      Exit;                                        // прерываем выполнение
    end;
   
    // формируем запрос на вставку данных в таблицу `recipes`
    aQuery :=
      'INSERT IGNORE INTO `recipes` (`name`, `timestamp`, `k1`, `k2`, `k3`, `comment`)' +
      'VALUES (' +
        QuotedStr(FormData[fldRecipeName].AsStr) + ', ' +
        MySQLDateTime(Now, dttMillisecond) + ', ' +
        QuotedStr(FormData[fldRecipeComp1].AsStr) + ', ' +
        QuotedStr(FormData[fldRecipeComp2].AsStr) + ', ' +
        QuotedStr(FormData[fldRecipeComp3].AsStr) + ', ' +
        QuotedStr(FormData[fldRecipeDesc].AsStr) +
      ')';
   
    // отправляем запрос на выполнение с тегом = RECIPE_ADD
    RunSQL(aQuery, nil, RECIPE_ADD);
  end;
end.

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

 

Кнопка "Удалить"

Кнопка "Удалить" должна удалять из таблицы выделенный рецепт. Код на событие 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);
 
  // отправляем запрос на выполнение с тегом = RECIPE_DEL
  RunSQL(aQuery, nil, RECIPE_DEL);
end.

Сначала, с помощью функции "GetCell(0, tblRecipes.RowIndex)" пытаемся получить из таблицы tblRecipes ячейку из колонки "ID" в выделенной строке (следует учесть, что колонки и строки в таблице нумеруются с нуля, поэтому для колонки "ID" указываем индекс равный нулю). Далее идет проверка на nil. Если скада-система не сможет получить ячейку по заданному индексу, то вместо ячейки вернется значение равное nil. Такое возможно только в случае, когда пользователь еще не выделил строку в таблице или таблица пуста. Тогда выполнение скрипта прервется и будет выдано окно о необходимости выбора рецепта. Если же ячейку из колонки "ID" в выделенной строке удалось получить, то можно выполнить удаление рецепта из таблицы БД по его ID. Отправляем запрос на исполнение с помощью процедуры RunSQL с тегом равным константе RECIPE_DEL, объявленной в глобальном модуле ранее.

 

Кнопка "Изменить"

Кнопка "Изменить" должна менять параметры ранее добавленного рецепта. При нажатии кнопки нужно взять значения из выделенной строки таблицы рецептов и записать их в соответствующие элементы формы редактирования рецепта (fldRecipeCompCh1, fldRecipeCompCh2 и т.д.), после чего отобразить форму на экране. Код на событие OnClick для кнопки "Изменить":

// Данный скрипт вызывается при нажатии кнопки изменения рецепта
var
  aIndex: Integer;
  aQuery: string;
begin
  aIndex := tblRecipes.RowIndex;  // получаем индекс выделенной строки в таблице рецептов
  if aIndex = -1 then             // если строка не выбрана
  begin                           // то выдаем предупреждение
    ShowMessageClient(GetClientName, 'Рецепт не выбран!''Сначала выберите рецепт ' +
      'который нужно изменить.');
    Exit;
  end;
 
  // заполняем форму ввода текущими значениями рецепта.
  // Значения берём из выделенной строки в таблице рецептов
  fldRecipeId.Text := tblRecipes.GetCell(0, aIndex).Text;
  fldRecipeNameCh.Text := tblRecipes.GetCell(1, aIndex).Text;
  fldRecipeCompCh1.Text := tblRecipes.GetCell(3, aIndex).Text;
  fldRecipeCompCh2.Text := tblRecipes.GetCell(4, aIndex).Text;
  fldRecipeCompCh3.Text := tblRecipes.GetCell(5, aIndex).Text;
  fldRecipeDescCh.Text := tblRecipes.GetCell(6, aIndex).Text;
 
  // выводим форму редактирования рецепта на экран
  wndChangeRecipe.ShowClient(GetClientName);
end.

После того, как пользователь отредактирует рецепт и нажмет кнопку "Изменить" в форме редактирования рецепта, необходимо сформировать запрос на обновление существующего рецепта в таблице БД "recipes". Для этого нужно создать новый скрипт с типом "Заполнена форма" и следующим кодом:

// Данный скрипт вызывается, когда пользователь заполнил
// форму изменения существующего рецепта
var
  aQuery: string;
begin
  // если заполнена форма изменения существующего рецепта
  if FormData.Window = wndChangeRecipe then
  begin
    if FormData[fldRecipeNameCh].AsStr = '' then   // если имя рецепта не задано
    begin
      FormData.SetError('Введите имя рецепта.');   // выдаём ошибку
      Exit;                                        // прерываем выполнение
    end;
 
    // формируем запрос на обновление записи рецепта с
    // заданным "id" в таблице "recipes"
    aQuery := 'UPDATE `recipes` SET ' +
      '`timestamp`=' + MySQLDateTime(Now, dttMillisecond) + ', ' +
      '`name`=' + QuotedStr(FormData[fldRecipeNameCh].AsStr) + ', ' +
      '`k1`=' + QuotedStr(FormData[fldRecipeCompCh1].AsStr) + ', ' +
      '`k2`=' + QuotedStr(FormData[fldRecipeCompCh2].AsStr) + ', ' +
      '`k3`=' + QuotedStr(FormData[fldRecipeCompCh3].AsStr) + ', ' +
      '`comment`=' + QuotedStr(FormData[fldRecipeDescCh].AsStr) +
      ' WHERE `id`=' + FormData[fldRecipeId].AsStr;
 
    // отправляем запрос на выполнение с тегом = RECIPE_EDIT
    RunSQL(aQuery, nil, RECIPE_EDIT);
  end;  
end.

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

 

Кнопка "Задействовать"

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

// Данный скрипт вызывается при нажатии кнопки "ЗАДЕЙСТВОВАТЬ"
var
  aIndex: Integer;
  aQuery: string;
begin
  aIndex := tblRecipes.RowIndex;  // получаем индекс выделенной строки в таблице рецептов
  if aIndex = -1 then             // если строка не выбрана,
  begin                           // то выдаем предупреждение 
    ShowMessageClient(GetClientName, 'Рецепт не выбран!''Сначала выберите рецепт который нужно задействовать.');
    Exit;
  end;
 
  // записываем значения (из выделенной строки таблицы рецептов) в переменные
  txtRecipeName.Text := tblRecipes.GetCell(1, aIndex).Text;
  vrK1.Value := StrToInt(tblRecipes.GetCell(3, aIndex).Text);
  vrK2.Value := StrToInt(tblRecipes.GetCell(4, aIndex).Text);
  vrK3.Value := StrToInt(tblRecipes.GetCell(5, aIndex).Text);
  txtRecipeName.Hint := tblRecipes.GetCell(6, aIndex).Text;
end.

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

 

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

// Данный скрипт вызывается каждый раз, когда был выполнен
// пользовательский SQL-запрос (вызванный через RunSQL)
 
begin
  // если был выполнен один из SQL-запросов, связанный с таблицей рецептов,
  // то выводим в таблицу актуальные данные
  case DataSet.Tag of
    RECIPE_ADD, RECIPE_DEL, RECIPE_EDIT:
      tblRecipes.RunSQL('SELECT * FROM `recipes`', tsSaveFixRow);
  end;
end.

В предыдущих скриптах мы выполняли запрос на добавление рецепта с тегом равным константе RECIPE_ADD. Запрос на удаление с тегом равным константе RECIPE_DEL. Запрос на обновление с тегом равным константе RECIPE_EDIT. Поэтому в данном скрипте выполняется проверка тега SQL-запроса и если он равен одной из констант (т.е. в таблице "recipes" был добавлен,  удален или изменен рецепт), то производим обновление таблицы рецептов, считывая из БД все данные из таблицы "recipes".

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

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

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