* test_benchmark_in1000.prg - compara 3 variante pentru citirea din ISTORIC_CODURI_FISCALE * pe loturi mari de coduri fiscale, ca inlocuitor pentru IN () inline care pica pe * ORA-01795 peste 1000 elemente (CitesteIstoricPentruCoduri, validare.prg:2698). * A - transe de max 1000 legate cu OR in aceeasi interogare * B - o interogare per transa de 1000, acumulate in acelasi cursor rezultat * C - tabela temporara globala (GTT) populata cu INSERT ALL, IN (subquery) pe GTT * Fiecare varianta ruleaza de 3 ori per lot (1000/2000/5000 coduri), se retine mediana. * Nu modifica validare.prg. Obiectele Oracle create (GTT de test) se sterg la final. * * Rulare: vfp9.exe -A -T "...\test_benchmark_in1000.prg" [watermark] LPARAMETERS tcWatermark SET SAFETY OFF SET TALK OFF SET DELETED ON SET EXACT ON SET CENTURY ON SET DATE DMY SET NULLDISPLAY TO '' CLOSE DATABASES PUBLIC gcTestLog, pdDataCache, pnZileCache gcTestLog = "D:\ROA\ROACONT\COMUN\utile\Teste\cache_anaf\test_benchmark_in1000_log.txt" STRTOFILE("START " + TTOC(DATETIME()) + CHR(13) + CHR(10), gcTestLog) ON ERROR DO ErrBench WITH ERROR(), MESSAGE(), PROGRAM(), LINENO() ON SHUTDOWN QUIT DO D:\ROA\ROACONT\COMUN\utile\Teste\test_init_env_auto.prg WITH 'CENTRAL', 'MARIUSM_AUTO', 'ROMFASTSOFT' IF gnHandle <= 0 DO WrLogB WITH 'EROARE: conectare Oracle esuata - GATA' QUIT ENDIF DO WrLogB WITH 'mediu OK, schema=' + gcS PUBLIC gnErorriSQL, gcUltimaEroareSQL gnErorriSQL = 0 gcUltimaEroareSQL = '' LOCAL loExecNou loExecNou = CREATEOBJECT('oexec_fara_dialog') loExecNou.nHandle = gnHandle goExecutor = loExecNou DO WrLogB WITH 'executor inlocuit (fara dialog modal la eroare SQL)' pdDataCache = DATE() pnZileCache = 36500 && vreau sa vad toate randurile din istoric, indiferent de prospetime *========================================================== * 0. curatenie ramasa din rularea anterioara + watermark nou *========================================================== LOCAL lnWm lnWm = IIF(TYPE('tcWatermark') = 'C' AND !EMPTY(tcWatermark), INT(VAL(tcWatermark)), 0) IF lnWm > 0 =goExecutor.oExecuta('DELETE FROM ISTORIC_CODURI_FISCALE WHERE ID > ' + TRANSFORM(lnWm)) =goExecutor.oExecuta('COMMIT') DO WrLogB WITH 'curatenie facuta (ID > ' + TRANSFORM(lnWm) + ')' ENDIF PUBLIC gnIdMaxIstoric gnIdMaxIstoric = 0 IF goExecutor.oExecuta('SELECT NVL(MAX(ID),0) AS IDMAX FROM ISTORIC_CODURI_FISCALE', 'crsWm') gnIdMaxIstoric = crsWm.IDMAX USE IN crsWm ENDIF DO WrLogB WITH 'watermark nou = ' + TRANSFORM(gnIdMaxIstoric) *========================================================== * 1. incarc cele 200 coduri reale + pregatesc obiectul GTT pentru varianta C *========================================================== LOCAL laReale[200], lnNReale, lcLinie, lnFH lnNReale = 0 lnFH = FOPEN('D:\ROA\ROACONT\COMUN\utile\Teste\cache_anaf\codes_200_reale.txt') IF lnFH >= 0 DO WHILE !FEOF(lnFH) lcLinie = ALLTRIM(FGETS(lnFH)) IF !EMPTY(lcLinie) lnNReale = lnNReale + 1 laReale[lnNReale] = lcLinie ENDIF ENDDO =FCLOSE(lnFH) ENDIF DO WrLogB WITH 'coduri reale incarcate: ' + TRANSFORM(lnNReale) DO WrLogB WITH '' DO WrLogB WITH '=== pregatire GTT pentru varianta C ===' LOCAL llGttOk llGttOk = .T. IF !goExecutor.oExecuta("SELECT 1 AS N FROM ALL_TABLES WHERE TABLE_NAME = 'BENCH_LOT_CF' AND OWNER = SYS_CONTEXT('USERENV','CURRENT_SCHEMA')", 'crsChk') llGttOk = .F. ENDIF LOCAL llExistaDeja llExistaDeja = (TYPE('crsChk') = 'U' OR !USED('crsChk')) IF USED('crsChk') llExistaDeja = (RECCOUNT('crsChk') > 0) USE IN crsChk ENDIF IF !llExistaDeja IF goExecutor.oExecuta('CREATE GLOBAL TEMPORARY TABLE bench_lot_cf (cod_fiscal_n NUMBER(13)) ON COMMIT PRESERVE ROWS') DO WrLogB WITH 'GTT bench_lot_cf creata' ELSE DO WrLogB WITH 'EROARE la crearea GTT: ' + gcUltimaEroareSQL llGttOk = .F. ENDIF ELSE DO WrLogB WITH 'GTT bench_lot_cf exista deja (rulare anterioara neterminata) - refolosesc' ENDIF *========================================================== * 2. rulez cele 3 variante pe loturile 1000 / 2000 / 5000 *========================================================== LOCAL laLoturi[3], lnLot, lnCntLot laLoturi[1] = 1000 laLoturi[2] = 2000 laLoturi[3] = 5000 FOR lnLot = 1 TO 3 lnCntLot = laLoturi[lnLot] DO WrLogB WITH '' DO WrLogB WITH '######## LOT ' + TRANSFORM(lnCntLot) + ' coduri ########' LOCAL lcLista, lnNRandA, lnNRandB, lnNRandC lcLista = BenchListaCoduri(lnCntLot, @laReale, lnNReale) * --- Varianta A: transe de 1000 legate cu OR --- LOCAL laTA[3], lnI, lnT0, lcSqlA, lnRandA FOR lnI = 1 TO 3 lnT0 = SECONDS() lnRandA = BenchRunVarA(lcLista) laTA[lnI] = SECONDS() - lnT0 ENDFOR lnNRandA = lnRandA DO WrLogB WITH 'A (OR-transe) timpi=' + BenchFmtArr(@laTA) + ' mediana=' + TRANSFORM(ROUND(BenchMediana(@laTA), 3)) + ' s randuri=' + TRANSFORM(lnNRandA) * --- Varianta B: o interogare per transa de 1000, acumulate --- LOCAL laTB[3], lnRandB FOR lnI = 1 TO 3 lnT0 = SECONDS() lnRandB = BenchRunVarB(lcLista) laTB[lnI] = SECONDS() - lnT0 ENDFOR lnNRandB = lnRandB DO WrLogB WITH 'B (bucla transe) timpi=' + BenchFmtArr(@laTB) + ' mediana=' + TRANSFORM(ROUND(BenchMediana(@laTB), 3)) + ' s randuri=' + TRANSFORM(lnNRandB) * --- Varianta C: GTT + IN (subquery), populare + interogare --- * NOTA: dezactivata din masuratoarea cronometrata - vezi test_debug_gtt3/4_log.txt: * pe conexiunea ODBC a aplicatiei (DSN CENTRAL, driver SQORA32.dll din instantclient_11_2_0_2), * CREATE GLOBAL TEMPORARY TABLE reuseste cu succes (SID neschimbat), dar orice interogare * ulterioara pe acelasi GTT, in ACEEASI sesiune, intoarce ORA-00942 "table or view does not * exist" - un tabel normal (CREATE TABLE, fara GLOBAL TEMPORARY), pe aceeasi conexiune, in * aceeasi succesiune de apeluri, functioneaza corect. Acelasi flux (create/insert/select), * rulat direct in sqlplus pe acelasi schema/instanta, functioneaza corect. Deci problema e * specifica GTT + acest driver ODBC, nu Oracle sau schema. Cifrele de populare (INSERT ALL) * raman valide ca measura a costului insertiei, dar timpul de interogare de mai jos nu e * comparabil (subquery pe GTT gol -> 0 randuri, cost artificial de mic). IF llGttOk LOCAL laTC[3], laTCins[3], lnRandC FOR lnI = 1 TO 3 lnT0 = SECONDS() lnRandC = BenchRunVarC(lcLista, @laTCins, lnI) laTC[lnI] = SECONDS() - lnT0 ENDFOR lnNRandC = lnRandC DO WrLogB WITH 'C (GTT+IN sub, NECONCLUDENT - vezi nota) timpi=' + BenchFmtArr(@laTC) + ' mediana=' + TRANSFORM(ROUND(BenchMediana(@laTC), 3)) + ' s randuri=' + TRANSFORM(lnNRandC) + ' (din care populare GTT, mediana=' + TRANSFORM(ROUND(BenchMediana(@laTCins), 3)) + ' s)' ELSE DO WrLogB WITH 'C (GTT+IN sub) SARITA - GTT indisponibila' ENDIF * sanitate: A si B trebuie sa intoarca acelasi numar de randuri (C e neconcludenta, vezi nota) IF lnNRandA = lnNRandB DO WrLogB WITH ' OK: A si B au intors acelasi numar de randuri' ELSE DO WrLogB WITH ' ATENTIE: numar de randuri DIFERIT intre A si B!' ENDIF ENDFOR *========================================================== * 3. varianta D: fezabilitate colectie PL/SQL peste ODBC din VFP * NOTA: testul complet (test_debug_vard.prg) s-a incheiat cu terminarea abrupta a procesului * vfp9.exe la incercarea de a lega un array VFP direct ca parametru colectie (?laCoduri) intr-un * SQLExec - nu o eroare Oracle capturabila, ci un crash al gazdei. Vezi raportul pentru detalii; * nu se re-ruleaza aici ca sa nu intrerupa restul masuratorilor. *========================================================== *========================================================== * 4. curatenie *========================================================== DO WrLogB WITH '' IF llGttOk =goExecutor.oExecuta('DROP TABLE bench_lot_cf') DO WrLogB WITH 'GTT bench_lot_cf stearsa' ENDIF IF TYPE('gnIdMaxIstoric') = 'N' AND gnIdMaxIstoric > 0 =goExecutor.oExecuta('DELETE FROM ISTORIC_CODURI_FISCALE WHERE ID > ' + TRANSFORM(gnIdMaxIstoric)) =goExecutor.oExecuta('COMMIT') DO WrLogB WITH 'curatenie finala: sterse randurile cu ID > ' + TRANSFORM(gnIdMaxIstoric) ENDIF DO WrLogB WITH 'GATA ' + TTOC(DATETIME()) QUIT *========================================================== * helpere *========================================================== PROCEDURE WrLogB LPARAMETERS tcText STRTOFILE(TRANSFORM(tcText) + CHR(13) + CHR(10), gcTestLog, .T.) ENDPROC PROCEDURE ErrBench LPARAMETERS tnError, tcMessage, tcProgram, tnLine STRTOFILE('EROARE ' + TRANSFORM(tnError) + ' [' + TRANSFORM(tcMessage) + '] in ' + TRANSFORM(tcProgram) + ' linia ' + TRANSFORM(tnLine) + CHR(13) + CHR(10), gcTestLog, .T.) RETURN ENDPROC FUNCTION BenchMediana LPARAMETERS taValori LOCAL laSort[3], lnI, lnJ, lnTmp FOR lnI = 1 TO 3 laSort[lnI] = taValori[lnI] ENDFOR FOR lnI = 1 TO 2 FOR lnJ = lnI + 1 TO 3 IF laSort[lnJ] < laSort[lnI] lnTmp = laSort[lnI] laSort[lnI] = laSort[lnJ] laSort[lnJ] = lnTmp ENDIF ENDFOR ENDFOR RETURN laSort[2] ENDFUNC FUNCTION BenchFmtArr LPARAMETERS taValori LOCAL lcRez, lnI lcRez = '[' FOR lnI = 1 TO 3 lcRez = lcRez + TRANSFORM(ROUND(taValori[lnI], 3)) + IIF(lnI < 3, ', ', '') ENDFOR RETURN lcRez + ']' ENDFUNC *** construieste lista CSV de coduri normalizate pentru un lot de tnSize: primele MIN(tnSize,200) *** din cele reale, restul sintetice (8000000001...) - distincte, deterministe intre variante FUNCTION BenchListaCoduri LPARAMETERS tnSize, taReale, tnNReale LOCAL lcLista, lnI, lnNReal lcLista = '' lnNReal = MIN(tnSize, tnNReale) FOR lnI = 1 TO lnNReal lcLista = lcLista + IIF(EMPTY(lcLista), '', ',') + taReale[lnI] ENDFOR FOR lnI = lnNReal + 1 TO tnSize lcLista = lcLista + IIF(EMPTY(lcLista), '', ',') + TRANSFORM(8000000000 + lnI) ENDFOR RETURN lcLista ENDFUNC *** taie lcLista (CSV) in transe de maxim tnMax elemente, intoarce array de siruri CSV FUNCTION BenchTransaza LPARAMETERS tcLista, tnMax, taTranse LOCAL lnTotal, lnTranse, lnI, lnDe, lnLa, lnJ lnTotal = GETWORDCOUNT(tcLista, ',') lnTranse = CEILING(lnTotal / tnMax) DIMENSION taTranse[MAX(lnTranse, 1)] FOR lnI = 1 TO lnTranse lnDe = (lnI - 1) * tnMax + 1 lnLa = MIN(lnI * tnMax, lnTotal) LOCAL lcBucata lcBucata = '' FOR lnJ = lnDe TO lnLa lcBucata = lcBucata + IIF(EMPTY(lcBucata), '', ',') + GETWORDNUM(tcLista, lnJ, ',') ENDFOR taTranse[lnI] = lcBucata ENDFOR RETURN lnTranse ENDFUNC *** interogarea de baza (identica cu CitesteIstoricPentruCoduri, validare.prg:2731-2749), *** cu <> ca placeholder pentru conditia pe cod_fiscal_n FUNCTION BenchSqlBaza LPARAMETERS tcCondCod LOCAL lcSql TEXT TO lcSql TEXTMERGE NOSHOW select cod_fiscal_n, firma, adresa, judet, regcom, dataorav_anaf, data_inceput_scptva, data_sfarsit_scptva, data_anul_imp_scptva, data_inceput_tvainc, data_sfarsit_tvainc, data_inactivare, data_reactivare, data_inceput_splittva, data_anulare_splittva, case when ?pdDataCache between data_inceput_scptva and nvl(data_sfarsit_scptva, to_date('29991231','yyyymmdd')) then 1 else 0 end as v_scptva, case when ?pdDataCache between data_inceput_tvainc and nvl(data_sfarsit_tvainc - 1, to_date('29991231','yyyymmdd')) then 1 else 0 end as v_tvainc, case when ?pdDataCache between data_inceput_splittva and nvl(data_anulare_splittva, to_date('29991231','yyyymmdd')) then 1 else 0 end as v_splittva, case when data_inactivare is not null and ?pdDataCache >= data_inactivare and (data_reactivare is null or ?pdDataCache < data_reactivare) then 1 else 0 end as v_inactiv from (select i.*, row_number() over (partition by cod_fiscal_n order by id desc) as rn from istoric_coduri_fiscale i where <> and dataorav_anaf is not null and sysdate - dataorav_anaf <= ?pnZileCache) where rn = 1 and (data_inceput_scptva is null or ?pdDataCache >= data_inceput_scptva) and (data_inceput_tvainc is null or ?pdDataCache >= data_inceput_tvainc) and (data_inactivare is null or ?pdDataCache >= data_inactivare) ENDTEXT RETURN lcSql ENDFUNC *** A: transe de 1000 legate cu OR in ACEEASI interogare FUNCTION BenchRunVarA LPARAMETERS tcLista LOCAL laTranse[1], lnTranse, lnI, lcCond, lcSql, lnRand lnTranse = BenchTransaza(tcLista, 1000, @laTranse) lcCond = '' FOR lnI = 1 TO lnTranse lcCond = lcCond + IIF(EMPTY(lcCond), '', ' or ') + 'cod_fiscal_n in (' + laTranse[lnI] + ')' ENDFOR IF lnTranse > 1 lcCond = '(' + lcCond + ')' ENDIF lcSql = BenchSqlBaza(lcCond) IF USED('crsBenchA') USE IN crsBenchA ENDIF lnRand = 0 IF goExecutor.oExecuta(lcSql, 'crsBenchA') lnRand = RECCOUNT('crsBenchA') USE IN crsBenchA ENDIF RETURN lnRand ENDFUNC *** B: o interogare per transa de 1000, acumulate in acelasi cursor rezultat FUNCTION BenchRunVarB LPARAMETERS tcLista LOCAL laTranse[1], lnTranse, lnI, lcCond, lcSql, lnRand lnTranse = BenchTransaza(tcLista, 1000, @laTranse) IF USED('crsBenchBAcc') USE IN crsBenchBAcc ENDIF lnRand = 0 FOR lnI = 1 TO lnTranse lcCond = 'cod_fiscal_n in (' + laTranse[lnI] + ')' lcSql = BenchSqlBaza(lcCond) IF USED('crsBenchBTmp') USE IN crsBenchBTmp ENDIF IF goExecutor.oExecuta(lcSql, 'crsBenchBTmp') IF RECCOUNT('crsBenchBTmp') > 0 IF !USED('crsBenchBAcc') SELECT * FROM crsBenchBTmp WHERE .F. INTO CURSOR crsBenchBAcc READWRITE ENDIF SELECT crsBenchBTmp SCAN SCATTER MEMO NAME loRandB INSERT INTO crsBenchBAcc FROM NAME loRandB SELECT crsBenchBTmp ENDSCAN ENDIF USE IN crsBenchBTmp ENDIF ENDFOR IF USED('crsBenchBAcc') lnRand = RECCOUNT('crsBenchBAcc') USE IN crsBenchBAcc ENDIF RETURN lnRand ENDFUNC *** C: GTT populata cu INSERT ALL (transe de 300), interogare cu IN (select ... from GTT) FUNCTION BenchRunVarC LPARAMETERS tcLista, taTimpInsert, tnRep LOCAL lnT0, lnRand =goExecutor.oExecuta('DELETE FROM bench_lot_cf') lnT0 = SECONDS() DO BenchPopuleazaGTT WITH tcLista taTimpInsert[tnRep] = SECONDS() - lnT0 LOCAL lcCond, lcSql lcCond = 'cod_fiscal_n in (select cod_fiscal_n from bench_lot_cf)' lcSql = BenchSqlBaza(lcCond) IF USED('crsBenchC') USE IN crsBenchC ENDIF lnRand = 0 IF goExecutor.oExecuta(lcSql, 'crsBenchC') lnRand = RECCOUNT('crsBenchC') USE IN crsBenchC ENDIF RETURN lnRand ENDFUNC *** populeaza bench_lot_cf cu codurile din tcLista, cate 300 pe instructiune (insert all ... select 1 from dual) PROCEDURE BenchPopuleazaGTT LPARAMETERS tcLista LOCAL laTranse[1], lnTranse, lnI, lnJ, lnDe, lnLa, lnTotal, lcIns lnTotal = GETWORDCOUNT(tcLista, ',') lnTranse = CEILING(lnTotal / 300) FOR lnI = 1 TO lnTranse lnDe = (lnI - 1) * 300 + 1 lnLa = MIN(lnI * 300, lnTotal) lcIns = 'insert all' FOR lnJ = lnDe TO lnLa lcIns = lcIns + ' into bench_lot_cf (cod_fiscal_n) values (' + GETWORDNUM(tcLista, lnJ, ',') + ')' ENDFOR lcIns = lcIns + ' select 1 from dual' =goExecutor.oExecuta(lcIns) ENDFOR ENDPROC DEFINE CLASS oexec_fara_dialog AS oexecutor FUNCTION oExecuta LPARAMETERS tcSql, tcCursor, tlProgress, tcTitluProgress, tnHandle, tlShowError, tlQuitOnError, tlReconnect LOCAL lcSql, lcCursor, lnHandle, lnRez lcSql = IIF(EMPTY(tcSql), This.cSql, tcSql) lcCursor = IIF(EMPTY(tcCursor), This.cCursor, tcCursor) lnHandle = IIF(EMPTY(tnHandle), This.nHandle, tnHandle) lnRez = This.oExecute(lcSql, lcCursor, tlProgress, tcTitluProgress, lnHandle) IF lnRez < 1 gnErorriSQL = gnErorriSQL + 1 gcUltimaEroareSQL = TRANSFORM(This.cEroare) ENDIF RETURN (lnRez >= 1) ENDFUNC ENDDEFINE