Autor Téma: SQLite TFDSQLiteFunction - využitie globálnej premennej  (Přečteno 1422 krát)

Offline miroB

  • Hrdina
  • ****
  • Příspěvků: 377
  • Karma: 14
    • Verze Delphi: D1,2,3,4,7,2005,2009, XE8,S,B,T10.2.2 Pro
Pre vysvetlenie som vytvoril projekt s funkciou runningTotal. Úlohu viem v pohode vyriešiť takto:
Kód: Delphi [Vybrat]
  1. var
  2.   globInt: Integer;
  3. ..
  4. procedure TForm1.FDSQLiteFunction1Calculate(AFunc: TSQLiteFunctionInstance;
  5.          AInputs: TSQLiteInputs; AOutput: TSQLiteOutput; var AUserData: TObject);
  6. begin
  7.   Inc( globInt, AInputs[ 0 ].AsInteger );
  8.   AOutput.AsInteger := globInt;
  9. end;
Lenže pri využití druhého volania tej istej funkcie v jednom SQL samozrejme dôjde k chybe, pretože glob. premenná je jedna a funkcie sú už dve. (viď obr.)
Ako by som naviazal globálnu premennú samostatne na každé volanie funkcie?
Je na to možné nejako použiť AUserData?
Ide o princíp. Potrebujem zložitejšiu štruktúru globálnych premenných, nejde o vyriešenie runningTotal. Ten je použitý iba pre jednoduché demo. Priložil som aj projekt, aby bolo vidieť, že pre jediné použitie funkcie, je všetko korektne nastavené.
Mimochodom volanie AfterExecute, BeforeExecute a Finalize pre FDSQLiteFunction zrejme nefunguje, resp pri volaní funkcie program cez tieto udalosti neprechádza.

Online Delfin

  • Guru
  • *****
  • Příspěvků: 1373
  • Karma: 57
  • SW konzultant
    • Verze Delphi: 2009, Tokyo
Re:SQLite TFDSQLiteFunction - využitie globálnej premennej
« Odpověď #1 kdy: 11-03-2018, 11:01:34 »
Byt bych rad pomohl, absolutne nemam tuseni o co jde. Nejspis jde o uzivatelsky definovane funkce FireDAC pro SQLite DBMS, ale co je spatne nejak nechapu. Nevim, mozna funkce chces rozlisit na zaklade AFunc.Func.Name, tezko rict...
« Poslední změna: 11-03-2018, 11:03:10 od Delfin »
I'm a soldier, so don't panic! I know the underground! I like WTFPL license! No more Google, go duck, go!

Offline miroB

  • Hrdina
  • ****
  • Příspěvků: 377
  • Karma: 14
    • Verze Delphi: D1,2,3,4,7,2005,2009, XE8,S,B,T10.2.2 Pro
Re:SQLite TFDSQLiteFunction - využitie globálnej premennej
« Odpověď #2 kdy: 11-03-2018, 11:21:55 »
Pozri prosím priložený obrázok v pôvodnom príspevku.
UDF ako taka je OK. Vyuzivam v nej vsak globalnu premennu. Co by bolo tiez OK, ak by sa funkcia v ramci SELECT pouzila len raz. Na obr. v druhej tabulke je vidiet ten vedlajsi a zly efekt. Pouzitie globalnej premennej nie je v druhom pripade korektne. Potreboval by som rozlisit prve a druhe volenie tej istej funkcie v jednom SELECT. Aby som mohol prislusne rozlisit glob. premennu.
Napr. glob[0] a glob[1] pre dve volania v jednom SELECT.
Lenze to by sa dalo iba na zaklade druheho parametra:
Kód: MySQL [Vybrat]
  1. SELECT runningTotal(val, 0), runningTotal(val, 1) FROM myTbl
Co je vsak dost neprijemne pre uzivatela, pretoze, musi strazit pocet volani tej istej funkcie v jednom SQL manualne.
Preco by som funkciu mal volat dvakrat?
Napriklad v pripade
Kód: MySQL [Vybrat]
  1. UPDATE myTbl SET xy=runningTotal(val) WHERE runningTotal(val) > 100
