Autor Téma: SQLite: TFDSQLiteFunction využitie parametra Aggregated  (Přečteno 8610 krát)

Offline miroB

  • Guru
  • *****
  • Příspěvků: 628
  • Karma: 17
    • Verze Delphi: D1,2,3,4,7,2005 .. D Tokyo 10.2.3 Pro C/S
Zrejme neviem korektne použiť OnFinalize event.
V rámci OnFinalize síce vypočítam, čo potrebujem, ale neviem to dať ako Output (výsledok). Taký parameter tam nie je.
Na rozdiel od OnCalculate, kde Output k dispozícii je.
Je možné, že zle chápem funkcie s Aggregated=True;
Tu je kód. Výpočet sRslt prebehne správne, ale celkovo funkcia nefunguje.
Cieľ je náhrada SQLite : Group_Concat. Ale len s výpisom jedinečných argumentov
PS: hodne som googlil, ale márne
Kód: Delphi [Vybrat]
  1. ..
  2.     T_Group_concat_distinct = class( TObject ) // AUserData
  3.       dict        : TDictionary<String, Integer>;
  4.       chSeparator : Char;
  5.       sRslt       : String;
  6.     public
  7.       Constructor CREATE;  // dict        := TDictionary<String, Integer>.Create;
  8.     end;
  9. ..
  10.  
  11. procedure Tdm.fnConcatDistinctFinalize( AFunc: TSQLiteFunctionInstance; var AUserData: TObject);
  12. var
  13.   s : String;
  14. begin
  15.   with T_ConcatDistinct( AUserData ) do
  16.     if dict <> nil then
  17.       begin
  18.       sRslt := '';
  19.       for s in dict.Keys do
  20.         if sRslt = '' then
  21.           sRslt := s
  22.         else
  23.           sRslt := sRslt + chSeparator + s;
  24. // Sem teoreticky  Output.AsWideString =sRslt, Ale to nejde
  25.       dict.Free;
  26.       dict                := nil;
  27.       end;
  28. end;
Tu je on Calculate event:
Kód: Delphi [Vybrat]
  1. procedure Tdm.fnConcatDistinctCalculate( AFunc: TSQLiteFunctionInstance; var AUserData: TObject);
  2. var
  3.   iVal: Integer;
  4. begin
  5.   if AUserData = nil then // Prvý priechod funkciou
  6.     begin
  7.     AUserData := T_Group_concat_distinct.CREATE;
  8.     if AInputs[ 1 ].IsNull then
  9.       T_Group_concat_distinct( AUserData ).chSeparator := ','
  10.     else
  11.       T_Group_concat_distinct( AUserData ).chSeparator := AInputs[ 1 ].AsWideString[ 1 ];
  12.     end;
  13.   with T_Group_concat_distinct( AUserData ) do
  14.     if dict = nil then
  15.       AOutput.AsWideString := sRslt
  16.     else
  17.       if ( AInputs[ 0 ].IsNull = false ) and ( dict.ContainsKey( AInputs[ 0 ].AsWideString ) = False ) then
  18.         dict.Add( AInputs[ 0 ].AsWideString, 0 );
  19. end;
« Poslední změna: 28-03-2018, 16:11:17 od Miroslav Baláž »

99525

  • Host
Re:SQLite: TFDSQLiteFunction využitie parametra Aggregated
« Odpověď #1 kdy: 28-03-2018, 17:50:44 »
Kód: Delphi [Vybrat]
  1. procedure Tdm.fnConcatDistinctFinalize(AFunc: TSQLiteFunctionInstance; var AUserData: TObject);
  2. begin
  3.   ...
  4.   AFunc.Output.AsWideString := sRslt;
  5.   ...
  6. end;

A z OnCalculate odstran predavani vysledku. Ten se u agregacnich funkci predava az v OnFinalize.
« Poslední změna: 28-03-2018, 17:54:21 od 99525 »

Offline miroB

  • Guru
  • *****
  • Příspěvků: 628
  • Karma: 17
    • Verze Delphi: D1,2,3,4,7,2005 .. D Tokyo 10.2.3 Pro C/S
Re:SQLite: TFDSQLiteFunction využitie parametra Aggregated
« Odpověď #2 kdy: 28-03-2018, 18:22:59 »
Upravil som, ako si napísal. Odobral som aj  kód priradovania  v OnCalculate, Teraz je OnFinalize takto:

