Oracle OCI - Sum() a zjisteni poctu desetinych mist secteneho sloupce

Jan Serak sherry na pikebo.cz
Středa Září 15 20:09:55 CEST 2004


Novák Jiří wrote:
> Dobry den preji !

Zdravicko!

> 
> narazil jsem na problem s kodem  ktery zjistuje pocet desetinych mist  
> numerickeho sloupce
> 
> result = OCIAttrGet (
>                param_handle,
>                OCI_DTYPE_PARAM,
>                &scale,
>                &scale_size,
>                OCI_ATTR_SCALE,
>                m_pSession->m_error_handle);

Tesi me, ze v Cesku taky nekdo pouziva OCI8 ;-)

> 
> Uvedena vec nefunguje pokud sloupec vznikl funkci sum()
> Ocekaval bych ze funkce vrati stejny pocet desetinych mist jako ma 
> puvodni secitany sloupec.
> Lec bohuzel  vraci 0 (puvodni sloupec je NUMBER(15,2))

Sum urcite nemuze mit vysledek stejneho typu jako jsou scitance z 
naprost jednoducheho duvodu: predstavte si tabulku T s milionem zaznamu, 
jejiz atribut A je typu number(1). Co by si chudak Oracle pocal se:

SELECT sum(a) FROM t;

;-)

Datovy typ v Oracle ma (na rozdil od klasickych programovacich jazyku) 
dve charakteristiky. Jedna urcuje, jakym zpusobem se muze s hodnotou 
daneho typu manipulovat (shodna s programovacimi jazyky se silnou 
typovou kontrolou), druha charakteristika je zpusob ukladani do databaze.

Druha charakteristika se uplatnuje vlastne pouze u definice tabulek, 
protoze pro typovou kontrolu podprogramu, views apod. neni relevantni.
Proto podprogramy nemaji parametry (resp. navratovou hodnotu) otypovany 
NUMBER(15,2), ale pouze NUMBER. To je i pripad funkce sum(), ktera vraci 
vzdy NUMBER. Analogicky, navratova hodnota funkce(-i) to_char je 
VARCHAR2 a nikoli VARCHAR2(<maximalni_delka>).

Jak to tedy je?

Oracle fyzicky uklada hodnoty datoveho typu NUMBER nikoli binarne, nybrz 
pomerne zbesilou variantou Binary-Coded-Decimal (BCD). Duvodem neni nic 
jineho nez rychlost zpracovani, coz v tento moment muze znit podivne (s 
binarnimi cisly muze pocitac pracovat nativne, kdezto tento paskvil...).
Zkuste si vsak spocitat, kolik bajtu zabere cislo 10^{120}, pripadne 
10^{-120}, coz jsou priblizne limity datoveho typu NUMBER, ktery zabere 
nejvyse 21 bajtu, ovsem v pripade 1^{120} by zabral 2 bajty(!).

Proto Oracle s datovym typem NUMBER pracuje jako s celociselnou mantisou 
a exponentem dekadickeho zakladu. Vice je to popsano jako nepovinne 
cteni v OCI Developers Guide.

Oracle rozlisuje mezi NUMBER(x), resp NUMBER(x,y), coz povazuje za cisla 
s pevnou desetinnou teckou, a NUMBER, coz povazuje za cisla s plovouci 
desetinnou teckou. V prvnim pripade tedy poskytuje jakousi dalsi 
kontrolu hodnoty pred jejim ulozenim do databaze (u NUMBER ulozi cokoli, 
co se vejde do limitu definovaneho pro NUMBER, tj. delka celociselne 
mantisy do 38, exponent dekadickeho zakladu v nejakem "hodne velkem" 
intervalu).

/* off-topic:
Limit 38 je IMHO zvolen velmi dobre vzhledem k praktickemu pouziti takto 
vysoce presnych cisel, staci si jen uvedomit, ze "nasobkove predpony" 
(nejsem si jist, jestli je to fyzikalne spravny termin) definovane SI 
jsou od 10^{18} do 10^{-18} a ze je potreba jedno "misto" na jednotky 
(10^0) a jedno na znamenko. Vetsi presnost opravdu neni prakticky potreba.
*/

Co s tim v OCIAttrGet?

Problem neni ve funkci samotne, ale v tom, jak chapete atribut 
OCI_ATTR_SCALE. Nyni cituji z dokumentace Oracle:

OCI_ATTR_PRECISION: The precision of numeric type attributes. If the 
precision is nonzero and scale is -127, then it is a FLOAT, else it is a 
NUMBER(precision, scale). For the case when precision is 0, 
NUMBER(precision, scale) can be represented simply as NUMBER.

OCI_ATTR_SCALE: ... (presne tyz text jako u OCI_ATTR_PRECISION)

Vas pripad, kdy OCI_ATTR_SCALE je 0, se rozpada na 2 moznosti v 
zavislosti na hodnote OCI_ATTR_PRECISION. Je-li ruzna od 0, pak se jedna 
o datovy typ NUMBER(x) (totez co NUMBER(x,0)), tj. pevna desetinna 
tecka, nejnizsi ukladany rad jsou jednotky. Pokud je vsak rovna 0, pak 
se jedna o datovy typ NUMBER, tj. plovouci desetinna tecka a pocet 
desetinnych mist je tedy irelevantni.

OCIAttrGet Vam skutecne rika pravdu. Zkuste v SQL*Plus tuhle vec:

CREATE TABLE zkusto AS (SELECT sum(1) suma FROM dual);
describe zkusto

Uvidite neco takoveho:

  Name                  Null?    Type
  --------------------- -------- ----------------------------
  SUMA                           NUMBER

Pak zkuste funkci OCIAttrGet zjistit hodnoty atributu OCI_ATTR_PRESISION 
a OCI_ATTR_SCALE nad sloupcem SUMA tabulky ZKUSTO. Uvidite, ze dostanete 
v obou pripadech 0.

> 
> a) existuje jina moznost jak se zeptat ?

Musite si uvedomit, co chcete zjistovat. Funkci OCIAttrGet zjistujete 
atribut datoveho typu, NIKOLI VLASTNOST KONKRETNI HODNOTY.

> b) lze presnost souctovaneho sloupce vynutit v dotazu na server ?

Obavam se, ze nikoli. Co zajima uzivatele, ktery vznesl dotaz z uvodu 
tohoto prispevku. Vysledek, nebo informace o tom, ze "se soucet neda 
nacpat do datoveho typu scitancu"? IMHO to prvni.

Obavam se, ze nechcete vynucovat presnost nejakeho sloupce, ale typ 
funkce sum().

Tedy, jestli presne chapu, co myslite tim "presnost souctovaneho sloupce".

> c) muze mit vliv jestli se ptam po Prepare nebo po Execute ? (pri 
> zjistovani velikosti by mohlo ....)

Nikoli. Uz po Prepare je jasny typ vyrazu, ktery se pri Execute nemeni 
na zaklade konkretnich hodnot.

> 
> Uvitam  figl v OCI, i obecne info tykajici se treba i jineho rozhrani k 
> oracle.

Nativnejsi rozhrani k Oracle, nez je OCI, najdete tezko ;-)
To, co zjevne ocekavate je, ze funkce sum() ma vracet hodnoty v pevne 
desetinne tecce. Ale v jake desetinne tecce? NUMBER(15,2)? Proc zrovna 
tuhle a ne treba NUMBER(18,8)? Nebo NUMBER(12,-23)?

Preji mnoho zdaru s OCI8!

Jan Serak



Další informace o konferenci Test