« Poslední změna: 11-03-2018, 11:29:54 od Miroslav Baláž »

Offline pf1957

  • Padawan
  • ******
  • Příspěvků: 2333
  • Karma: 125
    • Verze Delphi: D2007, XE3, DX10
Re:SQLite TFDSQLiteFunction - využitie globálnej premennej
« Odpověď #3 kdy: 11-03-2018, 11:46:57 »
Co je vsak dost neprijemne pre uzivatela, pretoze, musi strazit pocet volani tej istej funkcie v jednom SQL manualne.
Se obavam, ze jine reseni, jak se zbavit globalni promenne, na urovni SQL dotazu neni.

Samozrejme resenim by bylo vypocitat runningTotal(val) jen jednou jako invariant a vysledek priradit do SQL promenne. Ale to uz predstavuje nejaky proceduralni postup tj. spusteni nejakeho anonymniho bloku prikazu, coz SQLite tusim nepodporuje nebo Stored procedure.
« Poslední změna: 11-03-2018, 11:49:58 od pf1957 »

Offline pf1957

  • Padawan
  • ******
  • Příspěvků: 2333
  • Karma: 125
    • Verze Delphi: D2007, XE3, DX10
Re:SQLite TFDSQLiteFunction - využitie globálnej premennej
« Odpověď #4 kdy: 11-03-2018, 11:49:21 »
A nebo pouzit dve ruzne UDF: jednu EvalRunningTotal() a druhou FetchRunningTotal(). Ale stejne bys musel hlidat, abys nejdrive zavoval Eval... a pak teprve Fetch... kolikrat chces.

Offline miroB

  • Hrdina
  • ****
  • Příspěvků: 377
  • Karma: 14
    • Verze Delphi: D1,2,3,4,7,2005,2009, XE8,S,B,T10.2.2 Pro
Re:SQLite TFDSQLiteFunction - využitie globálnej premennej
« Odpověď #5 kdy: 11-03-2018, 12:07:53 »
Pre užívateľa by bolo asi najmenej mrzuté, keby musel zadať druhý parameter, asi takto:
Kód: MySQL [Vybrat]
  1. UPDATE myTbl SET xy=runningTotal(val, rowid) WHERE runningTotal(val, rowid) > 100
Rowid je prakticky automatika pre každú tabuľku SQLite. V onCalculate by som potom kontroloval počet volaní pre rovnaký rowId. Len v prvom riadku. No a potom aplikoval tento počet v cykle. Nepáči sa mi to síce, ale bez nejakého "hackovania" fireDAC to je asi jediná voľba.
Ale dúfal som.. či to niekto náhodou nezvládol už nejako elegantnejšie :)

Online Delfin

  • Guru
  • *****
  • Příspěvků: 1373
  • Karma: 57
  • SW konzultant
    • Verze Delphi: 2009, Tokyo
Re:SQLite TFDSQLiteFunction - využitie globálnej premennej
« Odpověď #6 kdy: 11-03-2018, 12:21:12 »
Ale dúfal som.. či to niekto náhodou nezvládol už nejako elegantnejšie :)

Obrazek je mi jedno, popis co potrebujes a snad pomuzu ;)
I'm a soldier, so don't panic! I know the underground! I like WTFPL license! No more Google, go duck, go!

Offline miroB

  • Hrdina
  • ****
  • Příspěvků: 377
  • Karma: 14
    • Verze Delphi: D1,2,3,4,7,2005,2009, XE8,S,B,T10.2.2 Pro
Re:SQLite TFDSQLiteFunction - využitie globálnej premennej
« Odpověď #7 kdy: 11-03-2018, 12:38:32 »
Potrebujem využiť globálnu premennú pri tvorbe UDF Funkcií v SQLite.
Nedokážem zistiť koľkáty krát je mnou vytvorená funkcia volaná v rámci jedného SQL príkazu. Pretože pri jej jednom zavolaní je to jedno, ale pre dve a viac volaní tej istej funkcie to už jedno nie je.
Pre objasnenie som priložil hotový projekt a aj výstupy z neho, aby to bolo možné overiť.

