Why use varchar2 instead of char for package constants? - oracle

Just came across a package which defines a large number of package-global constant strings as such:
DESTINATION_1 CONSTANT VARCHAR2(13) := '515 Pine Lane';
DESTINATION_2 CONSTANT VARCHAR2(18) := '670 Woodhaven Lane';
Is there any benefit to using varchar2 as the datatype for these over char?
Using Oracle 11g release 2.

In general I use VARCHAR2 instead of CHAR for all string variable or constant declarations for the simple reason that VARCHAR2 semantics are more consistent with my expectations. The issue here is that if the developer is less than accurate about his counting of the length of the constant, it will be padded on the right with blanks if declared as CHAR, while it will simply be stored without padding if declared as VARCHAR2. Consider:
DECLARE
strFixed CHAR(20) := 'This is a string';
strVariable VARCHAR2(20) := 'This is a string';
BEGIN
IF strFixed = strVariable THEN
DBMS_OUTPUT.PUT_LINE('Equal');
ELSE
DBMS_OUTPUT.PUT_LINE('Not equal');
END IF;
END;
At first look you might expect this to print "Equal", but it will actually print "Not equal". This is so because strFixed is not stored as 'This is a string'; instead, it's stored as "This is a string ' because CHAR variables are padded on the right with blanks out to the size specified in the variable declaration. Yes, I could have carefully counted the number of characters in the string (there are 17, by the way) and then adjusted the declaration carefully, but that is just SO 1970's (a decade I remember somewhat hazily and don't care to revisit :-). And, oh dear, I miscounted the number of characters in the string, so the fixed string would have been padded on the right to fill it out to the declared length and my comparison still wouldn't have worked.
The one case where I'll use CHAR instead of VARCHAR2 is if the variable of constant is only supposed to be a single character long. IMO declaring something as VARCHAR2(1) is just wrong. :-)
Just in passing I'll note that if you look in the package SYS.STANDARD you'll find that CHAR is declared as
subtype CHAR is VARCHAR2;
Thus, a CHAR is a VARCHAR2. Not sure how the space-padding is done, but it may well be that the space-padding is done at runtime and thus adds additional time.
Is there a performance advantage to one or the other? At most it won't be much. I suppose that if a CHAR variable has some space-padding it'll take a hair longer to compare than will an equivalent unpadded VARCHAR2 value, but in practical terms I don't believe this will matter. Also, since the space-padding is done at runtime that's going to add time. I suspect it's a wash, and will certainly be swamped by SQL effects.
Share and enjoy.

A CHAR datatype and VARCHAR2 datatype are stored identically ... So, in the case you describe, there's no difference.
The difference between a CHAR and a VARCHAR is that a CHAR(n) will ALWAYS be N bytes long, it will be blank padded upon insert to ensure this. A varchar2(n) on the other hand will be 1 to N bytes long, it will NOT be blank padded.

Hi this may be my assumption but varchar2 might have been used coz of performance,
a char is just a varchar2 blank padded out to the maximum length.
create table t ( x varchar2(30), y char(30) );
insert into t (x,y) values ( rpad('a',' ',30), 'a' );
IS ABSOLUTELY NOTHING, and given that the difference between columns X and Y below:
insert into t (x,y) values ('a','a')
is that X consumes 3 bytes (null indicator, leading byte length, 1 byte for 'a') and Y consumes 32 bytes (null indicator, leading byte length, 30 bytes for 'a ' )
Umm, varchar2 is going to be somewhat "at an advantage performance wise". It helps us NOT AT ALL that char(30) is always 30 bytes - to us, it is simply a varchar2 that is blank padded out to the maximum length.

Related

Why can't I trim a column of type CHAR?

Like the title says, if a create a table in my DB :
CREATE TABLE TEST
(
FIELD CHAR(20 CHAR) NULL
)
NOLOGGING
NOCOMPRESS
NOCACHE;
Insert this :
Insert into TEST
(FIELD)
Values
('TEST -here are blank spaces- ');
COMMIT;
Then i run the following statement :
UPDATE TEST SET FIELD = TRIM(FIELD);
COMMIT;
but the field still has blank spaces, notice that if I change the data type to varchar2, it works ... does anyone know why?
Thanks!
char is a fixed width data type. A char(20) will always and forever have a length of 20. If you try to insert a shorter string, it will be padded with spaces to the fixed width length of the field. So
UPDATE TEST SET FIELD = TRIM(FIELD);
removes the spaces due to the trim function, then adds them back because the string that gets written has to be exactly 20 bytes long.
Practically, there is almost never a case to use char. You're almost always better off with a varchar2. varchar2 is a variable length data type so there is no need for the database to append the spaces to the end.

Writing a Version Number Function in PL/SQL

I want to write a function that will give me the next version number for a table. The table stores the existing version on each record. For example,
I have the cat table
cats
seqid 1
name Mr Smith
version number 1.2b.3.4
How can I write a program that will be able to increment these values based on various conditions?
This is my first attempt
if v_username is not null
then v_new_nbr = substr(v_cur_nbr, 1,7)||to_number(substr(v_cur_nbr, 8,1))+1
should be 1.2b.3.5
substr(v_cur_nbr, 1,7)||to_number(substr(v_cur_nbr, 8,1))+1
This hurls ORA-01722: invalid number. The reason is a subtle one. It seems Oracle applies the concatenation operator before the additions, so effectively you're adding one to the string '1.2b.3.4'.
One solution is using a TO_CHAR function to bracket the addition with the second substring before concatenating the result with the first substring:
substr(v_cur_nbr, 1,7) || to_char(to_number(substr(v_cur_nbr, 8,1))+1)
Working demo on db<>fiddle.
Incidentally, a key like this is a bad piece of data modelling. Smart keys are dumb. They always lead to horrible SQL (as you're finding) and risk data corruption. A proper model would have separate columns for each element of the version number. We can use virtual columns to concatenate the version number for display circumstances.
create table cats(
seqid number
,name varchar2(32)
,major_ver_no1 number
,major_ver_no2 number
,variant varchar2(1)
,minor_ver_no1 number
,minor_ver_no2 number
,v_cur_nbr varchar2(16) generated always as (to_char(major_ver_no1,'FM9') ||'.'||
to_char(major_ver_no2,'FM9') ||'.'||
variant ||'.'||
to_char(minor_ver_no1,'FM9') ||'.'||
to_char(minor_ver_no2,'FM9') ) );
So the set-up is a bit of a nause but incrementing the version numbers is a piece of cake.
update cats
set major_ver_no1 = major_ver_no1 +1
, major_ver_no2 = 0
, variant = 'a';
There's a db<>fiddle for that too.
Try searching mask for TO_NUMBER to be able to get the decimal number, this small example might help:
CREATE TABLE tmp_table (version varchar2(100));
INSERT INTO tmp_table(version) VALUES ('1.2b.3.4');
DECLARE
mainVersion NUMBER;
subVersion NUMBER;
currentVersion VARCHAR2(100);
BEGIN
SELECT version INTO currentVersion FROM tmp_table;
mainVersion := TO_NUMBER(SUBSTR(currentVersion,1,3),'9.9') + 0.1;
subVersion := TO_NUMBER(SUBSTR(currentVersion,6,3),'9.9') + 1.1;
UPDATE tmp_table SET version = (mainVersion||'b.'||subVersion);
END;

Calculate database size after convert to Unicode

We want to convert our database (oracle 11g) from this character set: ISO-8859-8
to this character set: AL32UTF8.
The new mode need to support European characters and more.
Those languages can appear in a lot of tables.
I want to get some estimate about the new size of tables\whole database
according to the the current data.
Is there a good way to do that?
I don't think it will change much. I presume your VARCHAR2 columns are currently defined like VARCHAR2(30), which by default means VARCHAR2(30 BYTE). And that will remain unchanged after the conversion. If you defined them as VARCHAR2(30 CHAR), I'm not sure what would happen. When you define a column with character symantics, I think Oracle reserves as much space as it might need, which is 4 bytes per character for AL32UTF8.
I don't understand why you are so concerned about the size. Perhaps the DB will be a few(!) percent larger, most likely you will not notice any difference.
Anyway, this PL/SQL should give some idea on the size:
declare
iso_size number;
utf8_size number;
iso_size_sum number := 0;
utf8_size_sum number := 0;
begin
for aCol in (select table_name, column_name from user_tab_cols where data_type = 'VARCHAR') loop
iso_size.extend;
utf8_size.extend;
execute immediate
'select sum(LENGTH('||aCol.column_name||')),
sum(LENGTHB(convert('||aCol.column_name||', ''AL32UTF8'')))
from '||aCol.table_name INTO iso_size, utf8_size;
iso_size_sum := iso_size_sum + iso_size;
utf8_size_sum := utf8_size_sum + utf8_size ;
end loop;
dbms_output.put_line('Current size: '||to_char(iso_size_sum/1024/1024))||' MiByte');
dbms_output.put_line('Estimated UTF-8 size: '||to_char(utf8_size_sum/1024/1024))||' MiByte');
end;
The two numbers should give you an indication(!) how much the database would grow. Note, the data in Oracle are organized in Blocks (typically 8 kiBytes) not bytes.
Due to performance reasons you should run the query only on one representative table not the entire schema.

SQLDeveloper query automatic padding CHAR field

Given the table ATABLE with a field AFIELD of type CHAR(8), and where i have a field with value "1234567 "
Why, in SQL Developer, if I query:
SELECT * FROM ATABLE WHERE AFIELD = '1234567';
It will automatically pad the missing space and return the results, and if I query with :
SELECT * FROM ATABLE WHERE AFIELD = :value;
and input the value, it wont ?
From the documentation:
Within expressions and conditions, Oracle treats text literals as though they have the data type CHAR by comparing them using blank-padded comparison semantics.
When you do WHERE AFIELD = '1234567' the text literal '1234567' is treated as char and blank-padded comparison semantics are used to compare the column value and the literal. Even though the literal doesn't have the trailing space, those semantics see them as the same, so it finds a match.
When you use a bind variable the literal you assign to it is a char, but the bind variable itself is varchar2 - even if you declare it as char, oddly, though in that case the value is blank-padded anyway:
var char_value char(8);
exec :char_value := '1234567';
var varchar2_value varchar2(8);
exec :varchar2_value := '1234567';
select dump('1234567') as d1, dump(:char_value) as d2, dump(:varchar2_value) as d3
from dual;
D1 D2 D3
------------------------------------ ------------------------------------ ------------------------------------
Typ=96 Len=7: 49,50,51,52,53,54,55 Typ=1 Len=8: 49,50,51,52,53,54,55,32 Typ=1 Len=7: 49,50,51,52,53,54,55
The text literal is data type 96 (char), while both bind variables are type 1 (varchar/varchar2); but notice the char_value bind variable has the trailing space, with length 8 and the last character as code point 32.
When you compare your char column value with a varchar2 bind variable the column value is implicitly converted from char to varchar2:
The following rules govern implicit data type conversions:
During SELECT FROM operations, Oracle converts the data from the column to the type of the target variable.
So your space-padded char(8) column value is implicitly converted to varchar2(8) to match the bind variable's data type, and then because they are varchar2 the nonpadded comparison semantics are used.
When you compare your char(8) column with the supposedly-char(8) bind variable, you're actually comparing with a padded varchar2(8) - but both the implicitly converted column value and the blank-padded bind variable are actually the same, both with the trailing space; '1234567 ' is the same as '1234567 ', so there is a match, even with nonpadded comparison semantics.
With the varchar2(8) bind variable the same thing happens, but now the bound value is not padded, and as you are using nonpadded comparison semantics to compare '1234567 ' with '1234567' - they are not the same, so there is no match, and no data is returned by the query.
As #a_horse_with_no_name said you should almost always use varchar2 rather than char. But if you must use it and are stuck with it then at least make sure you use the same data type for comparisons.
You are right
SELECT * FROM ATABLE WHERE AFIELD = :value;
does not work with CHAR as you desire.
Anyway I have noticed that the following query works as you desire:
SELECT * FROM ATABLE WHERE AFIELD = &value;
If you use &value in several places, you can use &&value (double &) the first time (and &value elsewhere),
in order to avoid to input the same value several times;
when you have to change that value, you can undefine it with:
undef value;

difference between char(3) and char(3 char) for the below description

When declaring a CHAR or VARCHAR2 variable, to ensure that it can always hold n characters in any multibyte character set, declare its length in characters—that is, CHAR(n CHAR) or VARCHAR2(n CHAR), where n does not exceed FLOOR(32767/4) = 8191.
much value value we get from FLOOR(8191)
If you don't have to worry about multi-byte characters, then this is not a concern of yours. You'd also have to start using n* string manipulation functions. And so if I had a VARCHAR2(40) field, I could wind up with 6502 errors when inserting a 40 character multi-byte string. My 40 character string is no longer 40 bytes long but 160 bytes long.

Resources