Oracle primary keys: NUMBER vs NUMBER(7,0) - oracle

Is there any benefit to specifying the precision on the PK? Is 7,0 sufficient, given that there will probably never be more than a few thousand records?
Any dangers to not specifying the precision?

NUMBER(7, 0) just constrains the domain of values.
Their internal represenations do not differ:
CREATE TABLE t_pk (col1 NUMBER(7, 0) NOT NULL, col2 NUMBER(38) NOT NULL)
INSERT
INTO t_pk
VALUES (9999999, 9999999)
SELECT DUMP(col1), DUMP(col2)
FROM t_pk
DUMP(col1) DUMP(col2)
--- ---
Typ=2 Len=5: 196,10,100,100,100 Typ=2 Len=5: 196,10,100,100,100
In Oracle, the NUMBERs are stored as centesimal digits of the numeric value normalized to 0.01 <= N < 1 and prepended with the exponent.
In the example above:
196 is the 192-based exponent (4).
10 is decimal 9
100's are decimal 99's
The whole number reads in decimal as 00.09 99 99 99 * (100 ^ 4) = 9,999,999
The more digits are required to satisfy the precision requested, the more of them will be stored of course.
When you insert a precise value into a less precise column, it just gets rounded to column's precision and is stored rounded.
Therefore, it is safe performance-wise to declare you column NUMBER(38), since it implies no overhead over NUMBER(7, 0) (for the numbers that fit both types).
However, if your PRIMARY KEYs are integer by nature, you better specify precision as 0 to make sure no fractional value ever gets to your table.
Update:
#Mac also pointed that the clients may rely on the column datatype to figure out the values domain.
If your application expects an INT32, you should make your number a NUMBER(9) or below (or whatever type your client considers to be convertable to Int32).

On the database side of the problem, I have nothing to add to Quassnoi's answer.
But it is worth noting that it may as well have an impact on applications that access the database (or, to be more accurate, on the developers of these applications). In .NET for instance, if you get an IDataRecord including your primary key (using ODP .NET), a call to GetInt32 will miserably fail when your column is defined as NUMBER(38) and succeed when defined as NUMBER(7) (even when when the value is in the correct range).

If you're not expecting more than, say 100K records in the table, if you specify the PK with N(7,0) you'll get an early warning if some runaway process ends up overflowing the PK. If you specified it with N(38) the warning will not appear so early, perhaps.
I'd always err on the side of constraining sizes to the smallest expected for the "life of the product", with a reasonable margin for error.

Related

sequence in cycle mode in oracle

I have tried to find some real use cases fot sequences in cycle mode.
we can use use sequence for generating unique IDs for primary keys but it is not needed because we can use IDENTITY as well.
can you give me some good practical scenarios for using CYCLE sequence incrementing by 1 and then by a higher number like 10 or 100?
thanks
As you said, we usually use sequences for generating unique values - a good choice for primary keys. As primary keys can't be duplicated, there's no much sense in cycling the sequence.
(As of the identity columns: yes, that's an option in recent Oracle database versions, but they didn't exist until 12c so we used and still use sequences in lower versions).
Personally, I've never used MAXVALUE; most of my sequences are simple, using default options, such as
create sequence seq;
However, if you do set MAXVALUE and don't pay attention to number of values you use from it, once you reach its maximum you'll get
ORA-08004: sequence SEQ.NEXTVAL exceeds MAXVALUE and cannot be instantiated
One solution to that problem is to remove maxvalue (or set it to a higher value); another one is to use CYCLE so that - once you reach the maximum - sequence keeps working. Though, you have to use the CACHE parameter along with it (and its value must be lower than one cycle):
SQL> create sequence seq maxvalue 3 cycle;
create sequence seq maxvalue 3 cycle
*
ERROR at line 1:
ORA-04013: number to CACHE must be less than one cycle
Cycle is "3", so you can't set CACHE to value higher than that. This works:
SQL> create sequence seq maxvalue 3 cycle cache 2;
Sequence created.
When to use it?
In cases where its value is part of a composite primary key, where the second (actually, other) column(s) make sure that cycled sequence values won't violate primary key.
Another option is a staging table; for example, you daily get up to 1 million rows that represent payments. If primary key is set to number(6), you can't let sequence unconstrained (without the maxvalue) because you won't be able to insert value higher than 999.999 into that column. But, if you use the CYCLE parameter, everything will work OK - there won't be duplicates and values will fit the column.

Oracle db sequence with hibenate sequence