Kód: Delphi [Vybrat]
  1. ..
  2.   with T_Group_concat_distinct( AUserData ) do
  3.     begin
  4.     if dict <> nil then
  5.       begin
  6.       sRslt     := '';
  7.       for s in dict.Keys do
  8.         if sRslt = '' then
  9.           sRslt := s
  10.         else
  11.           sRslt := sRslt + chSeparator + s;
  12. //      AFunc.Output.AsWideString := sRslt;
  13.       dict.Free;
  14.       dict      := nil;
  15.       exit;
  16.       end;
  17.    AFunc.Output.AsWideString := sRslt;  // REM varianta 2.
  18.    end;
  19. ..
Výsledok je prázdny. Prázdny je, aj keď sa vymenia tie zaremovania.
Inak cez to priradenie prechádza v tomto tvare raz. A sRslt tam má správny zoznam hodnôt. Lenže vo výpise výsledku funkcie nie je nič. Zrejme NULL.
Aktuálne v OnCalculate:
Kód: Delphi [Vybrat]
  1. ..
  2.   with T_Group_concat_distinct( AUserData ) do
  3.     if dict <> nil then
  4.       if ( AInputs[ 0 ].IsNull = false ) and ( dict.ContainsKey( AInputs[ 0 ].AsWideString ) = False ) then
  5.         dict.Add( AInputs[ 0 ].AsWideString, 0 );
« Poslední změna: 28-03-2018, 18:26:29 od Miroslav Baláž »

Offline miroB

  • Guru
  • *****
  • Příspěvků: 628
  • Karma: 17
    • Verze Delphi: D1,2,3,4,7,2005 .. D Tokyo 10.2.3 Pro C/S
Re:SQLite: TFDSQLiteFunction využitie parametra Aggregated
« Odpověď #3 kdy: 28-03-2018, 18:43:59 »
Vytvorenie tabulky a kontrolný SELECT:
Kód: MySQL [Vybrat]
  1. Create table ignorujZastavky ( Passport Int32, Názov Text( 24 ), Číslo Int16 );
  2. INSERT INTO ignorujZastavky ( Passport, Názov, Číslo ) VALUES    -- 18,631,910,911,912,913
  3. ( NULL, 'Voz Trnávka',18 ),
  4. ( NULL, 'Voz Petržalka',631 ),
  5. ( 91002, 'Krasňany',910 ),
  6. ( 91101, 'Petržalka', 911 ),
  7. ( 91201, 'Trnávka1', 912 ),
  8. ( 91202, 'Trnávka2', 912 ),
  9. ( 91301, 'Hroboňova', 913 );
  10. select group_concat_distinct( Číslo , ',' ) AS [zoznam::Text(120) ] from ignorujZastavky;

99528

  • Host
Re:SQLite: TFDSQLiteFunction využitie parametra Aggregated
« Odpověď #4 kdy: 28-03-2018, 19:07:23 »
Excellent
Rated 1 time
Hm. Zvlastni. SQLite vola ten finalizacni callback s jinym kontextem (sqlite3_context). Tohle je nicmene chyba FireDAC. Ve zkratce, parametr context je v nasledujicich metodach pro agregacni funkce ruzny, no a protoze FireDAC ignoruje jeho predani, nemuzes tak vratit pri finalizaci hodnotu:

Kód: Delphi [Vybrat]
  1. procedure TSQLiteFunctionInstance.DoCalculate(context: psqlite3_context;
  2.   nargs: Integer; args: ppsqlite3_value);
  3. begin
  4.   ...
  5. end;
  6.  
  7. procedure TSQLiteFunctionInstance.DoFinalize(context: psqlite3_context);
  8. begin
  9.   Func.DoFinalize(Self); // o kontext se s timto kodem prijde
  10. end;

Rychla oprava by mohla byt napr. (modul FireDAC.Phys.SQLiteWrapper):

Kód: Delphi [Vybrat]
  1. procedure TSQLiteFunctionInstance.DoFinalize(context: psqlite3_context);
  2. begin
  3.   FOutput.Handle := context; // je treba uchovat kontext, jinak nebude mozne vratit hodnotu
  4.   Func.DoFinalize(Self);
  5. end;

Zkousel jsem predani agregacniho kontextu (uzivatelska data alokovana SQLite pomoci funkce sqlite3_aggregate_context) a predani probehne spravne, cili ty ruzne kontexty mezi kalkulaci a finalizaci jsou ze strany SQLite umyslem. Jen se na to nemyslelo ve FireDAC (nejspis nikdo netestoval).

Jinak to ze se ma hodnota predavat pri finalizaci vim z kodu SQLite. Podivej se napr. na funkci SUM, konkretne jeji finalizacni callback sumFinalize:

