Simple-Scada forum

Simple-Scada 2 => Ваши вопросы => Тема начата: alan54 от 30 Апреля 2019, 17:44:59

Название: Снова про массивы переменных
Отправлено: alan54 от 30 Апреля 2019, 17:44:59
Здравствуйте.
Эта тема уже поднималась, но заглохла, поэтому вынужден вернуться к ней снова.
Вот этим запросом:
Код: (sql)
SELECT date(timestamp), MAX(value) FROM oplax_sever_db.trends_data 
WHERE (ID=31) and (timestamp >= "2019-04-01") and (timestamp < "2019-05-01")
GROUP BY date(timestamp) order by timestamp
я получаю двумерный массив такого вида:
 
2019-04-01   712
2019-04-02   693
2019-04-03   544
2019-04-04   571
2019-04-05   706
2019-04-06   777
.............................

Как сохранить результаты запроса в некие глобальные переменные, из которых затем построить диаграмму (по оси Х дата, по оси Y максимальное значение счетчика)?
Сам сообразить не могу, прошу подсказки.
P.S. Тут пришла идея поместить результаты запроса не в массив, а в пользовательскую таблицу, и затем из нее построить диаграмму. Но использование массива мне кажется рациональнее. Только вот как?
Название: Re: Снова про массивы переменных
Отправлено: Simple-Scada от 07 Мая 2019, 16:17:07
Здравствуйте.

Для работы с результатом пользовательского SQL-запроса нужно использовать скрипт с типом события "Выполнен SQL-запрос". В таких скриптах результат запроса хранится в параметре DataSet (набор данных). Пройти по набору данных можно в цикле, вот так:
Код: (delphi)
begin
  { выполнять цикл пока не достигнем конца набора данных }
  while not DataSet.EOF do
  begin
    // здесь работаем с текущей строкой
    DataSet.Next;  // переходим на следующую строку
  end;
end.
А это тот же код, но добавлен проход по ячейкам каждой строки:
Код: (delphi)
var
  I: Integer;
  aStr: string;
begin
  { выполнять цикл пока не достигнем конца набора данных }
  while not DataSet.EOF do
  begin
    { проходим по каждой ячейке текущей строки }
    for I := 0 to DataSet.FieldCount - 1 do
      aStr := DataSet[I].AsStr;  // получаем значение текущей ячейки переведённое в строку и записываем его в переменную aStr.
 
    DataSet.Next;  // переходим на следующую строку
  end;
end.

