Добро пожаловать, Гость. Пожалуйста, войдите или зарегистрируйтесь.
Не получили письмо с кодом активации?

Официальный форум Simple-Scada.

Автор Тема: Снова про массивы переменных  (Прочитано 6276 раз)

alan54

  • Постоялец
  • ***
  • Сообщений: 145
    • Просмотр профиля
Снова про массивы переменных
« : 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. Тут пришла идея поместить результаты запроса не в массив, а в пользовательскую таблицу, и затем из нее построить диаграмму. Но использование массива мне кажется рациональнее. Только вот как?
« Изменён: 07 Мая 2019, 15:48:16 от Simple-Scada »

Simple-Scada

  • Администратор
  • *****
  • Сообщений: 3009
    • Просмотр профиля
    • Simple-Scada
Re: Снова про массивы переменных
« Ответ #1 : 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.

Цитировать
P.S. Тут пришла идея поместить результаты запроса не в массив, а в пользовательскую таблицу, и затем из нее построить диаграмму. Но использование массива мне кажется рациональнее. Только вот как?
Массив сначала придётся заполнить, т.е. поместить в него нужные переменные в нужном порядке, поэтому проще сразу выполнять запись напрямую в нужные переменные. В пользовательскую таблицу тоже можно сохранить результат, но нужно будет сначала сформировать SQL-запрос на вставку данных (INSERT INTO) в БД, что является довольно обширной задачей. Также нужно отметить что не получится увидеть тренд по списку значений X / Y в скаде, т.к. в меню трендов на оси X всегда расположено время. По списку значений X / Y можно построить тренд (диаграмму) только в отчетах.
« Изменён: 07 Мая 2019, 16:18:43 от Simple-Scada »

alan54

  • Постоялец
  • ***
  • Сообщений: 145
    • Просмотр профиля
Re: Снова про массивы переменных
« Ответ #2 : 07 Мая 2019, 16:37:20 »
Для записи значений в глобальные переменные придется писать "неудобный" код в котором записывать каждое значение в отдельную переменную, например в зависимости от значения счетчика цикла. Если переменные имеют однотипные имена, то код можно сделать достаточно простым если искать переменные по имени через GetVariableByName.
Что тогда представляют собой переменные типа Boolean Array, Byte Array, Word Array и т.п.?

alan54

  • Постоялец
  • ***
  • Сообщений: 145
    • Просмотр профиля
Re: Снова про массивы переменных
« Ответ #3 : 07 Мая 2019, 16:45:55 »
В пользовательскую таблицу тоже можно сохранить результат, но нужно будет сначала сформировать SQL-запрос на вставку данных (INSERT INTO) в БД, что является довольно обширной задачей. Также нужно отметить что не получится увидеть тренд по списку значений 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.
Все работает, но мне не нравится, что при первом клике на кнопку просмотра отчета выходит старый отчет (как будто новый не успевает сформироваться), приходится кликать еще раз. Это нормально?
« Изменён: 07 Мая 2019, 17:08:10 от alan54 »

Simple-Scada

  • Администратор
  • *****
  • Сообщений: 3009
    • Просмотр профиля
    • Simple-Scada
Re: Снова про массивы переменных
« Ответ #4 : 07 Мая 2019, 17:41:48 »
Цитировать
Все работает, но мне не нравится, что при первом клике на кнопку просмотра отчета выходит старый отчет (как будто новый не успевает сформироваться), приходится кликать еще раз.
Может быть у Вас отчет строится долго и Вы не дожидаясь нажимаете кнопку построения снова? Тогда возможна ситуация когда второй отчет заменит первый.
Если только один раз кликнуть кнопку построения отчета и ждать, то отчет не строится? Или строится через длительное время? Если не строится, то есть ли какие-нибудь предупреждения в журнале сервера?

alan54

  • Постоялец
  • ***
  • Сообщений: 145
    • Просмотр профиля
Re: Снова про массивы переменных
« Ответ #5 : 07 Мая 2019, 21:05:40 »
Может быть у Вас отчет строится долго и Вы не дожидаясь нажимаете кнопку построения снова? Тогда возможна ситуация когда второй отчет заменит первый.
Если только один раз кликнуть кнопку построения отчета и ждать, то отчет не строится? Или строится через длительное время?
Мне кажется, что пользовательская таблица по результату запроса просто не успевает сформироваться, и на просмотр выходит старый отчет. Какой командой можно сделать небольшую задержку перед ReportView?
« Изменён: 07 Мая 2019, 21:08:39 от alan54 »

Simple-Scada

  • Администратор
  • *****
  • Сообщений: 3009
    • Просмотр профиля
    • Simple-Scada