Kód: C++ [Vybrat]
  1. static void sumFinalize(sqlite3_context *context){
  2.   SumCtx *p;
  3.   p = sqlite3_aggregate_context(context, 0);
  4.   if( p && p->cnt>0 ){
  5.     if( p->overflow ){
  6.       sqlite3_result_error(context,"integer overflow",-1); /* <- signalizace chyby */
  7.     }else if( p->approx ){
  8.       sqlite3_result_double(context, p->rSum); /* <- predani vysledku */
  9.     }else{
  10.       sqlite3_result_int64(context, p->iSum); /* <- predani vysledku */
  11.     }
  12.   }
  13. }
« Poslední změna: 28-03-2018, 19:21:08 od 99528 »

99529

  • Host
Re:SQLite: TFDSQLiteFunction využitie parametra Aggregated
« Odpověď #5 kdy: 28-03-2018, 19:13:47 »
A mozna by se i sluselo rozsirit udalost OnFinalize o rozvinuti property Output, tedy s prototypem (na to uz je ale nejspis kvuli zpetne kompatibilite pozde):

Kód: Delphi [Vybrat]
  1. procedure (AFunc: TSQLiteFunctionInstance; AOutput: TSQLiteOutput; var AUserData: TObject)

On totiz ten finalizacni callback slouzi k predani vysledku (a uvolneni uzivatelskych dat), coz by prave rozvinutim property Output uzivatele vizualne pobidlo k predani hodnoty. Neni to vsak prekazkou. Jak uz jsem psal, hodnota se da predat i zapisem:

Kód: Delphi [Vybrat]
  1. AFunc.Output.AsWideString := sRslt;

Pro to je vsak treba opravit FireDAC.
« Poslední změna: 28-03-2018, 19:25:22 od 99529 »

Offline miroB

  • Guru
  • *****
  • Příspěvků: 628
  • Karma: 17
    • Verze Delphi: D1,2,3,4,7,2005 .. D Tokyo 10.2.3 Pro C/S
Re:SQLite: TFDSQLiteFunction využitie parametra Aggregated
« Odpověď #6 kdy: 28-03-2018, 19:33:07 »
Excellent
Rated 1 time
Hm. Zvlastni. SQLite vola ten finalizacni callback s jinym kontextem (sqlite3_context). Tohle je nicmene chyba FireDAC. Ve zkratce, parametr context je v nasledujicich metodach pro agregacni funkce ruzny, no a protoze FireDAC ignoruje jeho predani, nemuzes tak vratit pri finalizaci hodnotu:
..
Rychla oprava by mohla byt napr. (modul FireDAC.Phys.SQLiteWrapper):
Kód: Delphi [Vybrat]
  1. procedure TSQLiteFunctionInstance.DoFinalize(context: psqlite3_context);
  2. begin
  3.   FOutput.Handle := context; // je treba uchovat kontext, jinak nebude mozne vratit hodnotu
  4.   Func.DoFinalize(Self);
  5. end;
Skvelé..  Doplnil som do FireDAC.Phys.SQLiteWrapper riadok 3 z tvojho kódu.  A šlape to:)
Teraz už budú agregačné UDF funkcie fungovať. Je zaujímavé, že ani google o využívaní UDF SQLite / TFDSQLiteFunction s nastaveným parametrom Aggregate nepíše úplne nič..
Samozrejme tým pádom ani návody FireDAC/Embarcadero.
Ďakujem.

Offline miroB

  • Guru
  • *****
  • Příspěvků: 628
  • Karma: 17
    • Verze Delphi: D1,2,3,4,7,2005 .. D Tokyo 10.2.3 Pro C/S
Re:SQLite: TFDSQLiteFunction využitie parametra Aggregated
« Odpověď #7 kdy: 28-03-2018, 19:36:26 »
..
On totiz ten finalizacni callback slouzi k predani vysledku (a uvolneni uzivatelskych dat), coz by prave rozvinutim property Output uzivatele vizualne pobidlo k predani hodnoty. Neni to vsak prekazkou. Jak uz jsem psal, hodnota se da predat i zapisem:
Kód: Delphi [Vybrat]
  1. AFunc.Output.AsWideString := sRslt;
..
Práveže, toto som skúšal tiež, lenže to nefungovalo, tak som skúšal kadečo. Stratil som s tým teda ozaj dosť času. Uff..
Ale ten tvoj návrh na doplnenie handleru, ten to teda vyriešil..

99532

  • Host