Для записи значений в глобальные переменные придется писать "неудобный" код в котором записывать каждое значение в отдельную переменную, например в зависимости от значения счетчика цикла. Если переменные имеют однотипные имена, то код можно сделать достаточно простым если искать переменные по имени через GetVariableByName (https://simple-scada.com/help/script/getvariablebyname.html).

Цитировать
P.S. Тут пришла идея поместить результаты запроса не в массив, а в пользовательскую таблицу, и затем из нее построить диаграмму. Но использование массива мне кажется рациональнее. Только вот как?
Массив сначала придётся заполнить, т.е. поместить в него нужные переменные в нужном порядке, поэтому проще сразу выполнять запись напрямую в нужные переменные. В пользовательскую таблицу тоже можно сохранить результат, но нужно будет сначала сформировать SQL-запрос на вставку данных (INSERT INTO (http://www.mysql.ru/docs/man/INSERT.html)) в БД, что является довольно обширной задачей. Также нужно отметить что не получится увидеть тренд по списку значений X / Y в скаде, т.к. в меню трендов на оси X всегда расположено время. По списку значений X / Y можно построить тренд (диаграмму) только в отчетах.
Название: Re: Снова про массивы переменных
Отправлено: alan54 от 07 Мая 2019, 16:37:20
Для записи значений в глобальные переменные придется писать "неудобный" код в котором записывать каждое значение в отдельную переменную, например в зависимости от значения счетчика цикла. Если переменные имеют однотипные имена, то код можно сделать достаточно простым если искать переменные по имени через GetVariableByName (https://simple-scada.com/help/script/getvariablebyname.html).
Что тогда представляют собой переменные типа Boolean Array, Byte Array, Word Array и т.п.?
Название: Re: Снова про массивы переменных
Отправлено: alan54 от 07 Мая 2019, 16:45:55
В пользовательскую таблицу тоже можно сохранить результат, но нужно будет сначала сформировать SQL-запрос на вставку данных (INSERT INTO (http://www.mysql.ru/docs/man/INSERT.html)) в БД, что является довольно обширной задачей. Также нужно отметить что не получится увидеть тренд по списку значений X / Y в скаде, т.к. в меню трендов на оси X всегда расположено время. По списку значений X / Y можно построить тренд (диаграмму) только в отчетах.
Вы так долго не отвечали, что отчет из пользовательской таблицы я уже сделал:
Код: (delphi)
procedure btnGetChartPerform_OnClick(Sender:TM_Control)
var
  aQuery: string;
  aFrom, aTo: string;
  aShift:  integer;
begin
  vrTimeBegin.Value := RecodeTime(vrTimeBegin.AsDateTime, 0, 0, 0, 0);
  aFrom := MySQLDateTime(vrTimeBegin.AsDateTime, dttHour);
  aTo :=  MySQLDateTime(RecodeTime(vrTimeEnd.AsDateTime + 1, 0, 0, 0, 0), dttHour);
  aQuery := 'REPLACE INTO oplax_sever_db.report_values ' +
    'SELECT date(a.timestamp), round(max(a.value/12)), b.shift FROM oplax_sever_db.trends_data a ' +
    'LEFT JOIN oplax_sever_db.schedule b ON date(a.timestamp) = (b.date) ' +
    'WHERE (date(a.Timestamp) >= ' + aFrom + ') AND (date(a.timestamp) < ' + aTo + ') AND (a.ID=31) ' +
    'GROUP BY date(a.timestamp) ORDER BY date(a.timestamp)';
  aShift := vrShift_2.AsInt;
  if aShift = 0 then
    aQuery := aQuery + ';'
  else
    begin
      aQuery := aQuery + ' AND (b.shift = ' + IntToStr(aShift) + ');';
      vrShiftStr.Value := 'Бригада № ' + IntToStr(vrShift_2.Value);
    end;
  RunSQL(aQuery, nil, 11);
  ReportView(GetClientName, 'Диаграмма_производительности');
end.
Все работает, но мне не нравится, что при первом клике на кнопку просмотра отчета выходит старый отчет (как будто новый не успевает сформироваться), приходится кликать еще раз. Это нормально?
Название: Re: Снова про массивы переменных
Отправлено: Simple-Scada от 07 Мая 2019, 17:41:48
Цитировать
Все работает, но мне не нравится, что при первом клике на кнопку просмотра отчета выходит старый отчет (как будто новый не успевает сформироваться), приходится кликать еще раз.
Может быть у Вас отчет строится долго и Вы не дожидаясь нажимаете кнопку построения снова? Тогда возможна ситуация когда второй отчет заменит первый.
Если только один раз кликнуть кнопку построения отчета и ждать, то отчет не строится? Или строится через длительное время? Если не строится, то есть ли какие-нибудь предупреждения в журнале сервера (https://simple-scada.com/help/manual/server-journal.html)?
Название: Re: Снова про массивы переменных
Отправлено: alan54 от 07 Мая 2019, 21:05:40
Может быть у Вас отчет строится долго и Вы не дожидаясь нажимаете кнопку построения снова? Тогда возможна ситуация когда второй отчет заменит первый.
Если только один раз кликнуть кнопку построения отчета и ждать, то отчет не строится? Или строится через длительное время?
Мне кажется, что пользовательская таблица по результату запроса просто не успевает сформироваться, и на просмотр выходит старый отчет. Какой командой можно сделать небольшую задержку перед ReportView?
Название: Re: Снова про массивы переменных
Отправлено: Simple-Scada от 08 Мая 2019, 11:08:59
Цитировать
Мне кажется, что пользовательская таблица по результату запроса просто не успевает сформироваться
Да, верно, скорее всего так и происходит, отчет успевает построиться со старыми данными, и уже после его построения происходит обновление пользовательской таблицы.

У Вас пользовательская таблица формируется в процедуре btnGetChartPerform_OnClick через команду:
Код: (delphi)
RunSQL(aQuery, nil, 11);  // запрос помечается тегом равным 11 
Нельзя назвать какое-то определённое время за которое выполнится RunSQL, скорость её выполнения зависит от нагрузки на СУБД и производительности ПК в момент вызова RunSQL. Поэтому фиксированная задержка перед построением отчета будет неправильным решением. Правильнее будет создать отдельный скрипт с типом события "Выполнен SQL-запрос" (https://simple-scada.com/help/script/sql-query-completed.html) и в нём вызывать построение отчета, когда пользовательская таблица уже гарантированно обновлена. Только в промежутке между обновлением таблицы и построением отчета придётся сохранить имя клиента для которого строится отчет. Для этого можно создать внутреннюю переменную vrClientName типа string.
Код: (delphi)
procedure btnGetChartPerform_OnClick(Sender:TM_Control)
var
  aQuery: string;
  aFrom, aTo: string;
  aShift:  integer;
begin
  vrTimeBegin.Value := RecodeTime(vrTimeBegin.AsDateTime, 0, 0, 0, 0);
  aFrom := MySQLDateTime(vrTimeBegin.AsDateTime, dttHour);
  aTo :=  MySQLDateTime(RecodeTime(vrTimeEnd.AsDateTime + 1, 0, 0, 0, 0), dttHour);
  aQuery := 'REPLACE INTO oplax_sever_db.report_values ' +
    'SELECT date(a.timestamp), round(max(a.value/12)), b.shift FROM oplax_sever_db.trends_data a ' +
    'LEFT JOIN oplax_sever_db.schedule b ON date(a.timestamp) = (b.date) ' +
    'WHERE (date(a.Timestamp) >= ' + aFrom + ') AND (date(a.timestamp) < ' + aTo + ') AND (a.ID=31) ' +
    'GROUP BY date(a.timestamp) ORDER BY date(a.timestamp)';
  aShift := vrShift_2.AsInt;
  if aShift = 0 then
    aQuery := aQuery + ';'
  else
    begin
      aQuery := aQuery + ' AND (b.shift = ' + IntToStr(aShift) + ');';
      vrShiftStr.Value := 'Бригада № ' + IntToStr(vrShift_2.Value);
    end;

  vrClientName.Value := GetClientName;  // запоминаем имя клиента для которого нужно будет построить отчет
  RunSQL(aQuery, nil, 11);   // отправляем запрос на выполнение с тегом 11
end.

Скрипт "Выполнен SQL-запрос":
Код: (delphi)
begin
  if DataSet.Tag = 11 then   // если запрос с тегом 11 выполнен, то строим отчет
    ReportView(vrClientName.AsStr, 'Диаграмма_производительности');
end.
Название: Re: Снова про массивы переменных
Отправлено: alan54 от 08 Мая 2019, 15:09:49
Спасибо, с отчетом из пользовательской таблицы все получилось. Но вот как с моим предыдущим вопросом насчет переменных типа Array? Вы так и не ответили...
Название: Re: Снова про массивы переменных
Отправлено: Simple-Scada от 08 Мая 2019, 15:29:43
Типы данных Boolean Array, Byte Array, Word Array и т.п. используются для чтения переменных-массивов c OPC-DA /OPC-UA серверов, т.е. только для внешних тегов. Если нужна внутренняя переменная-массив, то её можно объявить в скрипте:
Код: (delphi)
var
  aArray: array[1..10] of integer;
begin
  aArray[1] := 55;
end.
Название: Re: Снова про массивы переменных
Отправлено: alan54 от 10 Мая 2019, 22:11:52
Почему-то отчеты с сохранением имени клиента во внутреннюю переменную и ее передачей в скрипт "Выполнен SQL-запрос", где вызывается команда ReportView,  не работают в WEB-клиенте...
Название: Re: Снова про массивы переменных
Отправлено: Simple-Scada от 11 Мая 2019, 19:19:26
Здесь проблема. Выполняя ReportView из скрипта "Выполнен SQL-запрос" сервер знает только имя клиента (из переменной vrClientName), но не знает обычный это клиент или web, поэтому строит по-умолчанию для обычного клиента. А должен строить разные типы отчетов, для web это .pdf-документ, для обычных клиентов .mdc файл отчета. Подумаем как можно решить эту проблему. Возможно получится сделать сохранение всех данных о клиенте, а не только имени, чтобы сервер знал тип клиента. 
Название: Re: Снова про массивы переменных
Отправлено: Simple-Scada от 21 Мая 2019, 10:22:25
alan54, в обновлении 2.3.5.0 добавили новые функции с помощью которых можно решить описанную задачу. Вот как нужно изменить код:

В процедуре btnGetChartPerform_OnClick:
Код: (delphi)
RunSQL(aQuery, GetClientData, 11);  // помечаем запрос тегом равным 11 и передаём данные клиента
Скрипт "Выполнен SQL-запрос":
Код: (delphi)
var
  aClientData: TM_ClientData;
begin
  if DataSet.Tag = 11 then   // если запрос с тегом 11 выполнен, то строим отчет
    if DataSet.Sender is TM_ClientData then
    begin
      aClientData := DataSet.Sender as TM_ClientData;
      ReportViewEx(aClientData, 'Диаграмма_производительности');
    end;
end.
Промежуточная переменная vrClientName типа string больше не нужна.
Название: Re: Снова про массивы переменных
Отправлено: alan54 от 23 Октября 2019, 21:34:59
Я как то пропустил это сообщение, прошу прощения и большое спасибо!
Название: Re: Снова про массивы переменных
Отправлено: MaxHari от 28 Октября 2019, 02:03:07
Типы данных Boolean Array, Byte Array, Word Array и т.п. используются для чтения переменных-массивов c OPC-DA /OPC-UA серверов, т.е. только для внешних тегов. Если нужна внутренняя переменная-массив, то её можно объявить в скрипте:
Код: (delphi)
var
  aArray: array[1..10] of integer;
begin
  aArray[1] := 55;
end.

Каким образом можно заполнить массив определёнными значениями для передачи в combobox?

На delphi это делается, к примеру:
var ar : Array[1..4] of byte = (1,5,2,4);

Simple-Scada такой код компилирует, но при сохранении выдаёт ошибку "Cannot initialize local variables".

Как быть в этом случае?
Можете ли порекомендовать что-то для формирования combobox в зависимости от пользовательского выбора из другого combobox?
Название: Re: Снова про массивы переменных
Отправлено: Simple-Scada от 28 Октября 2019, 09:43:20
Компилятор Simple-Scada практически ничем не отличается от Delphi-компилятора. Если Вы в Delphi (кроме последней версии 10.3) таким образом попробуете инициализировать локальную переменную, то получите точно такую же ошибку: "Cannot initialize local variables", ведь инициализировать локальные переменные на этапе объявления нельзя. Инициализировать на этапе объявления можно только глобальные переменные.
В Simple-Scada всё то же самое. Если в меню скриптов создать "Глобальный модуль" (https://simple-scada.com/help/script/globalmodule.html), то в нём можно будет объявить глобальную переменную и сразу инициализировать её, например:
Код: (delphi)
interface

var
  ar: Array[1..4] of byte = (1,5,2,4);

implementation

end.
, затем эту переменную можно будет использовать в коде любого скрипта, т.к. она глобальная. А локальные переменные можно инициализировать только на этапе реализации (между begin и end), например так:
Код: (delphi)
var
  ar: Array[1..4] of byte;
begin
  // сначала инициализируем массив
  ar[1] := 1;
  ar[2] := 5;
  ar[3] := 2;
  ar[4] := 4;

  // далее работаем с массивом...
end.

Цитировать
Можете ли порекомендовать что-то для формирования combobox в зависимости от пользовательского выбора из другого combobox?
Через скрипты в любой момент можно очистить раскрывающийся список через метод ComboBox.Clear (https://simple-scada.com/help/script/cmbclear.html) и сразу добавить в список новые строки через ComboBox.AddItem (https://simple-scada.com/help/script/cmbadditem.html). Таким образом можно на ходу менять состав раскрывающихся списков.
Название: Re: Снова про массивы переменных
Отправлено: MaxHari от 28 Октября 2019, 10:53:49
Спасибо за объяснение.

Метод ComboBox.AddItem (https://simple-scada.com/help/script/cmbadditem.html) добавляет только по одной строке.
Можно ли как-то передавать в ComboBox весь массив или список, сформированный ранее?
Это возможно делать с помощью того же метода AddItem в цикле, но как тогда сформировать массив или список небольшого количества заданных значений, не используя "ручной" метод?

Код
ar[1] := 1;
ar[2] := 5;
...
Другими словами, что-то по типу инициализации локального массива ar := (1,5,2,4) на этапе реализации (это также ручной метод, с более удобной записью). В delphi не силён, в примере могут быть синтаксические ошибки.


И на эту же тему...
В описании к  "Глобальному модулю" (https://simple-scada.com/help/script/globalmodule.html) есть важное замечание по поводу объявления глобальных переменных строкового типа..

Цитировать
Важно! Использование таких глобальных переменных является плохим тоном. Вместо этого рекомендуется создавать глобальные переменные через меню редактирования переменных. Особенно не рекомендуется объявлять в глобальных модулях переменные строковых типов из-за особенностей их хранения в памяти.

Как быть в этом случае со строковыми переменными?
Название: Re: Снова про массивы переменных
Отправлено: Simple-Scada от 29 Октября 2019, 09:45:02
Цитировать
Можно ли как-то передавать в ComboBox весь массив или список, сформированный ранее?
Нет, для добавления строк в список через скрипты можно использовать только метод AddItem. При необходимости добавление можно сделать в цикле.

Цитировать
но как тогда сформировать массив или список небольшого количества заданных значений, не используя "ручной" метод?
Массивы бывают статические и динамические:
Код: (delphi)
var
  arStat: array[1..4] of byte;  // статический массив
  arDyn: array of integer;      // динамический массив
Про статические массивы мы писали выше: если такой массив объявлен в глобальном модуле, то его можно инициализировать сразу в объявлении. Если статический массив описан не в глобальном модуле, то для его инициализации нужно либо пройти в цикле по элементам массива и заполнить каждый элемент, либо, если значения массива уникальные, то отдельной строкой кода инициализировать каждый элемент массива.
А с динамическими массивами инициализацию можно выполнить и в одну строку, вот пример объявление + инициализация + проход в цикле:
Код: (delphi)
var
  i: integer;
  a: array of integer;
begin
  a := [7, 8, 9]; // инициализируем массив тремя значениями

  for i := 0 to Length(a) - 1 do  // проходим по элементам массива в цикле
  begin
    // здесь работаем с каждым элементом массива
  end;
end.

Цитировать
В описании к  "Глобальному модулю" есть важное замечание по поводу объявления глобальных переменных строкового типа..
Это рекомендация. Ответ написан в процитированном Вами тексте: "... Вместо этого рекомендуется создавать глобальные переменные через меню редактирования переменных.". В целом, если без строк в глобальном модуле можно обойтись, то лучше их в нём не использовать. Если других вариантов нет, то можно использовать строковые переменные.

Создаётся впечатление, что Вы хотите решить какую-то странную задачу средствами скады, не совсем понятно зачем нужны эти массивы и строковые переменные, если со строками в списке можно работать напрямую. И почему Вы хотите передать в список числа типа byte, когда список работает со строками, а не с числами. Возможно, если бы Вы подробно описали конкретную задачу, то мы бы предложили оптимальное решение.
Название: Re: Снова про массивы переменных
Отправлено: MaxHari от 29 Октября 2019, 16:50:13
Спасибо за ответ и напоминание, что со строками в списке можно работать напрямую.

Задача простая, но из-за долгого перерыва и недостатке знаний, завис на простых моментах.

Есть два ComboBox'а, один состоит из 4-х элементов. Для второго имеется n строковых элементов, по разному набору с разными значениями для каждого из 4-х вариантов. Необходимо заполнять второй ComboBox в зависимости от выбора в первом.

Решил вначале объявлением строкового массива в глобальном модуле, инициализации и заполнением его в 'Проект запущен' и использованием в коде.
После переделал на работу со строкой путём вытягивание необходимых элементов по заданным правилам. Сложновато, но работает.
Как альтернатива - можно создать 4 дополнительных ComboBox'а и переносить значения по заданным правилам во второй ComboBox. Или в таблицу занести всё и из неё тянуть. Не знаю, что будет по памяти/нагрузке на систему сложнее.

// Тип byte использовал просто в качестве примера.
Название: Re: Снова про массивы переменных
Отправлено: Simple-Scada от 30 Октября 2019, 10:18:45
Обычно это делают одним OnDatachange скриптом. Пример: у нас есть два раскрывающихся списка ComboBox1 и ComboBox2. Нужно формировать разный набор строк в списке ComboBox2, в зависимости от того что выбрано в списке ComboBox1.

Решение: выделяем список ComboBox1 и на событие OnDataChange назначаем следующий скрипт:
Код: (delphi)
begin
  ComboBox2.Clear;  // очищаем список ComboBox2
  case ComboBox1.AsInt of
    0: // выбрана первая строка в списке ComboBox1
    begin
      ComboBox2.AddItem('Красный');
      ComboBox2.AddItem('Синий');
      ComboBox2.AddItem('Зелёный');
    end;

    1: // выбрана вторая строка в списке ComboBox1
    begin
      ComboBox2.AddItem('Один');
      ComboBox2.AddItem('Два');
      ComboBox2.AddItem('Три');
    end;

    2: // выбрана третья строка в списке ComboBox1
    begin
      ComboBox2.AddItem('Вверх');
      ComboBox2.AddItem('Вниз');
      ComboBox2.AddItem('Влево');
      ComboBox2.AddItem('Вправо');
    end;
  end;
end.
Задача решена.
Название: Re: Снова про массивы переменных
Отправлено: MaxHari от 01 Ноября 2019, 10:07:33
Тот же способ и использовал.
Вместо ComboBox1.AsInt брал ComboBox1.ItemIndex, а элементы загнал в строку и через обработчик вытягивал нужные значения и добавлял их цикле через ComboBox2.AddItem.

Спасибо за помощь :-)