Skip to Main Content
  • Questions
  • SQL*Loader problem with a quoted, comma separated data file

Breadcrumb

Question and Answer

Tom Kyte

Thanks for the question, Steve.

Asked: September 17, 2004 - 2:13 pm UTC

Last updated: November 14, 2008 - 5:02 pm UTC

Version: 8.1.7

Viewed 50K+ times! This question is

You Asked

Tom,

Hopefully I have provided all the statements you'll need to duplicate this problem below. I am trying to use SQL*Loader to put data into my table. The data is comma separated and each data field has double quotes around it. The double quoting is done because some of the fields have embedded commas. SQL*Loader seems to strip off the quotes first and then separate into fields which means that any embedded commas throw things off by one.

The sample data file (2 records) has the first record with no embedded commas, the second record with an embedded comma in the address field.

Are there any workarounds for this problem?

Thanks,
Steve

========================
supporting data below
========================

CREATE TABLE Import_Test (
IT_ID number(6) NOT NULL,
FNAME varchar2(10) NULL,
LNAME varchar2(10) NULL,
ADDRESS varchar2(30) NULL,
EXTRA_1 varchar2(10) NULL,
EXTRA_2 varchar2(10) NULL,
CONSTRAINT PK_Import_Test PRIMARY KEY (IT_ID)
)
/

CREATE SEQUENCE Seq_Import_Test_IT_ID
START WITH 1
INCREMENT BY 1
ORDER
NOCYCLE
/

CREATE TRIGGER Trg_Import_Test_IT_ID
BEFORE INSERT ON Import_Test
FOR EACH ROW
DECLARE
next_id NUMBER;
BEGIN
SELECT Seq_Import_Test_IT_ID.NEXTVAL
INTO next_id FROM DUAL;

:new.IT_ID := next_id;
END;
/

==============================================================
-- quoted, comma separated data
"FName","LName","Address","Extra_1","Extra_2"
"A","Smith","Address 1","e1e1e1e1e1","e2e2e2e2e2"
"B","Jones","Address 2, Suite 100","e1e1e1e1e1","e2e2e2e2e2"
==============================================================
-- SQL*Loader control file sqlload_1234.ctl
LOAD DATA
INFILE '/sqlload/quoted_csv.txt'
BADFILE '/sqlload/sqlload_1234.bad'
DISCARDFILE '/sqlload/sqlload_1234.dsc'
Truncate
INTO TABLE IMPORT_TEST
FIELDS TERMINATED BY ','
TRAILING NULLCOLS
(fname "rtrim(trim( chr(34) from :fname),chr(10)||chr(13)|| chr(34) )" ,
lname "rtrim(trim( chr(34) from :lname),chr(10)||chr(13)|| chr(34) )" ,
address "rtrim(trim( chr(34) from :address),chr(10)||chr(13)|| chr(34) )" ,
extra_1 "rtrim(trim( chr(34) from :extra_1),chr(10)||chr(13)|| chr(34) )" ,
extra_2 "rtrim(trim( chr(34) from :extra_2),chr(10)||chr(13)|| chr(34) )"
)
==============================================================
$ORACLE_BIN/sqlldr UID\@DSN/PWD control=sqlload/sqlload_1234.ctl log=sqlload/sqlload_1234.log
skip=1 ERRORS=999999999

==============================================================
-- sqlload_1234.log

SQL*Loader: Release 8.1.7.0.1 - Production on Fri Sep 17 10:38:12 2004

(c) Copyright 2000 Oracle Corporation. All rights reserved.

Control File: /sqlload/sqlload_1234.ctl
Data File: /sqlload/quoted_csv.txt
Bad File: /sqlload/sqlload_1234.bad
Discard File: /sqlload/sqlload_1234.dsc
(Allow all discards)

Number to load: ALL
Number to skip: 1
Errors allowed: 999999999
Bind array: 64 rows, maximum of 65536 bytes
Continuation: none specified
Path used: Conventional

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

