10.8.2026

Dátová pumba do DB

Dátová pumpa do DB

Dnes by som chcel ukázať, ako jednoducho sa dajú dostať údaje z D2000 do databázy.

Zadanie od zákazníka bolo jednoduché: Majú zoznam cca 30 meraných bodov, ktorých hodnoty potrebujú dostať do tabuľky v SQL databáze. Pričom:

  • Potrebujú hodnotu meraného bodu každých 15 minút, s pôvodnou časovou značkou. Ak sa teda naposledy menila pred hodinou, nebude zapísaná.
  • Ak je hodnota neplatná, do databázy sa nezapíše.

Najskôr sme si zo zákazníkom zadefinovali formát tabuľky v databáze. Tabuľka sensor_data bude mať stĺpce:

  • sensorid – Textový názov meraného bodu v D2000.
  • value – Hodnota meraného bodu.
  • time – Čas merania z komunikácie.

Obrázok 1 - Formát tabuľky sensor_data zobrazený v nástroji pgAdmin.

Podľa formátu sme navrhli Definíciu štruktúry SD.sensor_data s rovnakými názvami stĺpcov:

Obrázok 2 - Definícia štruktúry SD.sensor_data

Vytvorili sme objekt scada_data typu Databáza, zadali definované DSN a užívateľské meno.

Dôležitá poznámka: so zákazníkom sme sa dohodli, že časové značky budú v čase UTC+1 hod (čo zodpovedá slovenskému zimnému času). Výhoda oproti použitiu lokálneho času je, že sa nemusia riešiť nejednoznačné časy pri prechode letného času na zimný. Preto je na objekte scada_data nakonfigurované „Use monotonic time“ a offset 1 hodina.

Obrázok 3 - Objekt scada_data typu Databáza reprezentujúci spojenie na SQL databázu.

Ako SQL databázový server použil zákazník PostgreSQL. Na aplikačnom serveri D2000 bolo potrebné nakonfigurovať 64-bitové DSN (keďže bol použitý 64-bitový DBManager), ktorého meno (scada_data) je použité v konfigurácii objektu scada_data typu Databáza v D2000.

Obrázok 4 - Konfigurácia PostgreSQL DSN.

V pokročilých nastaveniach DSN (strana 2) je nutné nastaviť parameter „Level of rollback on errors“ na hodnotu „Statement“, aby zlyhanie jednej operácie v rámci transakcie nespôsobilo zlyhanie celej transakcie.

Obrázok 5 - Pokročilé nastavenia v konfigurácii PostgreSQL DSN.

No a chýba nám ešte objekt typu Tabuľka, ktorý bude reprezentovať samotnú tabuľku sensor_data v SQL databáze. Pri jeho konfigurácii sa využije zadefinovaná Definícia štruktúry. Názvy stĺpcov v tabuľke musia byť identické s názvami stĺpcov v Definícii štruktúry, pričom na poradí stĺpcov nezáleží.

Názov tabuľky je zadaný ako „.sensor_data“. Bodka na začiatku spôsobí, že DBManager nebude prefixovať názov tabuľky menom užívateľa (scada_user), keďže PostgreSQL oddeľuje užívateľov a schémy (na rozdiel od Oracle alebo Sybase SQL Anywhere).

Poznámka: PostgreSQL má špecifické správanie čo sa týka názvov stĺpcov, ak sú v zadané v úvodzovkách (citlivosť na malé/veľké písmená). Preto je odporúčané v prípade PostgreSQL spúšťať proces DBManager so štartovacím parametrom /NQ, aby nepoužíval úvodzovky (No Quotes).

Obrázok 6 - Konfigurácia objektu typu Tabuľka.

V zadaní bolo, že máme do databázy kopírovať hodnoty cca 30 meraných bodov. Aby sme ich v skripte nemuseli referencovať po jednom (a volať procedúru „pumpuj“ pre každý z nich), je výhodné pripojiť tieto merané body do štruktúrovanej premennej a následne v skripte prejsť všetky riadky štruktúrovanej premenej za použitia skriptu.

Takže vytvoríme si ešte jednu pomocnú definíciu štruktúry SD.sensor_list, ktorá bude mať stĺpec Object typu Objekt.

Obrázok 7 - Pomocná definícia štruktúry pre vytvorenie zoznamu meraných bodov.

Do štruktúry SV.sensor_list, ktorá využíva definíciu štruktúry SD.sensor_list, sme pripojili všetky požadované merané body.

Obrázok 8 - Merané body pripojené v štruktúre SV.sensor_list.

Posledným krokom je napísanie samotného ESL skriptu (eventu) E.data_pump, ktorý raz za 15 minút bude vkladať dáta do tabuľky sensor_data. Tento skript bude spúšťaný na zmenu minúty (systémový objekt Min).

Obrázok 9 - Telo skriptu E.data_pump na vkladanie dát do SQL databázy.