Offline pf1957

  • Padawan
  • ******
  • Příspěvků: 2333
  • Karma: 125
    • Verze Delphi: D2007, XE3, DX10
Re:SQLite TFDSQLiteFunction - využitie globálnej premennej
« Odpověď #8 kdy: 11-03-2018, 13:15:51 »
Potrebujem využiť globálnu premennú pri tvorbe UDF Funkcií v SQLite.
Nedokážem zistiť koľkáty krát je mnou vytvorená funkcia volaná v rámci jedného SQL príkazu. Pretože pri jej jednom zavolaní je to jedno, ale pre dve a viac volaní tej istej funkcie to už jedno nie je.
Zalezi, k cemu to presne ma byt, protoze muzes pouzit singleton a vypocitat soucet jen poprve a pak ho vracet, stejne jako muzes zajistit, aby kazde volani melo automaticky svoji kopii globalni promenne. Ale to nema nic spolecneho s obecnym resenim.

Offline miroB

  • Hrdina
  • ****
  • Příspěvků: 377
  • Karma: 14
    • Verze Delphi: D1,2,3,4,7,2005,2009, XE8,S,B,T10.2.2 Pro
Re:SQLite TFDSQLiteFunction - využitie globálnej premennej
« Odpověď #9 kdy: 11-03-2018, 13:28:05 »
Pre užívateľa by bolo asi najmenej mrzuté, keby musel zadať druhý parameter, asi takto:
Kód: MySQL [Vybrat]
  1. UPDATE myTbl SET xy=runningTotal(val, rowid) WHERE runningTotal(val, rowid) > 12
Rowid je prakticky automatika pre každú tabuľku SQLite. V onCalculate by som potom kontroloval počet volaní pre rovnaký rowId. Len v prvom riadku. No a potom aplikoval tento počet v cykle. Nepáči sa mi to síce, ale bez nejakého "hackovania" fireDAC to je asi jediná voľba.
Ale dúfal som.. či to niekto náhodou nezvládol už nejako elegantnejšie :)
Takto to veru nejde. Vďaka WHERE sa nutne nespraví všetko v prvom riadku a tým pádom sa v prvom behu nedá zistiť počet volaní.
Takže zatiaľ jediné riešenie, ktoré funguje, je to závislé na pozornosti užívateľa:
Kód: MySQL [Vybrat]
  1. UPDATE myTbl SET xy=runningTotal(val, 0) WHERE runningTotal(val, 1) > 12
Nie je to moc pekné, ale funguje. Tu je princíp (kód v OnCalculate):
Kód: Delphi [Vybrat]
  1.   if AInputs[ 1 ].IsNull then
  2.     begin
  3.     if AInputs[ 0 ].IsNull = false then
  4.       Inc( globInt[ 0 ], AInputs[ 0 ].AsInteger );
  5.     AOutput.AsInteger := globInt[ 0 ];
  6.     end
  7.   else
  8.     begin
  9.     if AInputs[ 0 ].IsNull = false then
  10.       Inc( globInt[ AInputs[ 1 ].AsInteger ], AInputs[ 0 ].AsInteger );
  11.     AOutput.AsInteger := globInt[ AInputs[ 1 ].AsInteger ];
  12.     end;
Musím povedať, že UDF funkcie sú veľmi rýchle v prípade dát, kde jedna tabuľka má > 700 MB.
Skúšal som finty ako CTE table WITH RECURSIVE. Ale to je v SQLite zrejme len pre malé dáta. Tu ide o priepastné časové rozdiely.
Cca 4-8 x pomalší je aj self JOIN na predošlý, alebo nasledujúci riadok. A to aj pre rowId ako INT32 primary key, čo má byť v SQLite špeciálne optimalizované.
« Poslední změna: 11-03-2018, 13:56:58 od Miroslav Baláž »

Online Delfin

  • Guru
  • *****
  • Příspěvků: 1373
  • Karma: 57
  • SW konzultant
    • Verze Delphi: 2009, Tokyo
