For solving this, you will probably need some more procedural code. Different
databases have different sets of built-in string functions (as you know). Thus, for finding a solution for this I have written "proof of concept" code that is rather generic and uses just the SUBSTR() function (the equivalent is SUBSTRING() in MS SQL).
When looking at LOAD DATA ... (MySQL) you can see that we need a .csv file,
and an existing table. Using my function, you will be able to SELECT sections
of the .csv file, by passing the column name plus 2 integers: one for the number of the "left-hand side delimiter", and one for the number of the "right-hand side delimiter". (Sounds horrible ...).
Example: suppose we have a comma-separated value looking like this,
and it is stored in a colum called csv:
aaa,111,zzz
If we want to "extract" 111 out of this, we call the function like this:
select split(csv, 1, 2) ... ;
-- 1: start at the first comma
-- 2: end at the second comma
The "start" and the "end" of the string can be selected like this:
select split(csv, 0, 1) ... ; -- from the start of the string (no comma) up to the first comma
select split(csv, 2, 0) ... ; -- from the second comma right up to the end of the string
I know that the function code is not perfect, and that it can be simplified in places (eg in Oracle we should use INSTR(), which can find a particular occurrence of a part of a string). Also, there's no exception handling right now.
It's just a first draft. Here goes ...
create or replace function split(
csvstring varchar2
, lcpos number
, rcpos number )
return varchar2
is
slen pls_integer := 0 ; -- string length
comma constant varchar2(1) := ',' ;
currentchar varchar2(1) := '' ;
commacount pls_integer := 0 ;
firstcommapos pls_integer := 0 ;
secondcommapos pls_integer := 0 ;
begin
slen := length(csvstring);
-- special case: leftmost value
if lcpos = 0 then
firstcommapos := 0 ;
for i in 1 .. slen
loop
currentchar := substr(csvstring, i, 1) ;
if currentchar = comma then
secondcommapos := i - 1 ;
exit ;
end if ;
end loop ;
return substr(csvstring, 1, secondcommapos) ;
end if ;
-- 2 commas somewhere in the middle of the string
if lcpos > 0 and rcpos > 0 then
for i in 1 .. slen
loop
currentchar := substr(csvstring, i, 1) ;
if currentchar = comma then
commacount := commacount + 1;
if commacount = lcpos then
firstcommapos := i ;
end if ;
if commacount = rcpos then
secondcommapos := i ;
end if ;
end if ;
end loop ;
return substr(csvstring, firstcommapos + 1, (secondcommapos-1) - firstcommapos ) ;
end if ;
-- special case: rightmost value
if rcpos = 0 then
secondcommapos := slen ;
for i in reverse 1 .. slen -- caution: count DOWN!
loop
currentchar := substr(csvstring, i, 1) ;
if currentchar = comma then
firstcommapos := i + 1 ;
exit ;
end if ;
end loop ;
return substr(csvstring, firstcommapos, secondcommapos-(firstcommapos-1)) ;
end if ;
end split;
Testing:
-- test table, test data
create table csv (
id number generated always as identity primary key
, astring varchar2(256)
);
-- insert some test data
begin
insert into csv (astring) values ('123,456,88789,null,null');
insert into csv (astring) values ('123,456,99789,1234,null');
insert into csv (astring) values ('123,456,00789,1234,null');
insert into csv (astring) values ('1,2222,77789,null,null');
insert into csv (astring) values ('11,222,88789,null,');
insert into csv (astring) values ('111,22,99789,,');
insert into csv (astring) values ('1111,2,00789,oooo,null');
end;
-- testing:
select
split(astring,0,1) col1
, split(astring,1,2) col2
, split(astring,2,3) col3
, split(astring,3,4) col4
, split(astring,4,0) col5
from csv
-- output
COL1 COL2 COL3 COL4 COL5
123 456 88789 null null
123 456 99789 1234 null
123 456 00789 1234 null
1 2222 77789 null null
11 222 88789 null -
111 22 99789 - -
1111 2 00789 oooo null
... The function seems to be overkill. However, if we write more procedural code, the SQL depending on it becomes rather "elegant". Best of luck with processing your csv!
Best Answer
This solution gives you your "Alternate Solution" answer. The only way to achieve what you are looking for as a primary solution would be a dynamic pivot operation on the result set after you have unpivoted it. This solution does require maintenance but is straightforward and no function is required.
Sample data:
Query:
Edit:
Ideally, you wouldn't have the table stored like that, it looks like it was copied from an Excel spreadsheet. You would have a table with three columns, date, name and value, then query based on that.