Scheduling Jobs with Oracle 9i DBMS_JOB Package

Creating a new job that runs every day at 4:00am:

	l_job number; 
		l_job, -- OUT; the job ID number that will be generated
		'schema_name.procedure_name;', -- IN; the name of the job you wish to run, aka. "what"
		trunc(sysdate)+1+4/24,  -- IN; the first time the job will be run
		'trunc(sysdate)+1+4/24' -- IN; the interval the job will be repeated

Regarding the interval, here are some examples:

-- Every 15 minutes starting from the minute/second of the previous execution

-- Every hour, same minute/second as the previous execution

-- Every hour, at the 15-minute mark
'trunc(sysdate, 'hh')+1/24+15/24/60'

-- Every hour, limited to between 9:00am and 5:00pm
'case when to_char(sysdate, ''hh24mi'') between ''0900'' and ''1700'' then sysdate+1/24 else null end'

-- Every 3 days, same hour/minute/second as the previous execution

-- Every day at 5:00am

-- Every Monday at 5:00am
'next_day(trunc(sysdate), ''monday'')+5/24'

To see a list of existing jobs:

select * from dba_jobs;

Altering all properties of an existing job:

		123, -- IN; job ID number
		'schema_name.procedure_name;', -- IN; the name of the job, aka. "what"
		trunc(sysdate)+1+4/24,  -- IN; the first time the job will be run after this change
		'trunc(sysdate)+1+4/24' -- IN; the interval the job will be repeated

Altering just the “what”:

		123, -- IN; job ID number
		'schema_name.procedure_name;' -- IN; the name of the job, aka. "what"

These procedures allows you to make changes in a manner very similar to dbms_job.what illustrated above:


Force a job to run:

	-- ... where the "123" is the job's ID number

Removing an existing job:

	-- ... where the "123" is the job's ID number

Ubuntu Boot Partition Full

The other day when I attempted to run some regular updates for my Linux box (running Ubuntu 14.04 LTS), I encountered the message that the update could no proceed because the boot partition was full. Here are the steps I took to clear unneeded files from the boot partition.

1. First, I found out that I am running kernel 3.19.0-65 with this command below.

me@computer:~$  uname -r


2. Next, list what kernel images are present in my root partition.

me@computer:~$ dpkg --list | grep linux-image

ii  linux-image-3.19.0-61-generic
3.19.0-61.69~14.04.1                                amd64        Linux
kernel image for version 3.19.0 on 64 bit x86 SMP
ii  linux-image-3.19.0-65-generic
3.19.0-65.73~14.04.1                                amd64        Linux
kernel image for version 3.19.0 on 64 bit x86 SMP
ii  linux-image-extra-3.19.0-61-generic
3.19.0-61.69~14.04.1                                amd64        Linux
kernel extra modules for version 3.19.0 on 64 bit x86 SMP
ii  linux-image-extra-3.19.0-65-generic
3.19.0-65.73~14.04.1                                amd64        Linux
kernel extra modules for version 3.19.0 on 64 bit x86 SMP
ii  linux-image-generic-lts-vivid               
                                       amd64        Generic Linux
kernel image

3. The above was actually a truncated example; the actual list was much longer. In summary, I had many older kernel images that I do not need anymore. In the example shown above, I decided that since I am running 3.19.0-65, I will not need the -61 image anymore. Below is the command I used to clear out -61; I ran similar commands for all the kernel images with even lower versions as well to clear up space.

me@computer:~$ sudo apt-get purge linux-image-3.19.0-61-generic

Reading package lists... Done
Building dependency tree
Reading state information... Done
The following packages will be REMOVED:
0 upgraded, 0 newly installed, 1 to remove and 8 not upgraded.
After this operation, 47.8 MB disk space will be freed.
Do you want to continue? [Y/n] Y

4. Finally, check out the contents of the /boot/ directory. If you see any orphaned files from older kernels, consider removing them to save space.

Bonus: Useful related commands

sudo apt-get autoremove
sudo apt-get clean
sudo apt-get update
sudo apt-get dist-upgrade