Čo si môžeme všimnúť?

  • Na riadku 9 je definícia intervalu (900 sekúnd, t.j. °15 minút).
  • Na riadkoch 11-12 je ukončenie, pokiaľ minúta nie je násobkom 15.
  • Na riadku 16 je nadbytočné orezanie času Min na 15 minút (pozostatok vývoja).
  • Na riadkoch 18-19 je definícia lokálnej štruktúry _dta s použitím definície štruktúry SD.sensor_data a jej zväčšenie na rovnaký rozmer, ako má štruktúra SV.sensor_list.
  • Na riadkoch 21-30 je cyklus napĺňania štruktúry _dta prejdením štruktúry SV.sensor_list. Pokiaľ je na príslušnom _i-tom riadku SV.sensor_list platná hodnota (riadok 22) a jej časová značka je novšia ako 15 minút (riadok 23), tak inkrementujeme pomocný index _idx (24) a naplníme ďalší riadok štruktúry _dta názvom pripojeného objektu, časom hodnoty a samotnou hodnotou (25-27).
  • Na riadkoch 32-33 je zmenšenie rozmeru štruktúry _dta (pokiaľ neboli naplnené všetky riadky a pomocný index _idx má menšiu hodnotu ako je rozmer SV.sensor_list).
  • No a nakoniec, na riadku 36 je vloženie všetkých hodnôt do tabuľky v databáze akciou DBS_INSERT. Vkladanie viacerých riadkov naraz je optimalizované a rýchlejšie ako po jednom.

Pre záujemcov prikladám ESL skript aj v textovej podobe 😊

;*********************************************************

;  DESCRIPTION: Data pump to PostgreSQL database

;  AUTHOR:      Humaj

;  LAST CHANGE: 2026 07 01

;*********************************************************

BEGIN

 PRAGMA "ENABLE_INOUT_BY_REF"

 INT  _intvl = 900                         ; size of interval (900 sec)

 IF %Mod(Min, _intvl/60) # 0 THEN          ; only export every 15 minutes

   RETURN

 ENDIF

 INT _i, _idx, _ret

 TIME _currTime = Min\TIM - %ModTime(Min\TIM, _intvl)  ; current time (aligned to 15 min)

 RECORD NOALIAS (SD.sensor_data) _dta

 REDIM _dta [SV.sensor_list\DIM]          ; resize to structure with I/O tags

 FOR _i RANGE SV.sensor_list DO_LOOP

   IF SV.sensor_list [_i]^Object\VLD THEN ; only valid value is copied to DB

     IF _currTime - SV.sensor_list [_i]^Object\TIM < _intvl THEN ; if it was change in last 15 minutes

       _idx := _idx + 1

       _dta[_idx]^sensorid := %HBJToStr(SV.sensor_list [_i]^Object\HBJ)

       _dta[_idx]^time     := SV.sensor_list [_i]^Object\TIM

       _dta[_idx]^value    := SV.sensor_list [_i]^Object

     ENDIF

   ENDIF

 END_LOOP

 IF _idx < SV.sensor_list\DIM THEN

   REDIM _dta [_idx]       ; reduce size of _dta to filled-in rows only

 ENDIF

 DBS_INSERT DB.sensor_data, _dta, _ret

END

Čo dodať?

Navrhnuté riešenie demonštruje niekoľko vecí:

  • Pomocou pripojenia meraných bodov (alebo aj iných objektov) do pomocnej štruktúry SV.sensor_list sa dosiahne nezávislosť kódu od dát. Pomocnú štruktúru je možné zväčšovať a pridávať do nej ďalšie D2000 objekty bez nutnosti zmeny v kóde. Zároveň je možné pomocou referenčnej integrity mapovať, že vytvorený skript E.data_pump používa štruktúru SV.sensor_list, ktorá používa pripojené merané body - ktoré nemôžu byť vymazané bez predchádzajúceho odpojenia zo štruktúry). Referenčná integrita teda zjednodušuje orientáciu v D2000 aplikácii a uľahčuje mapovanie dátových tokov.
  • D2000 má prepracovanú prácu s časom - na úrovni objektu Databáza je možné definovať, ako budú interpretované časy v databáze (lokálny čas alebo UTC čas s offsetom).
  • D2000 má optimalizovanú prácu s databázovými tabuľkami – napríklad vie vložiť/načítať viacero riadkov jedinou databázovou operáciou.
  • ESL skripty môžu obsahovať lokálne premenné, ktoré môžu byť aj štruktúrované, využívajúce existujúce definície štruktúry. Takýmto premenným je možné dynamicky meniť veľkosť (resize).

Čo som nedemonštroval, ale čitateľ si to môže vyskúšať sám – takto vytvorený ESL skript je možné v prostredí ESL editora komfortne ladiť – zobrazovať obsah lokálnych premenných, krokovať, nastavovať breakpointy – a to bez ohľadu na to, či je ESL skript vykonávaný na lokálnom alebo vzdialenom počítači.

V niektorom z budúcich blogov by som chcel ukázať ďalší skript, ktorý vie pracovať s históriou. Ďalšou požiadavkou zákazníka totiž bolo preniesť aj dáta z minulosti (ktoré sa nachádzajú v archívoch meraných bodov) a prípadne preniesť dáta za špecifikovanú dobu výpadku, ktorý môže byť spôsobený napríklad odstávkou servera s PostgreSQL databázou.

1.7.2026, Ing. Peter Humaj, www.ipesoft.com

Iné blogy