I'm working in a project based on oracle db and JPA,
I have realized that a column whose value should be generated using a sequence SEQ_CASO has a precision= 12.
However in the sequence object declared in Oracle db I have found the maximum number is much bigger than a 12 figures number (9999999999999...):
I would like to know what would happen if the records number exceeded a 12 figures number in the db? Would the precision of 12 numbers defined by JPA crush the app or something?
Answering to your main question:
I would like to know what would happen if the records number exceeded a 12 figures number in the db?
Considering the code you quoted was made by reverse engineering, the column is probably defined in Oracle as NUMBER(12,0) (precision 12, scale 0).
That said, when the sequence in your application arrives to the point of generating 13 digit numbers, the Oracle database will return the following error when trying to insert these numbers in your table:
ORA-01438: value larger than specified precision allows for this column
The definition of precision and scale can be tricky in Oracle, especially when they are not explicitly defined (i.e. the col is defined just as NUMBER - no precision or scale specified).
More information about precision and scale:
What is the difference between precision and scale?
What is the default Precision and Scale for a Number in Oracle?
you don't need to define unique,nullable attributes with #Column annotation. When you define #Id annotation then it becomes primary key column which is not null and unique.
Even you don't need to define precision attribute and it works properly without it.
#Id
#SequenceGenerator(name="SEQ_CASO",sequenceName="SEQ_CASO_PK",initialValue=1500000,allocationSize=1)
#GeneratedValue(generator="SEQ_CASO",strategy=GenerationType.Sequence)
#Column("ID")
private long Id;

How to calculate increase in storage usage on Oracle 11g?