“autoremove” gets rid of packages that were automatically installed previously, but are no longer needed.

“cleans” empties /var/cache/apt/archives/ and /var/cache/apt/archives/partial/.

“update” updates apt-get’s list of available software packages.

“dist-upgrade” is best explained via its man page entry:

dist-upgrade in addition to performing the function of upgrade, also intelligently handles changing dependencies with new versions of packages; apt-get has a “smart” conflict resolution system, and it will attempt to upgrade the most important packages at the expense of less important ones if necessary. The dist-upgrade command may therefore remove some packages.

Backing up Android Phone to Linux

My environment: Smart phone running Android version 6.0.1, and computer running Ubuntu 16.04 LTS

I have a few key folders on my phone that I would like to back up regularly. For simplicity sake, let us say that I am just dealing with the “Camera” folder which holds all my latest photographs. The following commands makes use of gvfs-commands (Gnome virtual file system) to copy/move files, assuming the phone has been connected via USB to the computer, and the phone has been unlocked.

The bash script I currently run looks like this:


for D in /run/user/1000/gvfs/*
	gvfs-copy ${D}/Phone/DCIM/Camera/*.* /picfolder/ 
	gvfs-move ${D}/Phone/DCIM/Camera/*.* /tmpfolder/
	gvfs-move /tmpfolder/*.* ${D}/Phone/Pictures/1-ToSort/ 

	echo "${D} done" 

As you may have noticed, I took the safe approach of copying the photos to my computer, then moving the same photos again to a temporary directory on my computer, and finally moving the temporary files back onto the phone in an archive folder. The final steps effectively represents a move from the Camera folder on the phone to the archive folder on the phone; this is because gvfs-move does not support the move of files within the same device at this time.

Combining Oracle trigger and DBMS_UTILITY.FORMAT_CALL_STACK to track transactions

I recently encountered a situation where a small number of records in a large Oracle table contain wrong values, and naturally I need to find out exactly which program is causing this problem. I decided to use Oracle triggers to do this job, making use of the built-in DBMS_UTILITY.FORMAT_CALL_STACK function as the main ingredient.

create or replace trigger trg_stack_trace_logger
before insert or update on inventory_table
for each row
	if (:old.expiration_date <> :new.expiration_date) then
		insert into stack_trace_log
		'User=' || user || '; ' ||
		'Date=' || to_char(sysdate,'mm/dd/yyyy hh24:mi:ss') || '; ' ||
		'Old Value=' || :old.expiration_date || '; ' ||
		'New Value=' || :new.expiration_date || '; ' ||
	end if;

As you can see, the output contains both old/new values of the transaction as well as some metadata (ie. the stack trace) of the transaction itself. The output is inserted into a table called “stack_trace_log”, which, for simplicity sake, is just a table consisted of a single varchar2 field; if you will use this type of tracking over a longer period, it is probably best to track username, date, etc. in their own fields for better reporting capabilities.

select * from stack_trace_log;

Date=11/24/2015 08:33:39; 
Old Value=2015-11-15-00.00.00; 
New Value=2030-11-15-08.33.39; 
----- PL/SQL Call Stack -----    
object handle line number object name
0x91626018    1           anonymous block
0x8dcb7b30    3           ERP.TRG_STACK_TRACE_LOGGER
0x9657ec50    354         package body ERP.INVENTORY_API
0x9657ec50    1483        package body ERP.INVENTORY_API
0x8c7d2758    4254        package body ERP.INVENTORY_API
0x969315a0    650         package body ERP.RECEIVING_API
0x969315a0    3524        package body ERP.RECEIVING_API
0x969315a0    2861        package body ERP.RECEIVING_API
0x91411208    342         package body ERP.BARCODE_ARRIVAL_API
0x8bd5cca8    1           anonymous block
0x82871f48    1120        package body SYS.DBMS_SYS_SQL
0x82886f48    323         package body SYS.DBMS_SQL
0x99f8e6c0    138         package body ERP.BARCODE_INTERFACE_API
0x93980f88    1           anonymous block

1 rows selected

Insert Microsoft Word content into Oracle database

The following sample is over-simplified, but it shows how we can iterate through tables (and their columns and rows) to extract text, and in turn inserting them into an Oracle table.

Option Explicit

Public Sub InsertIntoOracle()
    Dim cn As ADODB.Connection
    Dim source, user, password, str As String
    Dim aTable As Table
    Dim tbl, row, col As Long

    source = "database"
    user = "scott"
    password = "tiger"
    tbl = 0

    Set cn = New ADODB.Connection
    cn.Open "Provider = OraOLEDB.Oracle; Data Source=" & source & "; User Id=" & user & "; Password=" & password & ""
    For Each aTable In ActiveDocument.Tables
        tbl = tbl + 1
        For row = 1 To aTable.Rows.Count
            For col = 1 To aTable.Columns.Count
                str = Trim(aTable.Cell(row, col).Range.Text)
                If (Len(str) > 2) Then
                    cn.Execute "insert into document_content values('" & tbl & "-" & row & "-" & col & ": " & str & "')"
                End If

    If cn.Errors.Count = 0 Then
    End If

End Sub

Obfuscate sensitive data in Oracle

If business needs requires you to store sensitive data such as social security numbers, bank routing/account numbers, and so on, you should ensure the data is stored in a safe way. Below are a set of two simple functions to encrypt/obfuscate such data to get your started.

To encrypt a varchar2 string with a specific encryption phrase (or “key”):

create or replace function your_schema.encrypt(clear_varchar_ varchar2, key_ varchar2) return varchar2 
	v_clear_varchar varchar2(2000);
	v_enc_raw		raw(2000);
	v_enc_varchar	varchar2(2000);
	if (mod(length(clear_varchar_), 8) != 0) then
		v_clear_varchar := rpad(clear_varchar_, length(clear_varchar_) + 8 - mod(length(clear_varchar_), 8), chr(0));
		v_clear_varchar := clear_varchar_;
	end if;
	dbms_obfuscation_toolkit.desencrypt(input => utl_raw.cast_to_raw(rpad(v_clear_varchar, 64, ' ')),
		key => utl_raw.cast_to_raw(key_), 
		encrypted_data => v_enc_raw);
		v_enc_varchar := utl_raw.cast_to_varchar2(v_enc_raw);
	return v_enc_varchar;

The following function decrypts; you must use the same key that was used to encrypt it.

create or replace function your_schema.decrypt(enc_varchar_ varchar2, key_ varchar2) return varchar2 
	v_tmp_raw    	 raw(2048);
	v_clear_varchar	varchar2(4000);
	dbms_obfuscation_toolkit.desdecrypt(input => utl_raw.cast_to_raw(enc_varchar_),
		key =>  utl_raw.cast_to_raw(key_), 
		decrypted_data => v_tmp_raw);
	v_clear_varchar := replace(trim(utl_raw.cast_to_varchar2(v_tmp_raw)),chr(0),'');
	return v_clear_varchar;

Here is an example usage: The following SQL statement inserts an obfuscated password into a table that stores user data.

insert into your_schema.user_accounts (username, password)
	your_schema.encrypt('tiger', '_seCret!keY:3')

And below is how you would retrieve and decrypt the password.

select your_schema.decrypt(password, '_seCret!keY:3') from your_schema.user_accounts where username='scott';

Security is a serious matter and it warrants extensive research. This article merely offers the awareness that sensitive data should not be stored in clear text, and hopefully provides a good starting point.

Oracle role hierarchy report

The SQL below can be used to provide a list of roles that inherits the CONNECT role, and with the use of START WITH clause, it will also iterate through all the roles beneath those roles, thus providing a hierarchy report.

select level, drp.granted_role, rpad('-',6*level,'-')||drp.grantee as grantee,
	when u.username is not null and account_status='OPEN' then 'Ua'
	when u.username is not null and account_status<>'OPEN' then 'Ux'
	when r.role is not null then 'R'
end as grantee_type
from dba_role_privs drp, dba_users u, dba_roles r
where drp.grantee=u.username(+) and drp.grantee=r.role(+)
start with drp.granted_role='CONNECT'
connect by prior drp.grantee=drp.granted_role;

Below is a sample of what may be returned.


Running VBScript in 32-bit mode on a 64-bit Windows machine

I have a legacy VBScript program that has been recently been migrated to a new 64-bit Windows server by a system administrator due to a policy set by a higher power. By default, VBScript executed in 64-bit Windows automatically runs as a 64-bit program. Because I have reasons to have this program run in 32-bit mode, I modified my batch file to execute my program in a different way.

This is the BEFORE picture. When this batch file is run in 64-bit Windows, it will execute in 64-bit mode.

time /t
cscript ThisIsMyVbscriptProgram.vbs
time /t

Below is the AFTER picture. Note that I am passing my script into a new command prompt environment in the Windows SYSWOW64 folder; this new environment is strictly in 32-bit mode.

time /t
%WINDIR%SYSWOW64cmd.exe /c cscript ThisIsMyVbscriptProgram.vbs
time /t

As a side note: WOW stands for “Windows on Windows”.

Using DBA_SOURCE to query package source code

Recently I had the need to find out exactly what existing PL/SQL logic was touching a certain field that seems to update by itself, wiping out important data. Below was the quick query I put together, using the DBA_SOURCE view, to hunt down the culprit.

select name as package_name, line, text
from dba_source
where owner='MY_SCHEMA'
and type='PACKAGE BODY'
and (
  upper(text) like '%MY_TABLE_NAME%FIELD_NAME%'
  upper(text) like '%FIELD_NAME%MY_TABLE_NAME%'
order by name, line;

Note that I could have searched for “and upper(text) like ‘%UPDATE%MY_TABLE_NAME%FIELD_NAME%'” rather than having two conditions, but I wanted to err on the side of caution, thus my wish to pull more data out to be safe.

Using PL/SQL to call IFS APIs

IFS is an ERP system built by the Swedish software firm Industrial and Financial Systems AB. It exposes most of its functionality for automation, customization, etc. Below is an example of how we can use a quick script (to be executed by tools such as SQL*Plus) to automate the batch creation of detail lines in a Customer Order. Many of the other exposed APIs offered by IFS can be called in a similar manner.

	info_	varchar2(4000) := '';
	attr_	varchar2(4000) := '';
	objid_	varchar2(2000) := '';
	objversion_	varchar2(2000) := '';

	cursor recs_ is
	select order_no, cust_id, part_no, qty, unit_cost 
	from my_tmp_worklist_tab;
	for rec_ in recs_ loop
		info_ := '';
		objid_ := '';
		objversion_ := '';

		-- Add attributes to the Customer Order detail line
		Client_Sys.Add_To_Attr('ORDER_NO', rec_.order_no, attr_);
		Client_Sys.Add_To_Attr('DELIVER_TO_CUSTOMER_NO', rec_.cust_id, attr_);
		Client_Sys.Add_To_Attr('CATALOG_NO', rec_.part_no, attr_);
		Client_Sys.Add_To_Attr('PART_NO', rec_.part_no, attr_);
		Client_Sys.Add_To_Attr('BUY_QTY_DUE', rec_.qty, attr_);
		Client_Sys.Add_To_Attr('DESIRED_QTY', rec_.qty, attr_);
		Client_Sys.Add_To_Attr('REVISED_QTY_DUE', rec_.qty, attr_);
		Client_Sys.Add_To_Attr('COST', rec_.unit_cost, attr_);
		Client_Sys.Add_To_Attr('PLANNED_DELIVERY_DATE', sysdate+7, attr_);
		-- ... There may be other fields you may wish to populate...

		-- Example of how to update an already-established attribute
		if (rec_.unit_cost = 0) then
			Client_Sys.Set_Item_Value('COST', '0.01', attr_);
		end if;
			Customer_Order_line_API.New__(info_, objid_, objversion_, attr_, 'DO');
			when other then
			info_ := sqlerrm;
			dbms_output.put_line('Cust Order ' || rec_.order_no || ' error: ' || info_);

   end loop;