Showing posts with label SQL*Loader. Show all posts
Showing posts with label SQL*Loader. Show all posts

Wednesday, July 28, 2010

In case you don't know, SQL*loader exit codes have changed

Always check out the original article at http://www.oraclequirks.com for latest comments, fixes and updates.

A quick posting to report that, for some (good?) reason, Oracle decided to change the values behind the so-called exit codes that SQL*Loader returns upon exit on the Windows platform.

Interestingly enough, this change is actually "documented" because comparing the two pages in the "Utilities" book for Oracle 10gR2 and Oracle 11gR2, they differ slightly as shown below:
Win Oracle 10gR2  Win Oracle 11gR2  Unix
EX_SUCC 0 EX_SUCC 0 EX_SUCC 0
EX_WARN 2 EX_FAIL 1 EX_FAIL 1
EX_FAIL 3 EX_WARN 2 EX_WARN 2
EX_FTL 4 EX_FTL 4 EX_FTL 3
On Unix the exit codes have remained the same across the releases and the only advantage i see in this operation is in the fact that now they are more consistent between the platforms but was this change really worth the effort?

I noticed this because some old scripts, quite nicely, stopped working under certain, non frequent conditions and something remembered me of a similar problem that occurred years ago with SQLPlus.

Practically speaking, a batch program that was stopping for codes greater than 2, now might continue running.
In SQL*Loader terms this means that a failure on an INSERT operation (that requires the target table to be empty) may be not recognized any longer because your script expects a value equal or greater than 2, but now a failure is reported as 1.

So, if you are asking yourself why an old SQL*Loader batch program suddenly doesn't work anymore as before on Windows, then you might have found the answer to the problem.

By the way, I checked the upgrade guide and release notes but i failed to find any entries mentioning changes in SQL*Loader's exit codes.
Or may be i didn't look hard enough?

Wednesday, March 11, 2009

ORA-01841: (full) year must be between -4713 and +9999, and not be 0

Always check out the original article at http://www.oraclequirks.com for latest comments, fixes and updates.

There are oracle errors that can be easily explained.
Sometimes one of these trivial error messages pops up in an unexpected situation and it's really tough to get to the bottom of it.
And that is exactly what happened yesterday, but i am still doing some tests today.

Let's see first the simplest form of ORA-01841:
select to_date('0000-03-10','YYYY-MM-DD') as d
from dual;

ORA-01841: (full) year must be between -4713 and +9999, and not be 0
Gregorian calendar starts with year 1 and there is very little to say about it when you get this error in a query: just fix the input value.

When the input value comes from an external source and it contains all zeroes, something that I've seen happening quite often when you receive a file from a third party meaning a NULL date value, then you'll have to deal with it using one of those special init_spec clauses like NULLIF or DEFAULTIF (both in SQL*Loader and external tables using the SQL*Loader driver).

However there are less common situations like that i came across today, where i found this message in the log of the SQL*Loader driver invoked by an external table definition in a program that was running since months.
 LOG file opened at 03/10/09 11:13:10

Field Definitions for table IMP_BAD_BOXES
Record format DELIMITED BY NEWLINE
Data in file has same endianness as the platform
Reject rows with all null fields

Fields in Data Source:

CREATED CHAR (14)
Date datatype DATE, date mask YYYYMMDDHH24MISS
Record position (1, 14)
Trim whitespace same as SQL Loader
BOX_ID CHAR (8)
Record position (15, 22)
Trim whitespace same as SQL Loader
DEPT CHAR (2)
Record position (23, 24)
Trim whitespace same as SQL Loader
error processing column CREATED in row 5 for datafile BOXES.dat
ORA-01841: (full) year must be between -4713 and +9999, and not be 0

Date Cache Statistics for table IMP_BAD_BOXES
Max Size: 1000
Entries : 4
Hits : 0
Misses : 0

A batch script invoked by a Windows service running as System was failing to load a file whereas the same script run manually as Administrator was running just fine and the only difference between the two log files was in the aforementioned error on the last imported line of the file.