Column Name Position Len Term Encl Datatype
------------------------------ ---------- ----- ---- ---- ---------------------
FNAME FIRST * , CHARACTER
SQL string for column : "rtrim(trim( chr(34) from :fname),chr(10)||chr(13)|| chr(34) )"
LNAME NEXT * , CHARACTER
SQL string for column : "rtrim(trim( chr(34) from :lname),chr(10)||chr(13)|| chr(34) )"
ADDRESS NEXT * , CHARACTER
SQL string for column : "rtrim(trim( chr(34) from :address),chr(10)||chr(13)|| chr(34) )"
EXTRA_1 NEXT * , CHARACTER
SQL string for column : "rtrim(trim( chr(34) from :extra_1),chr(10)||chr(13)|| chr(34) )"
EXTRA_2 NEXT * , CHARACTER
SQL string for column : "rtrim(trim( chr(34) from :extra_2),chr(10)||chr(13)|| chr(34) )"


Table IMPORT_TEST:
2 Rows successfully loaded.
0 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: 64500 bytes(50 rows)
Space allocated for memory besides bind array: 0 bytes

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

Run began on Fri Sep 17 10:38:12 2004
Run ended on Fri Sep 17 10:38:14 2004

Elapsed time was: 00:00:02.65
CPU time was: 00:00:00.01
==============================================================
SQL> select * from import_test;

IT_ID FNAME LNAME ADDRESS EXTRA_1 EXTRA_2
--------- ---------- ---------- ------------------------------ ---------- ----------
1 A Smith Address 1 e1e1e1e1e1 e2e2e2e2e2
2 B Jones Address 2 Suite 100 e1e1e1e1e1


and Tom said...

You don't need sql functions, you just want "optionally enclosed by"


ops$tkyte@ORA9IR2> select * from import_test;

no rows selected

ops$tkyte@ORA9IR2> !sqlldr / t

SQL*Loader: Release 9.2.0.5.0 - Production on Fri Sep 17 14:50:40 2004

Copyright (c) 1982, 2002, Oracle Corporation. All rights reserved.

Commit point reached - logical record count 3

ops$tkyte@ORA9IR2> select * from import_test;

IT_ID FNAME LNAME ADDRESS EXTRA_1
---------- ---------- ---------- ------------------------------ ----------
EXTRA_2
----------
1 FName LName Address Extra_1
Extra_2

2 A Smith Address 1 e1e1e1e1e1
e2e2e2e2e2

3 B Jones Address 2, Suite 100 e1e1e1e1e1
e2e2e2e2e2


ops$tkyte@ORA9IR2> !cat t.ctl
LOAD DATA
INFILE *
Truncate
INTO TABLE IMPORT_TEST
FIELDS TERMINATED BY ',' optionally enclosed by '"'

TRAILING NULLCOLS
(fname ,
lname ,
address ,
extra_1 ,
extra_2
)
begindata
"FName","LName","Address","Extra_1","Extra_2"
"A","Smith","Address 1","e1e1e1e1e1","e2e2e2e2e2"
"B","Jones","Address 2, Suite 100","e1e1e1e1e1","e2e2e2e2e2"


Rating

  (9 ratings)

Is this answer out of date? If it is, please let us know via a Comment

Comments

Useful tip

Raja, August 03, 2005 - 2:01 am UTC

Hi Tom,
Thanks a lot for your response on the above query. Can you please tell me how to trim the trailing blank spaces in the dat file. Eg. My dept.dat file is generated by a program and I don't have any control in that program. The output for select * from dept will be.
10,ACCOUNTING ,NEW YORK
20,RESEARCH ,DALLAS
30,SALES ,CHICAGO
40,OPERATIONS ,BOSTON
50,FINANCE ,CHENNAI
55,new ,new
25,all ,all
80,TEST ,BANGALORE
My problem is if I use the above output as the dat file then the dname gets loaded with trailing blank spaces. Could you please tell me how to remove the trailing blank spaces in dname before loading into dept table using SQL loader.
Thanks in Advance
Raja


Tom Kyte
August 03, 2005 - 10:27 am UTC