Re: Снова про массивы переменных
« Ответ #6 : 08 Мая 2019, 11:08:59 »
Цитировать
Мне кажется, что пользовательская таблица по результату запроса просто не успевает сформироваться
Да, верно, скорее всего так и происходит, отчет успевает построиться со старыми данными, и уже после его построения происходит обновление пользовательской таблицы.

У Вас пользовательская таблица формируется в процедуре btnGetChartPerform_OnClick через команду:
Код: (delphi)
RunSQL(aQuery, nil, 11);  // запрос помечается тегом равным 11 
Нельзя назвать какое-то определённое время за которое выполнится RunSQL, скорость её выполнения зависит от нагрузки на СУБД и производительности ПК в момент вызова RunSQL. Поэтому фиксированная задержка перед построением отчета будет неправильным решением. Правильнее будет создать отдельный скрипт с типом события "Выполнен SQL-запрос" и в нём вызывать построение отчета, когда пользовательская таблица уже гарантированно обновлена. Только в промежутке между обновлением таблицы и построением отчета придётся сохранить имя клиента для которого строится отчет. Для этого можно создать внутреннюю переменную 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.

alan54

  • Постоялец
  • ***
  • Сообщений: 145
    • Просмотр профиля
Re: Снова про массивы переменных
« Ответ #7 : 08 Мая 2019, 15:09:49 »
Спасибо, с отчетом из пользовательской таблицы все получилось. Но вот как с моим предыдущим вопросом насчет переменных типа Array? Вы так и не ответили...
« Изменён: 08 Мая 2019, 15:10:55 от alan54 »

Simple-Scada

  • Администратор
  • *****
  • Сообщений: 3009
    • Просмотр профиля
    • Simple-Scada
Re: Снова про массивы переменных
« Ответ #8 : 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.

alan54

  • Постоялец
  • ***
  • Сообщений: 145
    • Просмотр профиля
Re: Снова про массивы переменных
« Ответ #9 : 10 Мая 2019, 22:11:52 »
Почему-то отчеты с сохранением имени клиента во внутреннюю переменную и ее передачей в скрипт "Выполнен SQL-запрос", где вызывается команда ReportView,  не работают в WEB-клиенте...

Simple-Scada

  • Администратор
  • *****
  • Сообщений: 3009
    • Просмотр профиля
    • Simple-Scada
Re: Снова про массивы переменных
« Ответ #10 : 11 Мая 2019, 19:19:26 »
Здесь проблема. Выполняя ReportView из скрипта "Выполнен SQL-запрос" сервер знает только имя клиента (из переменной vrClientName), но не знает обычный это клиент или web, поэтому строит по-умолчанию для обычного клиента. А должен строить разные типы отчетов, для web это .pdf-документ, для обычных клиентов .mdc файл отчета. Подумаем как можно решить эту проблему. Возможно получится сделать сохранение всех данных о клиенте, а не только имени, чтобы сервер знал тип клиента. 

Simple-Scada

  • Администратор
  • *****
  • Сообщений: 3009
    • Просмотр профиля
    • Simple-Scada
Re: Снова про массивы переменных
« Ответ #11 : 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 больше не нужна.

alan54

  • Постоялец
  • ***
  • Сообщений: 145
    • Просмотр профиля
Re: Снова про массивы переменных
« Ответ #12 : 23 Октября 2019, 21:34:59 »
Я как то пропустил это сообщение, прошу прощения и большое спасибо!

MaxHari

  • Новичок
  • *
  • Сообщений: 36
    • Просмотр профиля
Re: Снова про массивы переменных
« Ответ #13 : 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?

Simple-Scada

  • Администратор
  • *****
  • Сообщений: 3009
    • Просмотр профиля
    • Simple-Scada
Re: Снова про массивы переменных
« Ответ #14 : 28 Октября 2019, 09:43:20 »
Компилятор Simple-Scada практически ничем не отличается от Delphi-компилятора. Если Вы в Delphi (кроме последней версии 10.3) таким образом попробуете инициализировать локальную переменную, то получите точно такую же ошибку: "Cannot initialize local variables", ведь инициализировать локальные переменные на этапе объявления нельзя. Инициализировать на этапе объявления можно только глобальные переменные.
В Simple-Scada всё то же самое. Если в меню скриптов создать "Глобальный модуль", то в нём можно будет объявить глобальную переменную и сразу инициализировать её, например:
Код: (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 и сразу добавить в список новые строки через ComboBox.AddItem. Таким образом можно на ходу менять состав раскрывающихся списков.
« Изменён: 28 Октября 2019, 10:13:02 от Simple-Scada »