[ Sale_123 @ 01.10.2011. 02:34 ] @
Da bi malo isprobao mogucnosti QT-u, odlucio sam da napravim jednu jednostavnu aplikaciju za izdavanje faktura (C++/Qt-a/MySQL). I imam dva problema:


a) Koji tip podataka (ili biblioteku) koristiti u c++? (Necu da koristim float ili double). Sta je najlakse koristiti u sprezi sa mysql decimal tipom? Da li koristiti integer pa vrsiti konverziju u decimal ili u samoj bazi koristiti isto integer tj. bigint?

b) Dilema oko dizajna baze. Recimo da imam dvije tabele: fakture (kupac, datum, broj fakture, itd) i stavke fakture (roba, cijena, kolicina). E sad na osnovu cijene i kolicine robe, moguce je izracunati porez i finalnu cijenu, a isto tako na osnovu tih podatak moguce je izracunati ukupnu vrijednost fakture. Dilema je da li te stvari cuvati u bazu podataka ili svaki put izracunavati? (porez i finalnu cijenu u tabeli stavke fakture, a ukupnu sumu fakture naravno u tabeli faktura).
[ djoka_l @ 01.10.2011. 10:52 ] @
Rešenja za ove nedoumice zavise isključivo od načina na koje želiš da koristiš podatke.

Recimo, normalno bi bilo da za decimal koristiš double ako će se te vrednosti koristiti za obračune, na primer obračun kamate ili slično.
Međutim, ako je u pitanju samo učitavanje i prikazivanje podataka koje dobiješ upitima iz baze, možeš da koristiš stringove. Shvatam da je nezgodno to što kada radiš sa double tipom, mogu da se generišu greške zaokruživanja, ali to ne opravdava gubitak na brzini u komplikovanim izračunavanjima ukoliko umesto native double tipa, koji je podržan kroz FPU, koristiš neku klasu koja može da emulira decimal tip.

Oko druge dileme, rešenje se, opet, nameće kroz način upotrebe podataka. Jasno je da se normalna forma narušava ako, recimo, u tabeli faktura držiš ukupan iznos fakture, a u stavkama fakture pored količine i jedinične cene držiš i krajnju cenu (možemo zamisliti da tu imaš i polje za procenat popusta, pa onda iznos popusta i slično), ali to mnogo olakšava upite i formiranje izveštaja.

Još jedan primer narušavanja normalne forme bi bilo da držiš datum fakture u fakturi i datum fakture u stavci fakture, jer ćeš često morati da nađeš stavke fakture po opsegu datuma, a ako ubaciš takvu redundantnost, nećeš morati da radiš JOIN sa fakturom.

U slučaju akademskog projekta, to bi ti bilo zamereno. Ako je baza pre svega namenjena za transaction processing (OLTP) redundantosti, takođe, nisu preterano poželjne. Međutim za OLAP ovakve redundantnosti su OBAVEZNE. Kako će tvoja realna baza da se koristi za obe potrebe, najbolje je da svesno prekršiš pravila normalizacije i uvedeš redundantnosti, kako bi sebi olakšao život i ubrzao rad, s tmi da aplikacija mora da vodi strogo računa da se konzistentnost ne naruši.
[ Sale_123 @ 01.10.2011. 12:00 ] @
Citat:
djoka_l: Rešenja za ove nedoumice zavise isključivo od načina na koje želiš da koristiš podatke.

Recimo, normalno bi bilo da za decimal koristiš double ako će se te vrednosti koristiti za obračune, na primer obračun kamate ili slično.
Međutim, ako je u pitanju samo učitavanje i prikazivanje podataka koje dobiješ upitima iz baze, možeš da koristiš stringove. Shvatam da je nezgodno to što kada radiš sa double tipom, mogu da se generišu greške zaokruživanja, ali to ne opravdava gubitak na brzini u komplikovanim izračunavanjima ukoliko umesto native double tipa, koji je podržan kroz FPU, koristiš neku klasu koja može da emulira decimal tip.

Oko druge dileme, rešenje se, opet, nameće kroz način upotrebe podataka. Jasno je da se normalna forma narušava ako, recimo, u tabeli faktura držiš ukupan iznos fakture, a u stavkama fakture pored količine i jedinične cene držiš i krajnju cenu (možemo zamisliti da tu imaš i polje za procenat popusta, pa onda iznos popusta i slično), ali to mnogo olakšava upite i formiranje izveštaja.