ops$tkyte@ORA10G> !cat t.ctl
load data
INFILE *
replace INTO TABLE dept
FIELDS TERMINATED BY ','
(deptno "trim(:deptno)", dname "trim(:dname)", loc "trim(:loc)")
begindata
10,ACCOUNTING    ,NEW YORK
20,RESEARCH      ,DALLAS
30,SALES         ,CHICAGO
40,OPERATIONS    ,BOSTON
50,FINANCE       ,CHENNAI
55,new           ,new
25,all           ,all
80,TEST          ,BANGALORE
 
ops$tkyte@ORA10G> !sqlldr / t.ctl
 
SQL*Loader: Release 10.1.0.4.0 - Production on Wed Aug 3 10:17:52 2005
 
Copyright (c) 1982, 2004, Oracle.  All rights reserved.
 
Commit point reached - logical record count 8
 
ops$tkyte@ORA10G> select '"' || dname || '"' from dept;
 
'"'||DNAME||'"'
----------------
"ACCOUNTING"
"RESEARCH"
"SALES"
"OPERATIONS"
"FINANCE"
"new"
"all"
"TEST"
 
8 rows selected.


OR, if you don't want sql in there, use position:

ops$tkyte@ORA10G> !cat t.ctl
load data
INFILE *
replace INTO TABLE dept
FIELDS TERMINATED BY ','
(deptno position(1:2),
 dname  position(4:17),
 loc    position(19:20)
)
begindata
10,ACCOUNTING    ,NEW YORK
20,RESEARCH      ,DALLAS
30,SALES         ,CHICAGO
40,OPERATIONS    ,BOSTON
50,FINANCE       ,CHENNAI
55,new           ,new
25,all           ,all
80,TEST          ,BANGALORE
 
ops$tkyte@ORA10G> !sqlldr / t.ctl
 
SQL*Loader: Release 10.1.0.4.0 - Production on Wed Aug 3 10:19:22 2005
 
Copyright (c) 1982, 2004, Oracle.  All rights reserved.
 
Commit point reached - logical record count 8
 
ops$tkyte@ORA10G> select '"' || dname || '"' from dept;
 
'"'||DNAME||'"'
----------------
"ACCOUNTING"
"RESEARCH"
"SALES"
"OPERATIONS"
"FINANCE"
"new"
"all"
"TEST"
 
8 rows selected.
 

SQL Loader Problem with quoted, tild seperated data file

Srinivasa, May 04, 2006 - 6:05 am UTC

I have a data file fields seperated by tild (~) and enclosed by quotations. But inside the quoted string there are again quotes.

For example,

"7758100"~"PIN INITIAL "R" GOLD"~1
How to load such data. Please help.

control file looks likes this.

load data
infile dmdunit.dat
append
into table xyz
fields terminated by "~" optionally enclosed by '"'
( dmdunit char,
desc char,
ulevel integer external
)


Tom Kyte
May 04, 2006 - 7:32 am UTC

they would have to be escaped - OR - if the "optionally" is not really optionally - but mandatory, you could use

fields terminated by "~"


(leave off the optionally enclosed by bit) and then

ops$tkyte@ORA9IR2> !cat t.ctl
load data
infile *
into table t
replace
fields terminated by "~"
(a char "trim( both '\"' from :a )",
 b char "trim( both '\"' from :b )"
)
begindata
"fdafdad"~"daaafda"a"fdafdas"

ops$tkyte@ORA9IR2> !sqlldr / t

SQL*Loader: Release 9.2.0.6.0 - Production on Thu May 4 07:32:45 2006

Copyright (c) 1982, 2002, Oracle Corporation.  All rights reserved.

Commit point reached - logical record count 1

ops$tkyte@ORA9IR2> select * from t;

A                    B
-------------------- --------------------
fdafdad              daaafda"a"fdafdas
 

Excellent

a reader, May 04, 2006 - 8:18 am UTC

Thank you Tom.

This worked fine.

sqlldr question

A reader, November 11, 2008 - 2:07 pm UTC

