Easyflex Data validatie ds_bi_declaratieregels

Uit nader onderzoek op basis van consulting services blijkt het volgende:

Aantal rijen in januari

Bij het opvragen van het aantal rijen in januari middels onderstaande query komt het getal 5509:

select count(*)
from   range@datadictionary(8, 1) rge
join   DataService.BITool.Declaratieregels(jaar => 2024, week => rge.value) drl
on     month(drl.decldatum) = 1

Hierbij is de aanname gedaan dat de maand gelijk is aan de kalendermaand van de declaratiedatum (het veld decldatum). De periode qua weken is bewust breed genomen met 8 weken zodat er geen randgevallen zijn.

Echter, de volgende query op basis van tabelfunctieparameter maand gelijk aan 1 geeft aan dat er maar 5154 regels zijn:

select count(*)
from   DataService.BITool.Declaratieregels(jaar => 2024, maand => 1) drl

Er is dus een wezenlijk verschil in uitkomst.

Plaatsing 10558589

Een diepere analyse is uitgevoerd op basis van plaatsingsnummer uit Easyflex. Het plaatsingsnummer van de declaratie 116861652 van registratie 4280344 is 10558589 (bepaald via de bovenstaande queries).

Specifieke filtering op deze plaatsing via de tabelfunctieparameter plaatsingnummer geeft 16 rijen:

select drl.*
from   DataService.BITool.Declaratieregels(jaar => 2024, maand => 1, plaatsingnummer => 10558589) drl

maar zonder een tabelfunctieparameter met filtering alleen in de UniversalSQL-driver (dus client-side) komen er maar 15 rijen uit:

select drl.*
from   DataService.BITool.Declaratieregels(jaar => 2024, maand => 1) drl
where  plaatsingnummer = 10558589

Merk op dat de Easyflex UniversalSQL-driver op dit moment niet automatisch de where-clause omschrijft naar een tabelfunctieparameter zoals bijvoorbeeld bij Teamleader Focus wel vaak gebeurt (zie bijvoorbeeld Teamleader V2 performance improvements in server-side filtering of date columns).

Om uit te sluiten dat de where-clause niet juist geimplementeerd is in Invantive UniversalSQL, is ook de volgende variant uitgevoerd. Deze gaf echter 16 rijen retour, dus het client-side filter heeft geen merkbare invloed en het wel/niet meegeven van de tabelfunctieparameter wel:

select drl.*
from   DataService.BITool.Declaratieregels(jaar => 2024, maand => 1, plaatsingnummer => 10558589) drl
where  plaatsingnummer = 10558589

Paginering

Easyflex werkt met paginering. Indien paginering niet juist geimplementeerd is binnen de UniversalSQL-driver voor Easyflex dan zou dat kunnen leiden tot de gesignaleerde afwijking. Bij problemen met paginering zijn de twee meest waarschijnlijke oorzaken:

  • geen paginering gebruikt terwijl die er wel moet zijn,
  • gaten tussen pagina’s waar meestal 1 rij tussen pagina’s “verdwijnt”.