Re:SQLite: TFDSQLiteFunction využitie parametra Aggregated
« Odpověď #8 kdy: 28-03-2018, 20:10:13 »
Neni zac! ;) Tady je report RSP-20227.

Offline miroB

  • Guru
  • *****
  • Příspěvků: 628
  • Karma: 17
    • Verze Delphi: D1,2,3,4,7,2005 .. D Tokyo 10.2.3 Pro C/S
Re:SQLite: TFDSQLiteFunction využitie parametra Aggregated
« Odpověď #9 kdy: 06-04-2018, 15:06:20 »
Trochu problém, toto riešenie funguje len pre jednu inštanciu funkcie v rámci SQL.
Viacnásobné požitie tej iste funkcie v SQL vyššie uvedeným spôsobom nefunguje  :
Kód: MySQL [Vybrat]
  1. SELECT MojTotal(pole1), MojTotal(pole2) FROM xy
Pre funkcie, ktoré nie sú aggregate, by sa dalo využiť tvoje: AOutput.Handle + Dictionary.
Jednoduchým skombinovaním dvoch riešení to nejde.
Link na tvoj príspevok, kde navrhuješ postup pre viac inštancií jednej funkcie:
Lenže nové Output.Handle už neumožní vyhľadanie hodnoty v Dictionary, pretože sa zmeni riešením pre Aggregate.
Bijú sa tieto dva čiastkové kódy:
Kód: Delphi [Vybrat]
  1. if not FResults.TryGetValue(AOutput.Handle, Value) then
  2.     Value := AInputs[0].AsInteger
  3.   else
  4.     Value := Value + AInputs[0].AsInteger;
  5.   FResults.AddOrSetValue(AOutput.Handle, Value);
V rámci opravy Finalize, sa priradí iný handle, než je v Dictionary:
Kód: Delphi [Vybrat]
  1. procedure TSQLiteFunctionInstance.DoFinalize(context: psqlite3_context);
  2. begin
  3.   FOutput.Handle := context; // je treba uchovat kontext, jinak nebude mozne vratit hodnotu
  4.   Func.DoFinalize(Self);
  5. end;
[/quote]
Musel som použiť ešte ďalšiu property FHandleOld, pre zachovanie FHandle. Potom to už funguje.
« Poslední změna: 06-04-2018, 15:19:35 od Miroslav Baláž »

99598

  • Host
Re:SQLite: TFDSQLiteFunction využitie parametra Aggregated
« Odpověď #10 kdy: 06-04-2018, 15:22:26 »
Lenže nové Output.Handle už neumožní vyhľadanie hodnoty v Dictionary, pretože sa zmeni riešením pre Aggregate.
Musel som použiť ešte ďalšiu property FHandleOld, pre zachovanie FHandle. Potom to už funguje.

Ten navrzeny fix je spravne. Ne pro Tebe, protoze se snazis rozlisit jednotliva volani funkci v ramci SQL prikazu. To FireDAC nema.

Pokud pocitas v OnCalculate v Dictionary<AOutput.Handle, Value>, pak zapominas na to, ze ten finalizacni kontext dopredu neznas, stejne jako on nezna kontext krokovy. Jakym zpusobem chces priradit pri finalizaci vysledek? Kterou polozku z dictionary s vysledky pro dane volani vyberes?

Reseni samozrejme existuje. SQLite pro tento pripad pouziva sqlite3_aggregate_context. Funguje tak, ze pri prvnim volani v krokovem callbacku sqlite3_aggregate_context engine alokuje blok pameti a vrati Ti na nej ukazatel. S kazdym dalsim volanim krokoveho callbacku vrati stejny ukazatel. Ve finalizacnim callbacku pak (tentokrat s jinym kontextovym handle nez mel k dispozici krokovy callback) vrati ten stejny pointer ktery Ti vratil SQLite v callbacku krokovem. To je zpusob jakym provazat krokovy a finalizacni callback.

Jestli chces, muzu Ti poslat ukazku.
« Poslední změna: 06-04-2018, 15:38:20 od 99598 »

Offline miroB

  • Guru
  • *****
  • Příspěvků: 628
  • Karma: 17
    • Verze Delphi: D1,2,3,4,7,2005 .. D Tokyo 10.2.3 Pro C/S
Re:SQLite: TFDSQLiteFunction využitie parametra Aggregated
« Odpověď #11 kdy: 06-04-2018, 15:29:09 »
Ukazka by sa urcite hodila.
Ja som pre vyhladanie v dictionary (ta sa naplna pri krokovom prechadzani) vyuzil tuto konstrukciu:
Kód: Delphi [Vybrat]
  1. procedure TSQLiteFunctionInstance.DoFinalize(context: psqlite3_context);
  2. begin
  3.   FOutput.HandleOld := FOutput.Handle; // Miro doplnene HandleOld  6.4.2017
  4.   FOutput.Handle    := context;        // je treba uchovat kontext, jinak nebude mozne vratit hodnotu
  5.   Func.DoFinalize(Self);
  6. end;