Hi Tom,

I have created a table x as follows:

create table x(y varchar2(500) );

I am using sql*loader to load the data and I'm using "|" as a delimiter and I have data with "", normal single quotes and | as a literal.

I am unable to load | as a literal value using my current control file:

load data
infile *
truncate
into table x
fields terminated by "|"
(y)
begindata
"hmmm|ajob"|

Any ideas how I can accomplish this?
Tom Kyte
November 11, 2008 - 4:53 pm UTC

optionally enclosed by '"'

read the original answer.

sqlldr

A reader, November 12, 2008 - 5:54 am UTC

I did read the original answer - but that doesn't solve my problem if I have data with double, double quotes, quotes and pipes (as literals):

e.g. 
load data
infile *
truncate
into table x
fields terminated by "|" optionally enclosed by '"'
(y)
begindata
pyz "xyz qwe" mya?ab k.T.|
""I LOVE WORK" NOT?AB A.B." - BS""|
"ilovemywork "No No" my?ab I.T."|
"ABC (WE'WHERT HG "BLAH"|
""ABC, XYZ""|
"Res|RET"|


SQL*Loader: Release 9.2.0.8.0 - Production on Wed Nov 12 10:30:19 2008

Copyright (c) 1982, 2002, Oracle Corporation.  All rights reserved.

Commit point reached - logical record count 6

  1* select * from x
SQL> /

Y
------------------------------------------
pyz "xyz qwe" mya?ab k.T.
Res|RET

As you can see - not all of the data loads into the table

Any ideas would be much appreciated...

Tom Kyte
November 12, 2008 - 10:36 am UTC

I cannot even parse that data in my head.


for example, describe your logic for this one:

""I LOVE WORK" NOT?AB A.B." - BS""|



sqlldr

A reader, November 12, 2008 - 11:22 am UTC

OK Tom, in that example:

""I LOVE WORK" NOT?AB A.B." - BS""|

I want to have loaded in the database:

"I LOVE WORK" NOT?AB A.B." - BS"

i.e. whatever is delimited by pipes and double quotes
Tom Kyte
November 13, 2008 - 4:14 pm UTC

tell me the LOGIC behind that.

you are following no known standard of enclosing quotes in quotes that I've ever seen.



You appear to want just "terminated by" for that string

but for others you need optionally enclosed by for having the terminator in the string itself.



Looks like crap data

Sharuk, November 13, 2008 - 12:31 pm UTC

Don't mind, but the data looks like corrupted. why don't u clean it with "sed"(Unix) or "EditPlus"(Windows)

sqlldr

A reader, November 13, 2008 - 1:02 pm UTC

Listen mate - it might look like crap data to you ..but it doesn't matter what the ** it is - as long as it's able to load. IT'S A TEST!

sqlldr

A reader, November 13, 2008 - 5:04 pm UTC

Hi Tom,

The problem is we are having data coming in: The delimiter for the data is pipe and we cannot change that. However the data coming into the data file can contain pipe or double quotes as a literal (i.e. part of the data) We cannot ask them to change the data and we can't change the delimiter they are using.

I don't think there is a solution to this unless they clean up the data as there is no way of knowing what delimits what!
Tom Kyte
November 14, 2008 - 5:02 pm UTC

probably not - it is not really parsable - you have slightly different rules for different bits of data.

If you can sit down and as a human being detail the logic, the processing logic for each and every single case - we could almost certainly write a simple parser and do this in SQL with an external table (map the entire row as input and either use substr/instr or regular expressions to break the data up into columns)



...
I don't think there is a solution to this unless they clean up the data as
there is no way of knowing what delimits what!
.....

we have a saying for that.


...
We cannot
ask them to change the data and we can't change the delimiter they are using.
...

I'd ask them "of what use is this data to anyone then - please give us the rules for processing this data"

Garbage in
Garbage out

More to Explore

PL/SQL demos

Check out more PL/SQL tutorials on our LiveSQL tool.

PL/SQL docs

PL/SQL reference manual from the Oracle documentation library