Re:SQLite TFDSQLiteFunction - využitie globálnej premennej
« Odpověď #10 kdy: 12-03-2018, 09:14:13 »
Excellent
Rated 2 times
Jo takhle. Uz Ti rozumim. Ty chces rozlisit instance jednotlivych volani v ramci jednoho SQL prikazu. To se da pres sqlite3_context. Tech bude tolik, kolikrat je v prikazu funkce volana. Tedy pro prikaz:

Kód: MySQL [Vybrat]
  1. SELECT MyFunction(Val), MyFunction(Val) FROM MyTable WHERE MyFunction(Val) > 0

Budou existovat 3 ruzne kontexty. Ve FireDAC je kontext predavan do property AOutput.Handle. Pak si muzes vyrobit treba dictionary a pocitat v nem. Napr.:

Kód: Delphi [Vybrat]
  1. type
  2.   TForm1 = class(TForm)
  3.   ...
  4.   private
  5.     FResults: TDictionary<psqlite3_context, Integer>;
  6.   end;
  7.  
  8. procedure TForm1.FormCreate(Sender: TObject);
  9. begin
  10.   FResults := TDictionary<psqlite3_context, Integer>.Create;
  11. end;
  12.  
  13. procedure TForm1.FormDestroy(Sender: TObject);
  14. begin
  15.   FResults.free;
  16. end;
  17.  
  18. procedure TForm1.FDQuery1BeforeOpen(DataSet: TDataSet);
  19. begin
  20.   FResults.Clear;
  21. end;
  22.  
  23. procedure TForm1.FDSQLiteFunction1Calculate(AFunc: TSQLiteFunctionInstance;
  24.   AInputs: TSQLiteInputs; AOutput: TSQLiteOutput; var AUserData: TObject);
  25. var
  26.   Value: Integer;
  27. begin
  28.   // pro kazdy kontext (kazde volani v ramci prikazu) existuje (nebo bude existovat) 1 zaznam v dictionary<kontext, hodnota sumy>
  29.   if not FResults.TryGetValue(AOutput.Handle, Value) then
  30.     Value := AInputs[0].AsInteger
  31.   else
  32.     Value := Value + AInputs[0].AsInteger;
  33.   FResults.AddOrSetValue(AOutput.Handle, Value);
  34.  
  35.   AOutput.AsInteger := Value;
  36. end;
« Poslední změna: 12-03-2018, 09:34:17 od Delfin »
I'm a soldier, so don't panic! I know the underground! I like WTFPL license! No more Google, go duck, go!

Offline miroB

  • Hrdina
  • ****
  • Příspěvků: 377
  • Karma: 14
    • Verze Delphi: D1,2,3,4,7,2005,2009, XE8,S,B,T10.2.2 Pro
Re:SQLite TFDSQLiteFunction - využitie globálnej premennej
« Odpověď #11 kdy: 12-03-2018, 10:32:26 »
..
Budou existovat 3 ruzne kontexty. Ve FireDAC je kontext predavan do property AOutput.Handle. Pak si muzes vyrobit treba dictionary a pocitat v nem. Napr.:
..
To vyzerá veľmi nádejne.. A nielen vyzerá, ale aj funguje :)
Vďaka aj za zdrojový kód v Tvojom príspevku.
« Poslední změna: 12-03-2018, 10:34:06 od Miroslav Baláž »

Offline miroB

  • Hrdina
  • ****
  • Příspěvků: 377
  • Karma: 14
    • Verze Delphi: D1,2,3,4,7,2005,2009, XE8,S,B,T10.2.2 Pro
Re:SQLite TFDSQLiteFunction - využitie globálnej premennej
« Odpověď #12 kdy: 12-03-2018, 10:54:06 »
A. Rovno sa spýtam aj na prípadné možné využitie parametra AUserData v FDSQLiteFunction1Calculate
Kód: Delphi [Vybrat]
  1. FDSQLiteFunction1Calculate(AFunc: TSQLiteFunctionInstance; AInputs: TSQLiteInputs; AOutput: TSQLiteOutput; var AUserData: TObject);
Snáď AUserData slúži pre účely aggregate funkcií.
Nejak sa k tomu neviem dogoogliť. Veru ani ten AOutput s jeho Handle, popísaný v príspevku od Delfin, som nenašiel.