Pred tymto priradenim: "FOutput.Handle    := context;" handle obsahuje ten povodny "krokovy" handle
V dictionary v ramci Finalize potom vyhladavam pomocou HandleOld
To funguje aj v pripade pouzitia Group By
« Poslední změna: 06-04-2018, 15:34:12 od Miroslav Baláž »

99600

  • Host
Re:SQLite: TFDSQLiteFunction využitie parametra Aggregated
« Odpověď #12 kdy: 06-04-2018, 15:36:19 »
Ukazka by sa hodila.

Pripravim behem dne. Mam ted nejakou praci. Napad je pouzit agregacni kontext pro predani krokoveho kontext handle. V krokovem callbacku alokujes blok o velikosti sqlite3_context a ulozis do nej krokovy kontext handle. V dictionary ulozis ten samy handle a pocitas. No a ve finalizacnim callbacku si vyptas ten blok dat s krokovym kontext handle, najdes v dictionary vysledek a ten predas.

Ja som pre vyhladanie v dictionary (ta sa naplna pri krokovom prechadzani) vyuzil tuto konstrukciu:
Kód: Delphi [Vybrat]
  1. procedure TSQLiteFunctionInstance.DoFinalize(context: psqlite3_context);
  2. begin
  3.   FOutput.HandleOld := FOutput.Handle; // Miro doplnene HandleOld  6.4.2017
  4.   FOutput.Handle    := context;        // je treba uchovat kontext, jinak nebude mozne vratit hodnotu
  5.   Func.DoFinalize(Self);
  6. end;
V dictionary v ramci Finalize som vyhladaval pomocou HandleOld
To funguje aj v pripade pouzitia Group By

Jo takhle to myslis. No a mas garanci ze se nebude volat v poradi:

Kód: Delphi [Vybrat]
  1. OnCalculate - MojTotal(pole1)
  2. OnCalculate - MojTotal(pole2)
  3. OnFinalize - MojTotal(pole1)

Nejspis by nemelo, ale kdyby ano, zalohoval bys jiny kontext. Sel bych radeji cestou k tomu urcenou.
« Poslední změna: 06-04-2018, 15:48:20 od 99600 »

Offline miroB

  • Guru
  • *****
  • Příspěvků: 628
  • Karma: 17
    • Verze Delphi: D1,2,3,4,7,2005 .. D Tokyo 10.2.3 Pro C/S
Re:SQLite: TFDSQLiteFunction využitie parametra Aggregated
« Odpověď #13 kdy: 06-04-2018, 16:10:35 »
Skusim to spravit ako navrhujes. Zda sa to rozumne..

Ale teraz si uvedomujem, ze moj HandleOld prebera povodny krokovy handle, tesne pred tou tebou navrhnutou upravou (bez ktorej aggregate nefunguje). Za priradovanim handle-rov okamzite nasleduje DoFinalize, kde vystupoval povodny krokovy handle, takze je velmi pravdepodobne, ze tie volania budu OK, pretoze ja uchovavam povodny krokovy handle do HandleOld. Tak, ako by sa tvoja uprava kvazi nepouzila.

99603

  • Host
Re:SQLite: TFDSQLiteFunction využitie parametra Aggregated
« Odpověď #14 kdy: 06-04-2018, 16:28:26 »
Za priradovanim handle-rov okamzite nasleduje DoFinalize, kde vystupoval povodny krokovy handle, takze je velmi pravdepodobne, ze tie volania budu OK, pretoze ja uchovavam povodny krokovy handle do HandleOld. Tak, ako by sa tvoja uprava kvazi nepouzila.

Tebou navrhovane reseni nebude fungovat. Poradi volani je pro 2 zaznamy nasledujici:

Kód: Delphi [Vybrat]
  1. OnCalculate - MojTotal(pole1)
  2. OnCalculate - MojTotal(pole2)
  3. OnCalculate - MojTotal(pole1)
  4. OnCalculate - MojTotal(pole2)
  5. OnFinalize - MojTotal(pole1)
  6. OnFinalize - MojTotal(pole2)

Ty bys tedy ve finalizaci MojTotal(pole1) zalohoval kontext handle kroku MojTotal(pole2).