De paginagrootte van Easyflex voor de achterliggende API is 5000 (zie https://easyflex.atlassian.net/wiki/spaces/WEBDATAKLNT/pages/530219856/5.+BI-tool+dataservice+operaties).

Een code review van de Invantive UniversalSQL-driver voor Easyflex toont aan dat paginering aanwezig is. De driver geeft geen totenmet mee en altijd een vanaf plus de maximale paginagrootte (per API mogelijk anders).

Na ontvangst van een pagina met gegevens wordt gekeken of de hoeveelheid data overeenstemt met de maximale paginagrootte. Zo ja, dan wordt een volgende pagina opgehaald. Zo nee, dan is de dataset compleet.

Merk op dat de Invantive UniversalSQL-engine “streaming” is over de gehele keten zolang er geen groepsfuncties zijn (zoals count); er worden dus al rijen teruggegeven voordat de volledige dataset verzameld is.

Het aantal rijen voor maand 1 is 5154, terwijl er gemeten via de weekmethode 5509 uitkomen. Dit is ruim meer dan de maximale 5.000, dus dat duidt er op dat paginering plaatsvindt. Tenslotte kunnen er conform documentatie maar 5000 rijen per pagina terugkomen.

Het ontbrekende aantal van 355 rijen is significant meer dan het standaardgat dat soms in een algoritme optreedt doordat er inclusief of exclusief grenzen gewerkt wordt. In dat geval zou de afwijking 1 zijn; 5509 rijen zouden twee pagina’s behoeven, waardoor bij het verkeerd inclusief/exclusief werken 2 - 1 = 1 rij theoretisch zou kunnen wegvallen.

Een nader onderzoek van de daadwerkelijk uitgevoerde sessie I/O’s laat zien dat voor 5154 rijen echter maar een API-call nodig was via:

select call_safe_name
,      parameter_list
from   sessionios@datadictionary
where  data_container_alias = 'efx'
order
by     id desc

Mogelijk leidt het ontbreken van de parameter totenmet tot problemen. De parameter is gedocumenteerd als “O” (optioneel), maar het zou kunnen dat bij weglating het gedrag van de API-server niet de documentatie weerspiegelt.

Er blijken echter ook 5154 rijen geretourneerd te worden als specifiek het aantal van 5.000 meegegeven wordt in de query:

select /*+ http_disk_cache(false) http_memory_cache(false) */ 
       count(*)
from   DataService.BITool.Declaratieregels(jaar => 2024, maand => 1, totenmet => 5000) drl

Dat is eigenaardig.

Rij-aantallen

Door de totenmet te varieren is uiteindelijk een specifieke case gevonden via de volgende query:

select /*+ http_disk_cache(false) http_memory_cache(false) */ 
       *
from   DataService.BITool.Declaratieregels(jaar => 2024, maand => 3, totenmet => 31) drl
minus
select /*+ http_disk_cache(false) http_memory_cache(false) */ 
       *
from   DataService.BITool.Declaratieregels(jaar => 2024, maand => 3, totenmet => 30) drl

De verwachte uitkomst is 31 - 30 = 1 rij. Echter, er worden twee rijen geretourneerd die vrijwel identiek zijn. Het gaat bij twee regels voor beiden declid 375450208. Enkel de kolommen businessunit en businessunitnaam hebben een andere waarde. Dit is gecontroleerd doordat de volgende query maar 1 regel teruggeeft:

select /*+ http_disk_cache(false) http_memory_cache(false) */ 
       * except businessunit, businessunitnaam
from   DataService.BITool.Declaratieregels(jaar => 2024, maand => 3, totenmet => 31) drl
minus
select /*+ http_disk_cache(false) http_memory_cache(false) */ 
       * except businessunit, businessunitnaam
from   DataService.BITool.Declaratieregels(jaar => 2024, maand => 3, totenmet => 30) drl

Het valt hierbij op dat beide regels dezelfde waarde voor declid hebben. Dat is eigenaardig. Mogelijk is dat een hint naar de oorzaak.

Het aantal declaraties is bekeken via:

select count(distinct declid)
from   DataService.BITool.Declaratieregels(jaar => 2024, maand => 3, totenmet => 5000) drl

Dit aantal blijkt exact 5000 te zijn.

De volgende hypothese is dat het XML-formaat van de Easyflex SOAP API-server misschien verschillende XML-niveau’s kent, en dat UniversalSQL mogelijk het onderste niveau telt (waar dan 5149 regels uit volgen) en Easyflex het bovenste niveau (waar 5000 knopen zitten). Een voorbeeld daarvan is onderstaand weergegeven en is vergelijkbaar met boekstukkop met meerdere boekstukregels:

<root>
  <niveau1>
     <VELD1>waarde</VELD1>
     <niveau2><VELD2>waarde</VELD2></niveau2>
     <niveau2><VELD2>waarde</VELD2></niveau2>
  </niveau1>
</root>

Echter, het door Easyflex gehanteerde XML-formaat kent maar 1 niveau en is erg eenvoudig:

...
<ds_bi_declaratieregels_result>
<item><VELD1>waarde</VELD1>...</item>
<item><VELD1>waarde</VELD1>...</item>
<item><VELD1>waarde</VELD1>...</item>
</ds_bi_declaratieregels_result>
...

Al met al kan gesteld worden dat het volgende deel van de documentatie niet juist geformuleerd is:

totenmet - xsd:int - O - Eindpunt van te selecteren regels (max = 5000 per keer).

Duiding

Het woord “regels” zou kunnen duiden op “declaratieregelnummers”.

Mogelijkerwijs is deze fout historisch ontstaan omdat de documentatie een hint geeft dat er twee semantische betekenissen mogelijk zijn van declaratieregels:

  • Het is ook mogelijk dat meerdere businessunits zijn gekoppeld aan een plaatsing in Easyflex met een procentuele verdeling van omzet/kosten.
    Dan wordt per afzonderlijke businessunit een declaratieregel geretourneerd met daarin het idnr van een business unit en enkel die uren, kosten en omzet die horen bij dezelfde businessunit.

Het gevonden voorbeeld heeft verschillende businessunits, maar twee keer hetzelfde declaratienummer. Volgens bovenstaande tekst zijn dat er twee declaratieregels: een per afzonderlijke businessunit.

Uit de voorgaande zinsnede lijkt het alsof de per afzonderlijke businessunit geretourneerde <item/> ook een declaratieregel is, maar dan een ander dan gebruikt voor de telling.

Daarnaast zou het kunnen zijn dat het woord “regels” helemaal niet verwijst naar “declaratieregelnummers” of eventueel “declaratieregels” (variant 1) of “declaratieregels” (variant 2), maar gewoon naar regels uitvoerresultaat voor de paginering. Dit is bij verre het meest gebruikelijk - zo niet universeel - als industriestandaard.

Het blijft gokken; advies is om de leverancier van Easyflex de documentatie te laten bijstellen zodat:

  • de tenminste twee verschillende betekenissen van “declaratieregels” gesplitst of samengevoegd worden zodat de definities eenduidig zijn;
  • op vergelijkbare wijze de betekenis van alle voorkomens van “regels” te corrigeren door splitsing of samenvoeging;
  • duidelijk uit de documentatie volgt wat het aantal verwachte <item/> elementen per pagina is;
  • de werking van de software conform nieuwe documentatie is.

Zodra dit gebeurt is kan indien nog nodig de driver verbeterd worden.

Het uitgangspunt van de Invantive UniversalSQL-engine is dat aantoonbare correctheid bij resultaat zwaarder weegt dan werking uberhaupt. In de volgende release van Easyflex zal daarom een application control itgenefx026 opgenomen zijn dat bij retour van meer rijen dan verwacht een fatale fout optreedt.

Een regressietest is uitgevoerd voor alle Easyflex API’s. Het blijkt dat de volgende API’s onder hetzelfde euvel leiden (en dus meer rijen terug kunnen geven dan verwacht):