B. Ak by niekto vedel, kde je zdokumentované všetko okolo praktického využitia SQLite/FireDAC funkcií, veľmi by ma to potešilo. Docwiki mám prelezenú 100x, ale tieto veci tam nevidím.
« Poslední změna: 12-03-2018, 10:55:38 od Miroslav Baláž »

Online Delfin

  • Guru
  • *****
  • Příspěvků: 1373
  • Karma: 57
  • SW konzultant
    • Verze Delphi: 2009, Tokyo
Re:SQLite TFDSQLiteFunction - využitie globálnej premennej
« Odpověď #13 kdy: 12-03-2018, 11:04:34 »
A. Rovno sa spýtam aj na prípadné možné využitie parametra AUserData v FDSQLiteFunction1Calculate
Kód: Delphi [Vybrat]
  1. FDSQLiteFunction1Calculate(AFunc: TSQLiteFunctionInstance; AInputs: TSQLiteInputs; AOutput: TSQLiteOutput; var AUserData: TObject);
Snáď AUserData slúži pre účely aggregate funkcií.
Nejak sa k tomu neviem dogoogliť. Veru ani ten AOutput s jeho Handle, popísaný v príspevku od Delfin, som nenašiel.

To asi nikdo ;D A pouziti parametru AUserData je zoufale. Misto aby vyrobili udalost napr. OnInitialize (kdyz existuje OnFinalize), priradit AUserData objekt muzes nejdrive v OnCalculate a jeste je spolecny pro vsechny kontexty (cili je spolecny pro instanci TFDSQLiteFunction). A co do nej ulozis je na Tve fantazii. Ale pouziti je zoufale, podivej (ta inicializace se mi nelibi a to ze neni mozne vytvorit samostatny objekt pro kazdy kontext je taky celkem slabe):

Kód: Delphi [Vybrat]
  1. procedure TForm1.FDSQLiteFunction1Calculate(AFunc: TSQLiteFunctionInstance;
  2.   AInputs: TSQLiteInputs; AOutput: TSQLiteOutput; var AUserData: TObject);
  3. begin
  4.   if not Assigned(AUserData) then
  5.     AUserData := TMyObject.Create; // neni per kontext, je spolecny pro vsechny kontexty
  6.  
  7.   if AUserData is TMyObject then
  8.     TMyObject(AUserData).DoSomething;
  9.   ...
  10. end;
  11.  
  12. procedure TForm1.FDSQLiteFunction1Finalize(AFunc: TSQLiteFunctionInstance;
  13.   var AUserData: TObject);
  14. begin
  15.   FreeAndNil(AUserData);
  16. end;
« Poslední změna: 12-03-2018, 11:13:20 od Delfin »
I'm a soldier, so don't panic! I know the underground! I like WTFPL license! No more Google, go duck, go!

Offline miroB

  • Hrdina
  • ****
  • Příspěvků: 377
  • Karma: 14
    • Verze Delphi: D1,2,3,4,7,2005,2009, XE8,S,B,T10.2.2 Pro
Re:SQLite TFDSQLiteFunction - využitie globálnej premennej
« Odpověď #14 kdy: 12-03-2018, 11:43:29 »
..
a jeste je spolecny pro vsechny kontexty (cili je spolecny pro instanci TFDSQLiteFunction).
..
Týmto parameter AUserData prakticky stráca na význame. Myslím, že Delfinom navrhnuté riešenie DictionaryAOutput.Handle, ktoré dovoľuje pracovať jednotlivo s kontextami ( rozlišujú sa viaceré volania jednej funkcie ), môže byť upravené tak, aby nahradilo všetky možnosti AUserData.
V praxi je len ťažko možné zabrániť, aby UDF funkcia nebola volaná v jednom príkaze viackrát.

 

S rychlou odpovědí můžete používat BB kódy a emotikony jako v běžném okně pro odpověď, ale daleko rychleji.

Upozornění: do tohoto tématu bylo naposledy přispěno před 120 dny.
Zvažte prosím založení nového tématu.

Jméno: E-mail:
Ověření:
Datový typ v Delphi, který má True a False: