You asked: I need to have a query which will give me the smallest value in a table and the column containing it. Please note there can be any number of columns. All will be number data type.
We said: This can be achieved by unpivoting the result set and then group them to find their minimum value as shown below.
Tuesday, December 12, 2017
Sunday, November 5, 2017
Implicit conversion of Cursor FOR LOOP into BULK FETCH using the PLSQL_OPTIMIZE_LEVEL parameter
Prior to the Oracle version 10gR1, the PL/SQL compiler does not apply any changes to the PL/SQL unit to improve their performance while translating them to the system code. From and after the release 10gR1, Oracle has introduced the initialization parameter PLSQL_OPTIMIZE_LEVEL to instruct the PL/SQL optimizer to automatically rearrange the source code, remove redundant and unreachable code, transform the FOR loops to Bulk Collect, and inline the subroutines for better performance.
The PL/SQL optimizer performs the optimization techniques based on the level value that is set for the PLSQL_OPTIMIZE_LEVEL parameter. The possible values allowed for this parameter are 0, 1, 2 (Default), 3 (11g and higher only). The higher the value set for this parameter, the more the effort the PL/SQL optimizer makes to optimize the PL/SQL units.
The prototype of the PLSQL_OPTIMIZE_LEVEL initialization parameter is shown below,
Where, 2 is the default.
Altering the Optimization Level
The PL/SQL optimization level can be set/altered using the PLSQL_OPTIMIZE_LEVEL initialization parameter either at the SYSTEM or SESSION or at the OBJECT level.
The prototype for setting the optimization level either at the SYSTEM or SESSION level is shown below,
The prototype for setting the optimization at the object level is shown below,
If the object is recompiled without the REUSE SETTINGS clause, it takes the database’s current optimization level as its own. With this clause included in the compilation script, the object retains the optimization level it was last compiled with, instead of the database’s current optimization level.
Consider the test procedure for the further demonstration of the PLSQL_OPTIMIZE_LEVEL parameter for the implicit conversion of LOOPs into BULK FETCHes.
After the procedure is created, its compilation mode can be verified by querying the USER_PLSQL_OBJECT_SETTINGS data dictionary table as shown below,
NAME TYPE PLSQL_OPTIMIZE_LEVEL
---------------- ------------------ ------------------------------------
PROC_TEST PROCEDURE 2
Note: The default optimization level for the procedure will be the database’s current optimization level.
The PLSQL_OPTIMIZE_LEVEL=2 setting performs the below implicit actions on the package.
The above trace file output is extracted when the procedure code was executed with the PLSQL_OPTIMIZE_LEVEL parameter value set to <=1. The total number of rows processed is 2,24,208 and the total fetch operations performed is 2,24,214. This indicates that approximately 1 row is fetched for each fetch operation, resulting in CPU overhead and context switching.
The above trace file output is extracted when the procedure code was executed with the PLSQL_OPTIMIZE_LEVEL parameter value set to >=2 in the NCOMP mode (PLSQL_CODE_TYPE=NATIVE). The total number of rows processed is 2,24,208 and the total fetches performed is 2,247. This shows that the recursive statements are implicitly converted into ARRAY fetches with a limit of 100.
This approach automatically reduces the context switching and increases the performance of the package.
Thank you,
Boobal Ganesan
Author of Advanced PL/SQL Programming - The Definitive Reference
The PL/SQL optimizer performs the optimization techniques based on the level value that is set for the PLSQL_OPTIMIZE_LEVEL parameter. The possible values allowed for this parameter are 0, 1, 2 (Default), 3 (11g and higher only). The higher the value set for this parameter, the more the effort the PL/SQL optimizer makes to optimize the PL/SQL units.
The prototype of the PLSQL_OPTIMIZE_LEVEL initialization parameter is shown below,
plsql_optimize_level=<0|1|2|3>;
Where, 2 is the default.
Altering the Optimization Level
The PL/SQL optimization level can be set/altered using the PLSQL_OPTIMIZE_LEVEL initialization parameter either at the SYSTEM or SESSION or at the OBJECT level.
The prototype for setting the optimization level either at the SYSTEM or SESSION level is shown below,
ALTER <SYSTEM | SESSION> SET plsql_optimize_level=<0|1|2|3>;
The prototype for setting the optimization at the object level is shown below,
ALTER <object_type> <object_name> COMPILE plsql_optimize_level = <0|1|2|3>;
If the object is recompiled without the REUSE SETTINGS clause, it takes the database’s current optimization level as its own. With this clause included in the compilation script, the object retains the optimization level it was last compiled with, instead of the database’s current optimization level.
Consider the test procedure for the further demonstration of the PLSQL_OPTIMIZE_LEVEL parameter for the implicit conversion of LOOPs into BULK FETCHes.
CREATE OR replace PROCEDURE Proc_test
authid current_user
IS
l_pi_start_time PLS_INTEGER;
l_pi_end_time PLS_INTEGER;
l_pi_cpu_start_time PLS_INTEGER;
l_pi_cpu_end_time PLS_INTEGER;
l_pi_var1 PLS_INTEGER;
l_pi_var2 PLS_INTEGER;
FUNCTION Func_test( ip_pi_var1 PLS_INTEGER)
RETURN PLS_INTEGER
IS
BEGIN
RETURN ip_pi_var1+ip_pi_var1;
END;
BEGIN
l_pi_start_time :=NULL;
l_pi_cpu_start_time :=NULL;
l_pi_start_time :=dbms_utility.get_time;
l_pi_cpu_start_time :=dbms_utility.get_cpu_time;
FOR i IN
(
SELECT object_name
FROM dba_objects )
LOOP
NULL;
END LOOP i;
FOR j IN
(
SELECT table_name,
column_name
FROM dba_tab_cols )
LOOP
l_pi_var2 :=0;
l_pi_var2 :=1;
END LOOP j;
l_pi_var2 :=Func_test(10);
IF 1 =1 THEN
l_pi_var2 :=100;
ELSE
l_pi_var2:=1000;
END IF;
NULL;
l_pi_end_time :=dbms_utility.get_time;
l_pi_cpu_end_time :=dbms_utility.get_cpu_time;
dbms_output.put_line( 'Program run time: '
||(l_pi_end_time l_pi_start_time) /100
||' Seconds');
dbms_output.put_line( 'CPU run time: '
||(l_pi_cpu_end_time l_pi_cpu_start_time)/100
||' Seconds');
END;
/
authid current_user
IS
l_pi_start_time PLS_INTEGER;
l_pi_end_time PLS_INTEGER;
l_pi_cpu_start_time PLS_INTEGER;
l_pi_cpu_end_time PLS_INTEGER;
l_pi_var1 PLS_INTEGER;
l_pi_var2 PLS_INTEGER;
FUNCTION Func_test( ip_pi_var1 PLS_INTEGER)
RETURN PLS_INTEGER
IS
BEGIN
RETURN ip_pi_var1+ip_pi_var1;
END;
BEGIN
l_pi_start_time :=NULL;
l_pi_cpu_start_time :=NULL;
l_pi_start_time :=dbms_utility.get_time;
l_pi_cpu_start_time :=dbms_utility.get_cpu_time;
FOR i IN
(
SELECT object_name
FROM dba_objects )
LOOP
NULL;
END LOOP i;
FOR j IN
(
SELECT table_name,
column_name
FROM dba_tab_cols )
LOOP
l_pi_var2 :=0;
l_pi_var2 :=1;
END LOOP j;
l_pi_var2 :=Func_test(10);
IF 1 =1 THEN
l_pi_var2 :=100;
ELSE
l_pi_var2:=1000;
END IF;
NULL;
l_pi_end_time :=dbms_utility.get_time;
l_pi_cpu_end_time :=dbms_utility.get_cpu_time;
dbms_output.put_line( 'Program run time: '
||(l_pi_end_time l_pi_start_time) /100
||' Seconds');
dbms_output.put_line( 'CPU run time: '
||(l_pi_cpu_end_time l_pi_cpu_start_time)/100
||' Seconds');
END;
/
After the procedure is created, its compilation mode can be verified by querying the USER_PLSQL_OBJECT_SETTINGS data dictionary table as shown below,
SELECT name,
TYPE,
plsql_optimize_level
FROM user_plsql_object_settings
WHERE name = 'PROC_TEST';
TYPE,
plsql_optimize_level
FROM user_plsql_object_settings
WHERE name = 'PROC_TEST';
NAME TYPE PLSQL_OPTIMIZE_LEVEL
---------------- ------------------ ------------------------------------
PROC_TEST PROCEDURE 2
Note: The default optimization level for the procedure will be the database’s current optimization level.
The PLSQL_OPTIMIZE_LEVEL=2 setting performs the below implicit actions on the package.
- Redundant code removal
- The null assignment to the variables, L_PI_START_TIME and L_PI_CPU_START_TIME in the line numbers 16 and 17 respectively, are already null during their creation in the declaration section, thus making these two statements redundant.
- The statements L_PI_VAR2=0 and L_PI_VAR2=1 are redundant as it assigns a literal integer for every iteration inside the loop. Also, the assignment of the first statement is overridden by the second statement. The PL/SQL compiler finds these properties, takes the first statement (L_PI_VAR2=0) out of the loop structure and skips the second statement (L_PI_VAR2=1) during the program execution.
- Unreachable code removal
- The statement l_pi_var2:=1000; is unreachable in the code and this will be avoided.
- Implicit conversion of Cursor FOR LOOP into BULK ARRAY FETCH.
- This setting along with NCOMP mode (PLSQL_CODE_TYPE=NATIVE) converts all the FOR loops into ARRAY fetches with a limit of 100. let us compare the result of the trace file output for the above package with PLSQL_OPTIMIZE_LEVEL>=2 and PLSQL_OPTIMIZE_LEVEL<=1 settings respectively.
The above trace file output is extracted when the procedure code was executed with the PLSQL_OPTIMIZE_LEVEL parameter value set to <=1. The total number of rows processed is 2,24,208 and the total fetch operations performed is 2,24,214. This indicates that approximately 1 row is fetched for each fetch operation, resulting in CPU overhead and context switching.
The above trace file output is extracted when the procedure code was executed with the PLSQL_OPTIMIZE_LEVEL parameter value set to >=2 in the NCOMP mode (PLSQL_CODE_TYPE=NATIVE). The total number of rows processed is 2,24,208 and the total fetches performed is 2,247. This shows that the recursive statements are implicitly converted into ARRAY fetches with a limit of 100.
This approach automatically reduces the context switching and increases the performance of the package.
Thank you,
Boobal Ganesan
Author of Advanced PL/SQL Programming - The Definitive Reference
Wednesday, October 11, 2017
Neat trick to encrypt your PL/SQL subprograms and still be able to execute it!
We have encrypted our data while storing it in the database, but have we ever tried to encrypt our PL/SQL subprograms while creating them? There are scenarios where we must hide our code from our client for few reasons like stopping anyone from reading and then recreating our code, and much more.
There is an Oracle provided utility to achieve this task, that is the Oracle's WRAP utility. The WRAP utility doesn't encrypt our code but just obfuscates it. Here is an example,
Consider that we have planned to make the procedure unreadable to anybody who tries to understand it. For this scenario we can use the WRAP utility in Oracle to get this done as below,
The above SELECT query returns the obfuscated form of this PROC_WRAP procedure as below,
This obfuscated procedure can be still executed just like a normal procedure.
But wait! Can we Unwrap this obfuscated script to its readable format? The answer is YES!!
There are lots of online utilities like codecrete.net, which does the job. So, this is not the most efficient way to hide our code.
While thinking of a better approach, I tumbled upon the DBMS_CRYPTO package.
Now, what is the DBMS_CRYPTO package and what is it used for?
The DBMS_CRYPTO package encrypts and decrypts messages and was introduced to replace the DBMS_OBFUSCATION_TOOLKIT package by providing support for a range of new secure algorithms with easy usage. The algorithms 3DES_2KEY and MD4 are only provided for backward compatibility and the algorithms 3DES, AES, MD-5, SHA-1, and SHA-2 provide more security compared to them.
The DBMS_CRYPTO package consists of ENCRYPT and DECRYPT subprograms for encrypting and decrypting the string messages respectively.
The prototype of the ENCRYPT and the DECRYPT functions in this package are shown below,
The above procedure just decrypts our encrypted message and then executes it using the EXECUTE IMMEDIATE clause, considering it as a dynamic code. Not everyone can decrypt and run the procedure successfully as the KEY which is used for encryption is required for the decryption also and this KEY is to be passed as input to the procedure.
If the KEY is wrong, the procedure exits with an error as below,
Error report -
ORA-28234: key length too short
ORA-06512: at "SYS.DBMS_CRYPTO_FFI", line 67
ORA-06512: at "SYS.DBMS_CRYPTO", line 44
ORA-06512: at "SYS.PROC_TEST", line 7
ORA-06512: at line 1
28234. 00000 - "key length too short"
When we execute the procedure with the right KEY,
Result:
The input date is 10-OCT-17
There is an Oracle provided utility to achieve this task, that is the Oracle's WRAP utility. The WRAP utility doesn't encrypt our code but just obfuscates it. Here is an example,
CREATE OR replace PROCEDURE Proc_wrap(ip_date DATE)
IS
BEGIN
dbms_output.Put_line('The input date is '
||ip_date);
END;
/
IS
BEGIN
dbms_output.Put_line('The input date is '
||ip_date);
END;
/
Consider that we have planned to make the procedure unreadable to anybody who tries to understand it. For this scenario we can use the WRAP utility in Oracle to get this done as below,
SELECT dbms_ddl.wrap(q'[CREATE OR REPLACE PROCEDURE proc_encrypt( ip_date DATE) IS BEGIN dbms_output.put_line('the input DATE IS '||ip_date); END; /]')
FROM dual;
FROM dual;
The above SELECT query returns the obfuscated form of this PROC_WRAP procedure as below,
CREATE OR REPLACE PROCEDURE proc_encrypt wrapped
a000000
b2
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
7
71 ae
g10MIi6Tv0Y9j0IvM3mHKN7UxXIwg5nnm7+fMr2ywFwWFpeuSq7XDNHlVhazaabh0XKpMB/G
L+pEL2v6Q4hHQ138uSlbibhSm7JK/iiyveeysx0GMCyuJOqygchfwwJHNKwl0wKJ29PKA1Q1
7eg5J0DoKpwKT20qnCIdpgxFynY=
a000000
b2
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
7
71 ae
g10MIi6Tv0Y9j0IvM3mHKN7UxXIwg5nnm7+fMr2ywFwWFpeuSq7XDNHlVhazaabh0XKpMB/G
L+pEL2v6Q4hHQ138uSlbibhSm7JK/iiyveeysx0GMCyuJOqygchfwwJHNKwl0wKJ29PKA1Q1
7eg5J0DoKpwKT20qnCIdpgxFynY=
This obfuscated procedure can be still executed just like a normal procedure.
But wait! Can we Unwrap this obfuscated script to its readable format? The answer is YES!!
There are lots of online utilities like codecrete.net, which does the job. So, this is not the most efficient way to hide our code.
While thinking of a better approach, I tumbled upon the DBMS_CRYPTO package.
Now, what is the DBMS_CRYPTO package and what is it used for?
The DBMS_CRYPTO package encrypts and decrypts messages and was introduced to replace the DBMS_OBFUSCATION_TOOLKIT package by providing support for a range of new secure algorithms with easy usage. The algorithms 3DES_2KEY and MD4 are only provided for backward compatibility and the algorithms 3DES, AES, MD-5, SHA-1, and SHA-2 provide more security compared to them.
The DBMS_CRYPTO package consists of ENCRYPT and DECRYPT subprograms for encrypting and decrypting the string messages respectively.
The prototype of the ENCRYPT and the DECRYPT functions in this package are shown below,
DBMS_CRYPTO.ENCRYPT( src IN RAW, typ IN PLS_INTEGER, KEY IN RAW, iv IN RAW DEFAULT NULL)
RETURN RAW;
RETURN RAW;
DBMS_CRYPTO.DECRYPT( src IN RAW, typ IN PLS_INTEGER, KEY IN RAW, iv IN RAW DEFAULT NULL)
RETURN RAW;
RETURN RAW;
- SRC parameter accepts RAW and LOB input data for the encryption for the overloaded function and the procedures respectively.
- TYP parameter accepts the stream or block ciphers and the modifiers to be used.
- KEY parameter accepts the encryption key used by the user for the encryption process.
- IV parameter is also called as the initialization vector. This is for the block ciphers. The default is Null.
Now, consider the above same PROC_WRAP procedure for the encryption process. As a initial step, we must encrypt it using the below block. Note that the procedure is forward declared inside an anonymous block and this block is encrypted as shown below,
DECLARE
l_vc_input_string clob:= q'[DECLARE PROCEDURE proc_encrypt( ip_date DATE) IS BEGIN dbms_output.put_line('The input date is '||ip_date); END; BEGIN proc_encrypt(sysdate); END;]';
l_rw_encryption_key raw(32);
l_rw_encrypted_string raw(32767);
l_pi_encryption_type pls_integer:=DBMS_CRYPTO.ENCRYPT_AES256+ DBMS_CRYPTO.CHAIN_CBC+ DBMS_CRYPTO.PAD_PKCS5;
BEGIN
l_rw_encryption_key :=dbms_crypto.randombytes(32);
l_rw_encrypted_string := DBMS_CRYPTO.ENCRYPT(UTL_I18N.STRING_TO_RAW (l_vc_input_string, 'AL32UTF8'), l_pi_encryption_type, l_rw_encryption_key);
dbms_output.put_line('Input string before encryption=> '||l_vc_input_string);
dbms_output.put_line('Encryption Key=> '||l_rw_encryption_key);
dbms_output.put_line('Encrypted Script=> '||l_rw_encrypted_string);
END;
/
l_vc_input_string clob:= q'[DECLARE PROCEDURE proc_encrypt( ip_date DATE) IS BEGIN dbms_output.put_line('The input date is '||ip_date); END; BEGIN proc_encrypt(sysdate); END;]';
l_rw_encryption_key raw(32);
l_rw_encrypted_string raw(32767);
l_pi_encryption_type pls_integer:=DBMS_CRYPTO.ENCRYPT_AES256+ DBMS_CRYPTO.CHAIN_CBC+ DBMS_CRYPTO.PAD_PKCS5;
BEGIN
l_rw_encryption_key :=dbms_crypto.randombytes(32);
l_rw_encrypted_string := DBMS_CRYPTO.ENCRYPT(UTL_I18N.STRING_TO_RAW (l_vc_input_string, 'AL32UTF8'), l_pi_encryption_type, l_rw_encryption_key);
dbms_output.put_line('Input string before encryption=> '||l_vc_input_string);
dbms_output.put_line('Encryption Key=> '||l_rw_encryption_key);
dbms_output.put_line('Encrypted Script=> '||l_rw_encrypted_string);
END;
/
Input string before encryption=> DECLARE
PROCEDURE proc_encrypt(
ip_date DATE)
IS
BEGIN
dbms_output.put_line('The input date is '||ip_date);
END;
BEGIN
proc_encrypt(sysdate);
END;
Encryption Key=> B3012F6470338E0DE91E47B219E175593919CFC41FC5A50E7FD1D768034CD813
Encrypted Script=> A5B2FDEA81B528B5546A11A51EB5368DC750AA8B4E671E62CD977AD4203923E451131934E2BBE11981D75B9BC8F3AF65160F22A6018839C6E48E8FB70C273515A76D67ACD3081FD23BE0D671A93B19CC61DE0DC37E41B7607EABB41DF8397F0D540257B3196071468CEBE0B0F5353F57D8F8B1273F1DC8643F3347B159E5E9F6C1C6A769426F5FE8E3F3BEF8AE5241CF5E67FC33735F30777EB1C0DD231A4909
Here, we have generated the KEY randomly of 32 bytes using the DBMS_CRYPTO.RANDOMBYTES function.
After getting the encrypted form of the procedure, we must create our "client facing procedure" as like below,
CREATE OR REPLACE PROCEDURE proc_test(
ip_rw_encryption_key raw)
IS
l_vc_encrypted_code VARCHAR2(32767):='750C744D1CCC6380303EACCD0A9ED8805980C9F3FFDA6EC67688396E1DDDFC87D2B8055E9A9256EF1DD4FAED42AC13569C25FFBE260107AF060651B7836E75E2CC20C19BE8E5AFE3F46FCA37BE0A62EAE0B3B15241D60B11A1E40C3F80A9A4DA6A575614B1BB4AF98A222919D1D654996D4567F475D99FD53021433A3940A38AE7F14B7C0EA4C374956D1BBDDA5C6145C1E0B000FFCF1622F3250160EC721C65';
l_pi_encryption_type pls_integer :=DBMS_CRYPTO.ENCRYPT_AES256+ DBMS_CRYPTO.CHAIN_CBC+ DBMS_CRYPTO.PAD_PKCS5;
BEGIN
EXECUTE immediate UTL_I18N.RAW_TO_CHAR(DBMS_CRYPTO.DECRYPT(l_vc_encrypted_code, l_pi_encryption_type, ip_rw_encryption_key), 'AL32UTF8');
END;
ip_rw_encryption_key raw)
IS
l_vc_encrypted_code VARCHAR2(32767):='750C744D1CCC6380303EACCD0A9ED8805980C9F3FFDA6EC67688396E1DDDFC87D2B8055E9A9256EF1DD4FAED42AC13569C25FFBE260107AF060651B7836E75E2CC20C19BE8E5AFE3F46FCA37BE0A62EAE0B3B15241D60B11A1E40C3F80A9A4DA6A575614B1BB4AF98A222919D1D654996D4567F475D99FD53021433A3940A38AE7F14B7C0EA4C374956D1BBDDA5C6145C1E0B000FFCF1622F3250160EC721C65';
l_pi_encryption_type pls_integer :=DBMS_CRYPTO.ENCRYPT_AES256+ DBMS_CRYPTO.CHAIN_CBC+ DBMS_CRYPTO.PAD_PKCS5;
BEGIN
EXECUTE immediate UTL_I18N.RAW_TO_CHAR(DBMS_CRYPTO.DECRYPT(l_vc_encrypted_code, l_pi_encryption_type, ip_rw_encryption_key), 'AL32UTF8');
END;
/
The above procedure just decrypts our encrypted message and then executes it using the EXECUTE IMMEDIATE clause, considering it as a dynamic code. Not everyone can decrypt and run the procedure successfully as the KEY which is used for encryption is required for the decryption also and this KEY is to be passed as input to the procedure.
If the KEY is wrong, the procedure exits with an error as below,
EXEC proc_test('ABCDEFFBE508071AA1EC5FF4D301A5DF12572742BCA5420C78B2');
Error report -
ORA-28234: key length too short
ORA-06512: at "SYS.DBMS_CRYPTO_FFI", line 67
ORA-06512: at "SYS.DBMS_CRYPTO", line 44
ORA-06512: at "SYS.PROC_TEST", line 7
ORA-06512: at line 1
28234. 00000 - "key length too short"
When we execute the procedure with the right KEY,
EXEC proc_test('6A0B656248D9955AEEFBE508071AA1EC5FF4D301A5DF12572742BCA5420C78B2');
Result:
The input date is 10-OCT-17
Thus, with this trick, we have made sure that our PL/SQL code is unreadable and can be accessed only if we have the right KEY.
Thank you,
Boobal Ganesan
Sunday, September 10, 2017
Duplication check on a BLOB column containing image files
We can store multimedia files like music, image, or video files either inside the database (BLOB type) or outside the database (BFILE type).
BLOB:
The BLOB datatypes are read-write binary large datatypes, that lets us store large binary files like video, audio, and PDF documents in the database. The maximum size of these types can range from 8 TB to 128 TB based on the DB_BLOCK_SIZE initialization parameter, which is set during the database creation in the init.ora file. The formula for calculating the maximum size of the LOB type is (4 GB - 1) * DB_BLOCK_SIZE value. Once this parameter is set, there is no going back for any change except for re-creating the database. The maximum value that can be set for the DB_BLOCK_SIZE parameter is 16 KB for Linux X86 environments and 32 KB for other environments. However, setting this parameter’s value to its maximum may affect the I/O operations.
BFILE: Binary File
BFILE is a read-only datatype that the database uses to access external binary files with a maximum size limit of 4 GB. Even though the BFILE and the BLOB types intends to store binary data, they both work differently from each other. Both the BFILE and BLOB column stores only the locators which point to the actual data that resides outside the ROW, but the BLOB type stores its binary data inside the database whereas the BFILE type stores its data outside the database in the OS layer. The BFILE type is capable of storing external binary files which can be in PDF, GIF, JPEG, MPEG, MPEG2, text, or other formats.
Note: The External LOB type doesn’t support UPDATE or DELETE operations as they are READ-ONLY.
Consider the below example for creating a table which holds a sequence number and a BLOB column to hold image files.
When we want to maintain uniqueness for a non-LOB column, we can either create an unique index, primary key (which by default creates an unique index), or an unique key (which by default creates an unique index).
Can we also create a similar unique index, primary key or an unique key for a LOB column?
The answer is NO!
Oracle does not allow us to create any of those for a LOB column. Let's check it out using the below examples.
Error report -
SQL Error: ORA-02329: column of datatype LOB cannot be unique or a primary key
02329. 00000 - "column of datatype %s cannot be unique or a primary key"
*Cause: An attempt was made to place a UNIQUE or a PRIMARY KEY constraint
on a column of datatype VARRAY, nested table, object, LOB, FILE
or REF.
So, how can we achieve the uniqueness for a LOB column? There is a neat trick to do that.
We can create another column to our table which can be used for holding the hash value of the image and check uniqueness for that column. The hash value is actually a compact numeric version of the image file which will be same for same input image. By checking uniqueness on this column, we can make sure that the image is not duplicated.
Let's try this using the below examples.
Firstly, we are adding a new column to our MYTABLE. This new column is a virtual column which will return the hash value of the IMAGE_FILE column.
The second parameter to the DBMS_CRYPTO.HASH function accepts any of the below hash functions.
After the column is added, we must create a unique index on this virtual column as below,
We must now create a directory object for the images' location as below,
Now, we are all set to insert the image file into our table using the below script.
dbms_output.Put_line('Total size of the image file is '
||dbms_lob.Getlength(l_bl_dest)
||' Bytes');
dbms_lob.CLOSE(l_bf_src);
dbms_lob.CLOSE(l_bl_dest);
END;
/
We can now check our image file using any IDE. I am currently using the SQL DEVELOPER 4.1.3 version for checking the inserted image file.
Now, when we rerun the same PL/SQL block again to insert the same image, we get the below unique index violation.
Error report -
ORA-00001: unique constraint (SYS.IDX_HASH_VALUE) violated
ORA-06512: at line 11
00001. 00000 - "unique constraint (%s.%s) violated"
*Cause: An UPDATE or INSERT statement attempted to insert a duplicate key.
For Trusted Oracle configured in DBMS MAC mode, you may see
this message if a duplicate entry exists at a different level.
*Action: Either remove the unique restriction or do not insert the key.
We have found a workaround to check uniqueness on the LOB columns in this blog.
Thank you,
Boobal Ganesan
BLOB:
The BLOB datatypes are read-write binary large datatypes, that lets us store large binary files like video, audio, and PDF documents in the database. The maximum size of these types can range from 8 TB to 128 TB based on the DB_BLOCK_SIZE initialization parameter, which is set during the database creation in the init.ora file. The formula for calculating the maximum size of the LOB type is (4 GB - 1) * DB_BLOCK_SIZE value. Once this parameter is set, there is no going back for any change except for re-creating the database. The maximum value that can be set for the DB_BLOCK_SIZE parameter is 16 KB for Linux X86 environments and 32 KB for other environments. However, setting this parameter’s value to its maximum may affect the I/O operations.
BFILE: Binary File
BFILE is a read-only datatype that the database uses to access external binary files with a maximum size limit of 4 GB. Even though the BFILE and the BLOB types intends to store binary data, they both work differently from each other. Both the BFILE and BLOB column stores only the locators which point to the actual data that resides outside the ROW, but the BLOB type stores its binary data inside the database whereas the BFILE type stores its data outside the database in the OS layer. The BFILE type is capable of storing external binary files which can be in PDF, GIF, JPEG, MPEG, MPEG2, text, or other formats.
Note: The External LOB type doesn’t support UPDATE or DELETE operations as they are READ-ONLY.
Consider the below example for creating a table which holds a sequence number and a BLOB column to hold image files.
CREATE TABLE mytable
(
seq NUMBER,
image_file BLOB
);
(
seq NUMBER,
image_file BLOB
);
/
When we want to maintain uniqueness for a non-LOB column, we can either create an unique index, primary key (which by default creates an unique index), or an unique key (which by default creates an unique index).
Can we also create a similar unique index, primary key or an unique key for a LOB column?
The answer is NO!
Oracle does not allow us to create any of those for a LOB column. Let's check it out using the below examples.
- Creating an unique index on a LOB column
CREATE UNIQUE INDEX idx_image_file ON mytable(image_file);
Error report -SQL Error: ORA-02327: cannot create index on expression with datatype LOB
02327. 00000 - "cannot create index on expression with datatype %s"
*Cause: An attempt was made to create an index on a non-indexable
expression.
02327. 00000 - "cannot create index on expression with datatype %s"
*Cause: An attempt was made to create an index on a non-indexable
expression.
- Creating a unique constraint on a LOB column
ALTER TABLE mytable ADD CONSTRAINT uk_image_file UNIQUE (image_file);
Error report -
SQL Error: ORA-02329: column of datatype LOB cannot be unique or a primary key
02329. 00000 - "column of datatype %s cannot be unique or a primary key"
*Cause: An attempt was made to place a UNIQUE or a PRIMARY KEY constraint
on a column of datatype VARRAY, nested table, object, LOB, FILE
or REF.
We can create another column to our table which can be used for holding the hash value of the image and check uniqueness for that column. The hash value is actually a compact numeric version of the image file which will be same for same input image. By checking uniqueness on this column, we can make sure that the image is not duplicated.
Let's try this using the below examples.
Firstly, we are adding a new column to our MYTABLE. This new column is a virtual column which will return the hash value of the IMAGE_FILE column.
ALTER TABLE mytable ADD (hash_value generated always AS
(dbms_crypto.hash(image_file, 2)) virtual);
The second parameter to the DBMS_CRYPTO.HASH function accepts any of the below hash functions.
- HASH_MD4 = 1
- HASH_MD5 = 2
- HASH_SH1 = 3
- HASH_SH256 = 4
- HASH_SH384 = 5
- HASH_SH512 = 6
After the column is added, we must create a unique index on this virtual column as below,
CREATE UNIQUE INDEX idx_hash_value ON mytable(hash_value);
We must now create a directory object for the images' location as below,
CREATE OR replace directory dir_pictures AS 'F:\Pictures';
Now, we are all set to insert the image file into our table using the below script.
DECLARE
l_bf_src BFILE := Bfilename('DIR_PICTURES', 'Batman.JPG');
l_bl_dest BLOB := Empty_blob();
l_i_src_offset INTEGER := 1;
l_i_dest_offset INTEGER := 1;
BEGIN
dbms_lob.Createtemporary(l_bl_dest, TRUE, dbms_lob.SESSION);
dbms_lob.OPEN(l_bf_src, dbms_lob.file_readonly);
dbms_lob.OPEN(l_bl_dest, dbms_lob.lob_readwrite);
dbms_lob.Loadblobfromfile(l_bl_dest, l_bf_src, dbms_lob.lobmaxsize,
l_i_dest_offset, l_i_src_offset);
INSERT INTO mytable
(seq,
image_file)
VALUES (1,
l_bl_dest );
l_bf_src BFILE := Bfilename('DIR_PICTURES', 'Batman.JPG');
l_bl_dest BLOB := Empty_blob();
l_i_src_offset INTEGER := 1;
l_i_dest_offset INTEGER := 1;
BEGIN
dbms_lob.Createtemporary(l_bl_dest, TRUE, dbms_lob.SESSION);
dbms_lob.OPEN(l_bf_src, dbms_lob.file_readonly);
dbms_lob.OPEN(l_bl_dest, dbms_lob.lob_readwrite);
dbms_lob.Loadblobfromfile(l_bl_dest, l_bf_src, dbms_lob.lobmaxsize,
l_i_dest_offset, l_i_src_offset);
INSERT INTO mytable
(seq,
image_file)
VALUES (1,
l_bl_dest );
commit;
dbms_output.Put_line('Total size of the image file is '
||dbms_lob.Getlength(l_bl_dest)
||' Bytes');
dbms_lob.CLOSE(l_bf_src);
dbms_lob.CLOSE(l_bl_dest);
END;
/
We can now check our image file using any IDE. I am currently using the SQL DEVELOPER 4.1.3 version for checking the inserted image file.
Now, when we rerun the same PL/SQL block again to insert the same image, we get the below unique index violation.
Error report -
ORA-00001: unique constraint (SYS.IDX_HASH_VALUE) violated
ORA-06512: at line 11
00001. 00000 - "unique constraint (%s.%s) violated"
*Cause: An UPDATE or INSERT statement attempted to insert a duplicate key.
For Trusted Oracle configured in DBMS MAC mode, you may see
this message if a duplicate entry exists at a different level.
*Action: Either remove the unique restriction or do not insert the key.
Thank you,
Boobal Ganesan
Subscribe to:
Posts (Atom)
-
The DESCRIBE_COLUMNS API describes the columns of the SELECT statement which is opened and parsed through a cursor. This procedure consist...
-
The multiset operators are a bunch in number, which combines two or more collection types of the same type and returns a collection resu...
-
We have encrypted our data while storing it in the database, but have we ever tried to encrypt our PL/SQL subprograms while creating them? T...