Još jedan primer narušavanja normalne forme bi bilo da držiš datum fakture u fakturi i datum fakture u stavci fakture, jer ćeš često morati da nađeš stavke fakture po opsegu datuma, a ako ubaciš takvu redundantnost, nećeš morati da radiš JOIN sa fakturom.

U slučaju akademskog projekta, to bi ti bilo zamereno. Ako je baza pre svega namenjena za transaction processing (OLTP) redundantosti, takođe, nisu preterano poželjne. Međutim za OLAP ovakve redundantnosti su OBAVEZNE. Kako će tvoja realna baza da se koristi za obe potrebe, najbolje je da svesno prekršiš pravila normalizacije i uvedeš redundantnosti, kako bi sebi olakšao život i ubrzao rad, s tmi da aplikacija mora da vodi strogo računa da se konzistentnost ne naruši.


Wow, svaka cast na odgovoru! I hvala mnogo!

Jesi mozda imao iskustva sa intelovom bilibotekom: Decimal Floating-Point Math Library? Tako nesto bi bilo super za rad, samo pitanje je kako izvrsiti konverziju iz tog tipa u mysql decimal i obrnuto.

Razmisljao sam da ne bi mozda bilo lose napraviti klasu decimal, koja u osnovi koristi 64-bitni integer, i da na taj nacin rijesim problem sa zaokruzivanjem i brzom. Kada snimam u bazu, konverzija bi isla jednostavno jer bi broj pretvorio u string, i na 4 mjestu s desna dodao tacku. Medjutim, ostaje problem kako iz mysql decimal-a pretvoriti u integer? Kontam da ce upit da vrati u tipu double, sto mi bas i nije po volji.

I jos jedna dilema u vezi tim sta je najbolje praksa. Recimo da prekrsim pravila normalizacije i uvedem redudantnost. Da li je pametno koristiti stored procedures da bi se osigurao integritet podataka ili to ipak prepustiti data layer-u u programu?
[ djoka_l @ 01.10.2011. 13:47 ] @
Nemam iskustva sa Intel bibliotekom, ali bih je, po nazivu, odmah izbacio iz razmatranja. Recimo, moji programi su bez izmena radili na Intel, Sparc, Alpha baziranim serverima (a imao sam i test instalacije na Z-Series IBM mainframe i na PowerPC procesorima). Zato uvek pokušavam da koristim samo portabilna rešenja. Ako tebi portabilnost nije interesantna, onda slobodno koristi ono što ti odgovara. Ali, ne zaboravi, da si pitanje postavio na MySQL forumu, a dotična baza postoji i van Intel okruženja.

Kao drugo, ja se nikada ne bih upustio u razmatranje nekog drugor rešenje, osim double, kada su u pitanju poslovne aplikacije. Jednostavo, nije vredno truda.

I na tvoje drugo pitanje odgovor zavisi od okruženja u kojoj će se aplikacija vrteti.
Ako ti je zamisao da se baza vrti na nekoj zveri od mašine, ne vidim zašto se ne bi validacija radila na DB serveru.
Ako ti je DB server kritičan po resursima, onda imaš dva scenarija
1) aplikacija radi u LAN-u, tako da imaš poveranja u klijente. U tom slučaju, zašto ne prebaciti deo validacije na klijente i osloboditi resure DB servera za kritičnije stvari
2) aplikacija se vrti u browserima, na primer radiš neki program za e-commerce. U tom slučaju ne veruješ klijentima i moraš da uradiš sve moguće validacije (e sad, da li na DB serveru ili na aplikacionom, opet zavisi od neke procene resursa)
[ Sale_123 @ 01.10.2011. 14:19 ] @
Citat:
djoka_l: Nemam iskustva sa Intel bibliotekom, ali bih je, po nazivu, odmah izbacio iz razmatranja. Recimo, moji programi su bez izmena radili na Intel, Sparc, Alpha baziranim serverima (a imao sam i test instalacije na Z-Series IBM mainframe i na PowerPC procesorima). Zato uvek pokušavam da koristim samo portabilna rešenja. Ako tebi portabilnost nije interesantna, onda slobodno koristi ono što ti odgovara. Ali, ne zaboravi, da si pitanje postavio na MySQL forumu, a dotična baza postoji i van Intel okruženja.


Koliko sam razumio, ova biblioteka nije samo za intel, ali zahtjeva IA-32 ili IA-64. Znaci moze i AMD ;)

Citat:
djoka_l
Kao drugo, ja se nikada ne bih upustio u razmatranje nekog drugor rešenje, osim double, kada su u pitanju poslovne aplikacije. Jednostavo, nije vredno truda.


A sta je sa zaokruzivanjem? Use Float or Decimal for Accounting Application Dollar Amount?

Citat:
djoka_l
I na tvoje drugo pitanje odgovor zavisi od okruženja u kojoj će se aplikacija vrteti.
Ako ti je zamisao da se baza vrti na nekoj zveri od mašine, ne vidim zašto se ne bi validacija radila na DB serveru.
Ako ti je DB server kritičan po resursima, onda imaš dva scenarija
1) aplikacija radi u LAN-u, tako da imaš poveranja u klijente. U tom slučaju, zašto ne prebaciti deo validacije na klijente i osloboditi resure DB servera za kritičnije stvari
2) aplikacija se vrti u browserima, na primer radiš neki program za e-commerce. U tom slučaju ne veruješ klijentima i moraš da uradiš sve moguće validacije (e sad, da li na DB serveru ili na aplikacionom, opet zavisi od neke procene resursa)


He he, ne mislim ja ovo raditi nesto komercijalno niti veliko. Ovo je cisto projekat da se upoznam sa qt-om, sql i radom aplikacija u c++-u generalno. Nije mi cilj da sklepam aplikaciju (sto bi bilo vrlo jednostavno za uraditi), nego mi je cilj da izvucem sto vise iskustva radeci na ovom projektu. Recimo da mi je i baza i aplikacija na istom racunaru. Sta onda? Kakva su tvoja iskustva sa stored procedures? Sta je lakse za razvoj, odrzavanje, itd?

Generalno, stored procedures nisu korisnike ako zelimo da kreiramo query dinamicki (npr. pretraga baze).

Hvala na pomoci!
[ bogdan.kecman @ 02.10.2011. 02:30 ] @
stored procedure sluze da deo biznis logike prebacis na bazu - to je nekada bilo popularno danas se smatra zastarelim i pogresnim nacinom rada
vezano za odrzavanje onih redundantih polja mozes da uvedes trigere koji ce se brinuti da je sadrzaj tih polja uvek ispravan
sto se tice linkovanja vrednosti izmedju C-a i MySQL-a o tome brine posao konektor i ti ne moras da brines, ako ti u aplikaciji koristis double a u bazi decimal konektor ce to bez problema konvertovati tamo vamo .. ono sto je zgodno kod decimal-a je da imas "predvidivu" tacnost te "predvidivo" poredjenje dok kod float-a to nemas .. e sad za samo poredjenje u 99% slucajeva taj problem ne postoji (mora da se brine o onih 1%) ali predvidiva tacnost je vrlo bitna. Ako "poreska sluzba" radi svu racunicu na 2 decimale to sto ces ti da radis sa double vrednostima ce samo da znaci da se tvoja i njihova racunica ne poklapa ... posebno ako ispostavis fakturu klijentu na 1234.1234 dinara, nikad ti se nece poklopiti posto banka zaokruzuje na 2 decimale i onih 0.0034 din ti nikad nece leci na racun .. problem je tu vise zakonski nego programerski a ono sto ti decimal sa strane mysql-a nudi je predvidiva tacnost (za razliku od double-a) ... ne postoji nikakva potreba za externe biblioteke osim ako nemas nameru da radis sa imaginarnim / kompleksnim brojevima sto ce nasi poreski moguli verovatno da zahtevaju u bliskoj buducnosti no to je neka druga prica :(
[ Sale_123 @ 05.10.2011. 00:16 ] @
Ok, evo napravio sam testnu tabelu:

Code:
mysql> describe stavke_fakture;
+------------+---------------+------+-----+---------+----------------+
| Field      | Type          | Null | Key | Default | Extra          |
+------------+---------------+------+-----+---------+----------------+
| id         | int(11)       | NO   | PRI | NULL    | auto_increment |
| naziv_robe | varchar(250)  | YES  |     | NULL    |                |
| osnovica   | decimal(22,2) | YES  |     | NULL    |                |
| pdv        | decimal(22,2) | YES  |     | NULL    |                |
| cijena     | decimal(22,2) | YES  |     | NULL    |                |
+------------+---------------+------+-----+---------+----------------+


i u njoj se nalazi sledeca dva unosa:
Code:
mysql> select * from stavke_fakture;
+----+------------+----------------------+------+--------+
| id | naziv_robe | osnovica             | pdv  | cijena |
+----+------------+----------------------+------+--------+
|  1 | Kaladont   |                12.25 | 5.00 |  17.25 |
|  2 | Kuca       | 12345678901234567.89 | 0.26 |   0.00 |
+----+------------+----------------------+------+--------+
2 rows in set (0.00 sec)


Dio koda koji ispisuje vrijednosti iz tabele:
Code:

QSqlQuery query ("SELECT * FROM stavke_fakture");
int c_name = query.record().indexOf("naziv_robe");
int c_price = query.record().indexOf("osnovica");
while(query.next()) {
    std::cout << query.value(c_name).toString().toStdString() << " " 
    << std::fixed << query.value(c_price).toDouble() << std::endl;
}


Izlaz:
Code:

Kaladont 12.250000
Kuca 12345678901234568.000000


Kao sto vidite kod kuce je doslo do gubitka podataka. Kako to rijesiti? Kako prebaciti podatke u c++ a da ne dodje do gubitaka? Trenutno gledam source code od c++ konektora za MySQL i u resultset.h vidim sledece metode:
getBlob
getBoolean
getDouble
getInt
getUInt
getInt64
getUInt64
getString

Nigdje ne vidim metodu koju bi mogao iskoristiti da prebacim DECIMAL tip u c++ a da ne dodje do gubitka. Mozda getString?
[ bogdan.kecman @ 05.10.2011. 09:32 ] @
sta ti otstampa kada probas

Code:

QSqlQuery query ("SELECT * FROM stavke_fakture");
int c_name = query.record().indexOf("naziv_robe");
int c_price = query.record().indexOf("osnovica");
while(query.next()) {
    std::cout << query.value(c_name).toString().toStdString() << " " 
    << std::fixed << query.value(c_price) << std::endl;
}


double je u c-u prosecno 15 cifara ti ovde imas 19 cifara, c ti prikazuje 17 cifara sto je otprilike maximum. long double postoji u nekim implementacijama c/c++ (16 bitni) ali je pitanje kako se implemtira gde, long double je u microsnot c/c++ sinonim za double, intel kompajler ima /qlong‑double (ili bese Q veliko nisam siguran) opciju kada implementira long double kao 80bitni double dok bez te opcije se valjda isto ponasao kao sinonim za double, ne secam se za gcc ali bese long double implementira zavisnosti od opcija -m128bit-long-double bi rekao da pravi 128 bitni long double ..

ako se dobro secam, mysql konektor nema long double nego samo double tako da si u tom slucaju limitiran sa 15-16 cifara za preciznost mada nisam siguran, nisam davno gledao source za c/c++ konektor ... probaj da ga sipnes u long double mozda i proradi .. uvek mozes da ga sisnes kao string no ne znam sta ces sa njim kao stringom osim da ga prikazes, neces bas moci nesto da racunas :D
[ bogdan.kecman @ 05.10.2011. 09:41 ] @
sad sam pogledao source c konektora, getDouble vraca long double .. dakle samo je bitno kako ga iskompajliras
[ Sale_123 @ 06.10.2011. 01:01 ] @
Citat:
bogdan.kecman: sad sam pogledao source c konektora, getDouble vraca long double .. dakle samo je bitno kako ga iskompajliras

Tacno, upravu si, samo sto nazalost Qt ne koristi C++ konnector, nego koristi C konektor i za tip decimal vrsi konverziju u double a ne long double. Steta.

Citat:
bogdan.kecman
uvek mozes da ga sisnes kao string no ne znam sta ces sa njim kao stringom osim da ga prikazes, neces bas moci nesto da racunas :D

Mogao bi da nadjem zarez, izbacim ga, i konvertujem u long int i da zadnje dvije/tri/cetiro cifre gledam ko cente. Samo izgleda da to za sada u Qt-u nije moguce, tj. nije moguce iscitati ga kao string, jer qt automatski vrsi konverziju.
[ Sale_123 @ 06.10.2011. 02:51 ] @
Bogdane,

kolega mi je predlozio da koristim funkciju convert() da bi castovao decimal u char(23) i poslije u QT-u mogu da radim sta hocu sa Stringom. Ima li sta lose u tome sto se tice mysql-a?

Code:
"SELECT id, naziv_robe, CONVERT(osnovica, CHAR(23)) as osnovica, pdv, cijena FROM `stavke_fakture`"
[ djoka_l @ 06.10.2011. 08:27 ] @
Pa ja sam ti odmah u prvom postu rekao da možeš da koristiš stringove, a ti si to tek sada primetio, nakon što ti je kolega rekao.
Primer ti je potupuno nerealan, cena kuće sa 21 tačnom cifrom, vidi se da baš nemaš iskustva sa finansijskim aplikacijama. Lepo sam napisao, za obračune koje ćeš realno imati potrebe da radiš, double je dovoljan. Za prikaz na ekranu koristi string, ako baš moraš. Kada želiš da upišeš u bazu rezultat nekog obračuna OBAVEZNO radi round. Iako je iznos deklarisan kao DECIMAL(22,2) uvek je pametnije da se radi eksplicitna konverzija. Ovo ti kažem iz iskustva, jer samo imao slučaj da sam morao (doduše na ORACLE bazi) jednom da uradim CREATE TABLE tbl AS SELECT * from tbl1 UNION ALL SELECT * from tbl2, pa sam u rezulrutućoj tabeli dobio da mi je polje koje je bilo NUMBER(22,2) postalo samo NUMBER, a to se prvo primetilo posle obračuna kamate, kada su se pojavile decimale iza druge.
[ bogdan.kecman @ 06.10.2011. 10:50 ] @
i c konektor bi trebalo da cita decimal kao long double. problem je sto je po defaultu u c-u long double sinonim za double, moras da mu kazes da koristi "pravi" long double (i moras da prekompajliras c konektor takodje sa istom opcijom kojom kompajliras tvoj program)

nemas nikakav problem sa kastovanjem u string ako hoces da koristis samo za prikaz, no kako ti kolega rece broj koji si stavio je neprimeren, ako nesto kosta 12 peta dinara onih .89 ni u jednom softwareu na svetu niko nece da prati ... zato double i radi tako kako radi ... (pamti 15-16 znacajnih cifara + exponent).

[ Sale_123 @ 06.10.2011. 12:48 ] @
Citat:
djoka_l: Pa ja sam ti odmah u prvom postu rekao da možeš da koristiš stringove, a ti si to tek sada primetio, nakon što ti je kolega rekao.
Primer ti je potupuno nerealan, cena kuće sa 21 tačnom cifrom, vidi se da baš nemaš iskustva sa finansijskim aplikacijama. Lepo sam napisao, za obračune koje ćeš realno imati potrebe da radiš, double je dovoljan. Za prikaz na ekranu koristi string, ako baš moraš. Kada želiš da upišeš u bazu rezultat nekog obračuna OBAVEZNO radi round. Iako je iznos deklarisan kao DECIMAL(22,2) uvek je pametnije da se radi eksplicitna konverzija. Ovo ti kažem iz iskustva, jer samo imao slučaj da sam morao (doduše na ORACLE bazi) jednom da uradim CREATE TABLE tbl AS SELECT * from tbl1 UNION ALL SELECT * from tbl2, pa sam u rezulrutućoj tabeli dobio da mi je polje koje je bilo NUMBER(22,2) postalo samo NUMBER, a to se prvo primetilo posle obračuna kamate, kada su se pojavile decimale iza druge.


Izvini, ali ja sam mislio da hoces da u tabeli koristim char umjesto decimal. To mi ne odgovora jel hocu da imam mogucnost da radim racunanja i upite pomocu SQL-a. Ako bi cijene bile snimljenje u char-u, ne bi mogao da vrsim racunanje. Sada sam se sjetio da bi mozda mogao izvrsiti kastovanje, za sta prije nisam znao da mysql ima mogucnost.

Znam da je cijena nerealna, poenta je bila da ispitam sta se desava kad je cifra velika i na koji nacin se ucitava i iscitava. Mysql podrzava decimal do 65 cifara, ali mi ocito nemamo nacina da je iscitamo nego da koristimo C-api i gledamo vrijednost kao string, a ne kao double.

Tacno, dao sam nerealan primjer, ali kroz istoriju je bilo slucajeva da dodje do jake inflacije (daleko bilo) i da cifre sa kojima se barata postanu milion, miliarde i trilioni. Ja samo hocu da znam, ako stavim polje DECIMAL(22,2) ili npr. DECIMAL(18,4), do to stvarno jeste moguca tacnost, a ne da imamo prividnu tacnost. Druga stvar, GnuCash koristi svoju format gnc_numeric, koji je u osnovi koristi da 64-bitna integera (nazivnik i kolicnik), i stalno me kopka zasto su tako radili a nisu koristili double.
I ne radi se ovde samo o kalkulacijama, sta recimo da pravim npr. program za razmjenu valuta, gdje moramo da cuvamo 7-8 decimala plus milionske iznose. Jel moze onda long double da to podrzi? Znaci hocu da znam na cemu sam i gdje je granica, a normalno je da cu za neke stvari koristiti double umjestio da se zezam sa ovim.

U svakom slucaju, hvala puno na savjetima, ti i Bogdan ste mi mnogo pomogli, i sto se tice tehnicke strane i sto se tice uvida u rad finansiskih aplikacija.
[ Sale_123 @ 06.10.2011. 12:55 ] @
Citat:
bogdan.kecman: i c konektor bi trebalo da cita decimal kao long double. problem je sto je po defaultu u c-u long double sinonim za double, moras da mu kazes da koristi "pravi" long double (i moras da prekompajliras c konektor takodje sa istom opcijom kojom kompajliras tvoj program)

nemas nikakav problem sa kastovanjem u string ako hoces da koristis samo za prikaz, no kako ti kolega rece broj koji si stavio je neprimeren, ako nesto kosta 12 peta dinara onih .89 ni u jednom softwareu na svetu niko nece da prati ... zato double i radi tako kako radi ... (pamti 15-16 znacajnih cifara + exponent).

Ja koliko sam vidio, c konektor koristi char za sve podatke (typedef char **MYSQL_ROW; /* return data as array of strings */). A sto se tice c++ konektora, to su upravu za double i long double i hvala ti mnogo sto si mi napomenuo da nije kod svih kompajlera isto.

Sto se tice cijene, jeste neralna, ali htio sam da znam gdje je granica. Ono sto mi nikako nije jasno, zasto GnuCash ne koristi double ili long double, nego koriste svoj tim gnc_numeric.
[ bogdan.kecman @ 06.10.2011. 13:04 ] @
koriste svoj tip zato sto onda mogu da ga implementiraju lako isto na svim platformama ... long double nije isti svuda on je 8 bajta, 10, 12, 16 bajtova ... zavisi od implementacije do implementacije ..

limit sa double je 15 znacajnih cifara
[ Sale_123 @ 06.10.2011. 21:53 ] @
Citat:
djoka_l: Kada želiš da upišeš u bazu rezultat nekog obračuna OBAVEZNO radi round. Iako je iznos deklarisan kao DECIMAL(22,2) uvek je pametnije da se radi eksplicitna konverzija.

Jel radis round(moj_var * 100) / 100 pa snimas u double ili imas neki drugi nacin na koji radis round?

Hvala jos jednom na pomoci!
[ djoka_l @ 07.10.2011. 10:17 ] @
round(neki_broj, broj_decimala)

Code (sql):

UPDATE faktura
    SET iznos = round(neto_cena*(1+stopa_pdv), 2)
WHERE id = 1234
 
[ Sale_123 @ 07.10.2011. 11:26 ] @
Aaaa, ti koristis mysql funkciju za round, ja sam mislio da round radis prije u C, C++-u. Hvala!