The only known difference with the past is in that the input file is now generated by an export procedure whereas it was previously exported manually from an excel spreadsheet.
Practically speaking the old files did not contain the trailing newline at the bottom.

Given the following external table definition:
CREATE TABLE "IMP_BOXES"
( "BOX_ID" NUMBER(8,0),
"DEPT" VARCHAR2(2 BYTE),
"CREATED" DATE
)
ORGANIZATION EXTERNAL
( TYPE ORACLE_LOADER
DEFAULT DIRECTORY "LOG_DIR"
ACCESS PARAMETERS
( RECORDS DELIMITED BY NEWLINE
LOGFILE 'BOXES.log'
FIELDS
(
CREATED POSITION(1:14) CHAR DATE_FORMAT DATE MASK "YYYYMMDDHH24MISS",
BOX_ID POSITION(15:22) CHAR,
DEPT POSITION(23:24) CHAR
)
)
LOCATION
( "IMPORT_DIR":'BOXES.dat'
)
);
a source file of 4 records containing a newline (0D0A) in the last record position would cause ORA-01841 to be raised on the 5th record when launched automatically from the Windows service. The same source file did not cause any error when executed as a user with administrator rights.
This problem was reproduced on two different 10GR2 instances running on top of Windows 2003 SE, so, at least, the good news is this is a consistent pattern.
Oracle NLS session parameters were looking the same (i spooled NLS_SESSION_PARAMETERS before invoking the script), so i'd rule them out as a possible cause, i wonder if the system user is reading a different parameter set from the registry.
Note that adding any combination of MISSING FIELD VALUES ARE NULL and/or REJECT ROWS WITH ALL NULL FIELDS did not affect the result.

So, in the end, i don't really know yet what's going on.

I failed to find the exact reason (or oracle bug?) but at least i could find a workaround, consisting in changing the external table definition as follows (inserted clause in green color):

CREATE TABLE "IMP_BOXES"
( "BOX_ID" NUMBER(8,0),
"DEPT" VARCHAR2(2 BYTE),
"CREATED" DATE
)
ORGANIZATION EXTERNAL
( TYPE ORACLE_LOADER
DEFAULT DIRECTORY "LOG_DIR"
ACCESS PARAMETERS
( RECORDS DELIMITED BY NEWLINE
LOGFILE 'BOXES.log'
LOAD WHEN((1:1) != NULLS)
FIELDS
(
CREATED POSITION(1:14) CHAR DATE_FORMAT DATE MASK "YYYYMMDDHH24MISS",
BOX_ID POSITION(15:22) CHAR,
DEPT POSITION(23:24) CHAR
)
)
LOCATION
( "IMPORT_DIR":'BOXES.dat'
)
);
The advantage of checking a character range using the syntax (n:m) is in that i can discard the record before the parser fails to read the last record.
While i was looking for a solution i also incidentally found out that all Oracle Utilities manual through version 11G, where they talk about the so-called condition specifier, are wrong when they say that you can use the keyword NULL in the comparison.
The correct keyword is NULLS.

Another interesting finding is in that the .bad file generated by Oracle contained a weird 1A character preceding the rejected newline character, a character that was nowhere to be found in the source file. This file is only generated when the script is launched from the service.

Curious, isn't it?

See message translations for ORA-01841 and search additional resources.

Tuesday, August 19, 2008

SQL*Loader, ORA-01722 and the importance of being blank

Always check out the original article at http://www.oraclequirks.com for latest comments, fixes and updates.

Although I wrote hundreds of SQL*Loader control files over the years, yesterday i came across one of those trivial situations that takes hours to be sorted out because of some tiny detail.

The task: to load a text file containing fixed-length fields and numeric fields are formatted using a postfix notation, minus sign for negative numbers or a blank for positive numbers.

Piece of cake i thought, i can format the field using an inline SQL function like "TO_NUMBER(:COL2, '999999MI')" as in:
load data
infile *
truncate
into table test_table
(
COL1 POSITION(01:19) CHAR,
COL2 POSITION(20:26) CHAR "TO_NUMBER(:COL2,'999999MI')",
COL3 POSITION(27:27) CHAR
)
BEGINDATA
THIS ROW IS OK 123456-Y
THIS ROW IS NOT OK!234567 Y
THIS ROW IS NOT OK!012345 Y
And here is what i found in the SQL*Loader log file:

SQL*Loader: Release 10.2.0.1.0 - Production on Mon Aug 18 23:21:39 2008

Copyright (c) 1982, 2005, Oracle. All rights reserved.

Control File: test.ctl
Data File: test.ctl
Bad File: test.bad
Discard File: none specified

(Allow all discards)

Number to load: ALL
Number to skip: 0
Errors allowed: 50
Bind array: 64 rows, maximum of 256000 bytes
Continuation: none specified
Path used: Conventional

Table TEST_TABLE, loaded from every logical record.
Insert option in effect for this table: TRUNCATE

Column Name Position Len Term Encl Datatype
------------------------------ ---------- ----- ---- ---- ---------------------
COL1 1:19 19 CHARACTER
COL2 20:26 7 CHARACTER
SQL string for column : "TO_NUMBER(:COL2,'999999MI')"
COL3 27:27 1 CHARACTER

Record 2: Rejected - Error on table TEST_TABLE, column COL2.
ORA-01722: invalid number

Record 3: Rejected - Error on table TEST_TABLE, column COL2.
ORA-01722: invalid number


Table TEST_TABLE:
1 Row successfully loaded.
2 Rows not loaded due to data errors.
0 Rows not loaded because all WHEN clauses were failed.
0 Rows not loaded because all fields were null.


Space allocated for bind array: 2304 bytes(64 rows)
Read buffer bytes: 1048576

Total logical records skipped: 0
Total logical records read: 3
Total logical records rejected: 2
Total logical records discarded: 0

Run began on Mon Aug 18 23:21:39 2008
Run ended on Mon Aug 18 23:21:40 2008

Elapsed time was: 00:00:00.54
CPU time was: 00:00:00.12
The corresponding rows were also recorded in the badfile.

I must admit that this problem has tortured me for a couple of hours, before i finally got the "inspiration".

Eventually i realized that i had a problem with the blanks when i managed to re-create the error condition using a simple query. In particular i noticed that one can perform the following SELECT without problems:
select to_number('000000 ','999999MI') t from dual;

T
-
0
but one gets ORA-01722 when the trailing blank is trimmed:
select to_number('000000','999999MI') t from dual;

ORA-01722: invalid number
The main problem is in the fact that SQL*Loader by default ignores blanks or better said it strips them out altogether. Blank trimming is somewhat documented in the SQL*Loader Reference manual, but it's easy to overlook such fundamental information.
At this point i had a strong suspect on the fact that SQL*Loader was removing the trailing blank, regardless of the fixed-length specification, so i decided to enable the PRESERVE BLANKS option at the file level:
load data
infile *
truncate
preserve blanks
into table test_table
(
COL1 POSITION(01:19) CHAR,
COL2 POSITION(20:26) CHAR "TO_NUMBER(:COL2,'999999MI')",
COL3 POSITION(27:27) CHAR
)
BEGINDATA
THIS ROW IS OK 123456-Y
THIS ROW IS NOW OK 234567+Y
THIS ROW IS NOW OK 012345 Y
And this time it worked without a hitch.

Well, almost, first i had to sort out a side-effect of PRESERVE BLANKS, because in my real-life scenario i had also a trailing numeric column that started complaining about the presence of blanks.
So i modified the field definition to include NULLIF field = BLANKS and after doing that my control file was finally ok.

Edited on Tuesday 19:
note also that the MI numerical format allows to specify either a blank or a plus (+) sign indifferently.
I inserted a plus sign in line 2 of the last datafile version to clarify this point.

yes you can!

Two great ways to help us out with a minimal effort. Click on the Google Plus +1 button above or...
We appreciate your support!

latest articles