How would I calculate or estimate the increased storage difference from increasing the precision on a column from number(2,0) to number(6,0)? Or is it the same? No difference? Let's pretend I have 1 million rows in the table. What does the arithmetic look like?
The precision of a number field is basically just a constraint on how much precision Oracle will retain when storing a value. The underlying number format is actually the same (it's a varying-width field, and not something like a fixed-sized integer or float) - the amount of space required is related to the number of digits in the specific numbers which are being stored.
So if you don't modify the values in the table, then the size shouldn't change. But if you increase the precision and then update the table with values with more digits, then they will potentially consume more space.

Is there any way to preserve number precision (0.100 vs 0.1) in Oracle?

Is there any way to preserve number precision (0.100 vs 0.1) in Oracle? The precision needs to be stored for accountability reasons.
I want to avoid storing these numbers as a string because there is also a set of stored procedures which do some number crunching on these values.
No. The numbers 0.100, 0.1, and .1 are all identical from Oracle's perspective. They'll all have identical internal representations.
If you need to store the precision, you'll need a second column to store the precision. Or you'll need to store the number in a VARCHAR2 column and convert it to a number before doing the number crunching. Of course, you'll need to define the rules for handling precision in your number crunching logic. If you add 0.100 and 0.22, for example, is the result 0.32 or 0.320?
I would suggest storing both the numeric value for queries and mathematical operations, and the string version "as entered" for audit purposes.
I would suggest if the use case only requires the additional digits to be displayed then convert it to a string as the last step before sending the data to the report:
SELECT
TO_CHAR(column_name, 999.999)
FROM
table_name;

Once a HiLo is in use, what happens if you change the capacity (maximum Lo)?

If I start using a HiLo generator to assign ID's for a table, and then decide to increase or decrease the capacity (i.e. the maximum 'lo' value), will this cause collisions with the already-assigned ID's?
I'm just wondering if I need to put a big red flag around the number saying 'Don't ever change this!'
Note - not NHibernate specific, I'm just curious about the HiLo algorithm in general.
HiLo algorithms in general basically map two integers to one integer ID. It guarantees that the pair of numbers will be unique per database. Typically, the next step is to guarantee that a unique pair of numbers maps to a unique integer ID.
A nice explanation of how HiLo conceptually works is given in this previous SO answer
Changing the max_lo will preserve the property that your pair of numbers will be unique. However, will it make sure that the mapped ID is unique and collision-free?
Let's look at Hibernate's implementation of HiLo. The algorithm they appear to use (as from what I've gathered) is: (and I might be off on a technicality)
h = high sequence (starting at 0)
l_size = size of low block
l = low sequence (starting at 1)
ID = h*l_size + l
So, if your low block is, say, 100, your reserved ID blocks would go 1-100, 101-200, 201-300, 301-400...
Your High sequence is now 3. Now what would happen if you all of a sudden changed your l_size to 10? Your next block, your High is incremented, and you'd get 4*10+1 = 41
Oops. This new value definitely falls within the "reserved block" of 1-100. Someone with a high sequence of 0 would think, "Well, I have the range 1-100 reserved just for me, so I'll just put down one at 41, because I know it's safe."
There is definitely a very, very high chance of collision when lowering your l_max.
What about the opposite case, raising it?
Back to our example, let's raise our l_size to 500, turning the next key into 4*500+1 = 2001, reserving the range 2001-2501.
It looks like collision will be avoided, in this particular implementation of HiLo, when raising your l_max.
Of course, you should do some own tests on your own to make sure that this is the actual implementation, or close to it. One way would be to set l_max to 100 and find the first few keys, then set it to 500 and find the next. If there is a huge jump like mentioned here, you might be safe.
However, I am not by any means suggesting that it is best practice to raise your l_max on an existing database.
Use your own discretion; the HiLo algorithm isn't exactly one made with varying l_max in mind, and your results may in the end be unpredictable depending on your exact implementation. Maybe someone who has had experience with raising their l_max and finding troubles can prove this count correct.
So in conclusion, even though, in theory, Hibernate's HiLo implementation will most likely avoid collisions when l_max is raised, it probably still isn't good practice. You should code as if l_max were not going to change over time.
But if you're feeling lucky...
See the Linear Chunk table allocator -- this is logically a more simple & correct approach to the same problem.
What's the Hi/Lo algorithm?
By allocating ranges from the number space & representing the NEXT directly, rather than complicating the logic with high words or multiplied numbers, you can directly see what keys are going to be generated.
Essentially, "Linear Chunk allocator" uses addition rather than multiplication. If the NEXT is 1000 & we've configured range-size of 20, NEXT will advance to 1020 and we'll hold keys 1000-1019 for allocation.
Range-sized can be tuned or reconfigured at any time, without loss of integrity. There is a direct relationship between the NEXT field of the allocator, the generated keys & MAX(ID) existing in the table.
(By comparison, "Hi-Lo" uses multiplication. If the next is 50 & the multiplier is 20, then you're allocating keys around 1000-1019. There are no direct correlation between NEXT, generated keys & MAX(ID) in the table, it is difficult to adjust NEXT safely and the multiplier can't be changed without disturbing current allocation point.)
With "Linear Chunk", you can configure how large each range/ chunk is -- size of 1 is equivalent to traditional table-based "single allocator" & hits the database to generate each key, size of 10 is 10x faster as it allocates a range of 10 at once, size of 50 or 100 is faster still..
A size of 65536 generates ugly-looking keys, wastes vast numbers of keys on server restart, and is equivalent to Scott Ambler's original HI-LO algorithm.
In short, Hi-Lo is an erroneously complex & flawed approach to what should have been conceptually trivially simple -- allocating ranges along a number line.
I tried to unearth behviour of HiLo algorith through a simple helloWrold-ish hibernate application.
I tried a hibernate example with
<generator class="hilo">
<param name="table">HILO_TABLE</param>
<param name="column">TEST_HILO</param>
<param name="max_lo">40</param>
</generator>
Table named "HILO_TABLE" created with single column "TEST_HILO"
Initially I set value of TEST_HILO column to to 8.
update HILO_TABLE set TEST_HILO=8;
I observed that pattern to create ID is
hivalue * lowvalue + hivalue
hivalue is column value in DB (i.e. select TEST_HILO from HILO_TABLE )
lowvalue is from config xml (40 )
so in this case IDs started from 8*40 + 8 = 328
In my hibernate example i added 200 rows in one session. so rows were created with IDs 328 to 527
And in DB hivalue was incremented till 13.
The increment logic seems to be :-
new hivalue in DB = inital value in DB + (rows_inserted/lowvalue + 1 )
= 8 + 200/40 = 8 + 5 =13
Now if I run same hibernate program to insert rows, the IDs should start from
13*40 + 13 = 533
When ran the program it was confirmed.
Just by experience I'd say: yes, decreasing will cause collisions. When you have a lower max low, you get lower numbers, independent of the high value in the database (which is handled the same way, eg. increment with each session factory instance in case of NH).
There is a chance that increasing will not cause collisions. But you either need to try or ask someone who knows better then I do to be sure.
Old question, I know, but worth answering with a 'yes, you can'
You can increase or decrease your nex_hi at any point as long as you recompute your hibernate_unique_key table based on the current Id numbers of your tables.
In our case, we have a Id per entity hibernate_unique_key table with two columns:
next_hi
EntityName.
The next_hi for any given Id is calculated as
SELECT MAX(Id) FROM TableName/(#max_lo + 1) + 1
The script below runs through every table with an Id column and updates our nex_hi values
DECLARE #scripts TABLE(Script VARCHAR(MAX))
DECLARE #max_lo VARCHAR(MAX) = '100';
INSERT INTO #scripts
SELECT '
INSERT INTO hibernate_unique_key (next_hi, EntityName)
SELECT
(SELECT ISNULL(Max(Id), 0) FROM ' + name + ')/(' + #max_lo + ' + 1) + 1, ''' + name + '''
'
FROM sys.tables WHERE type_desc = 'USER_TABLE'
AND COL_LENGTH(name, 'Id') IS NOT NULL
AND NOT EXISTS (select next_hi from hibernate_unique_key k where name = k.EntityName)
DECLARE curs CURSOR FOR SELECT * FROM #scripts
DECLARE #script VARCHAR(MAX)
OPEN curs
FETCH NEXT FROM curs INTO #script
WHILE ##FETCH_STATUS = 0
BEGIN
--PRINT #script
EXEC(#script)
FETCH NEXT FROM curs INTO #script
END
CLOSE curs
DEALLOCATE curs

Resources