I am writing a data import utility to import data into a database application. The data can be retrieved from the clipboard (e.g. from Excel) or a file and is initially stored in a TStringlist. In the next step the user can select the appropriate column separator (tab, comma, etc.). After splitting the data into columns (using the selected separator), each string value (let's call it a cell value) is checked for validity against the applicable database field type.
The issue I'm having is with datetime values (and possibly date and/or time value, but I haven't checked that yet). If the user selects the "wrong" separator, the data is not split and each line contains a single column (which is correct, of course). In such a case, the cell value may contain a string like the one below (I am showing the debug value to show the correct separator, tab in this case):
'04/01/10 00:00'#9'2.50'#9'100'#9'Text value'
When using TryStrToDateTime or StrToDatetime, this string value "passes" because the string is "clipped" (i.e. ignoring the trailing text and returning the correct datetime value of 04/01/10). If I then, at a later stage, pass the cell value (the original string) as a variant to a range comparison function, it obviously fails with an EVariantTypeCastError.
Is there a method (or existing Delphi RTL function) to check for the string value to contain a valid date(time) only (i.e. no trailing text)? Unable to find a function (or function parameter) taking this into account, I have also been thinking about checking string length but my software is used internationally and the datetime format will therefore be varying and may thus have different lengths.
PS: I have added below sample code here as I couldn't add it to my comments.
procedure TForm1.Button1Click(Sender: TObject);
var
lStrVal: string;
lIsDateTime: boolean;
lDateTimeVal: TDateTime;
begin
lStrVal := '01/01/2019 10:00' + chr(9) + '120.00' + chr(9) + 'Some text';
lIsDateTime := TryStrToDateTime(lStrVal, lDateTimeVal);
if lIsDateTime then
messageDlg('String value is a date/time! ' + #13#10 + 'String: ' + lStrVal + #13#10 + 'Date/time is: ' + DateTimeToStr(lDateTimeVal), mtInformation, [mbOK], 0)
else
messageDlg('String value cannot be converted to a date/time!', mtWarning, [mbOK], 0);
end;
It appears that the solution (at least for my application) is to combine TryStrToDateTime and TryStrToDate. To demonstrate, start a new VCL application and drop a ListBox and TButton on the form (leave standard names). Paste the following code in the TButton OnClick handler:
var
lDateVal, lDateTimeVal: TDateTime;
lStrVal: string;
lResult: boolean;
begin
ListBox1.Items.Clear;
lStrVal := '01/01/2019 10:00' + '-120.00' + '-Some text';
ListBox1.Items.Add('String value : ' + lStrVal);
lDateVal := EncodeDate(2000, 1, 1);
lDateTimeVal := EncodeDate(2000, 1, 1) + EncodeTime(1, 0, 0, 0);
lResult := TryStrToDate(lStrVal, lDateVal);
if lResult then
ListBox1.Items.Add(' - TryStrToDate : TRUE')
else
ListBox1.Items.Add(' - TryStrToDate : FALSE');
ListBox1.Items.Add('DateTime value: ' + DateTimeToStr(lDateVal));
lResult := TryStrToDateTime(lStrVal, lDateTimeVal);
if lResult then
ListBox1.Items.Add(' - TryStrToDateTime : TRUE')
else
ListBox1.Items.Add(' - TryStrToDateTime : FALSE');
ListBox1.Items.Add('DateTime value: ' + DateTimeToStr(lDateTimeVal));
// Reset
lDateVal := EncodeDate(2000, 1, 1);
lDateTimeVal := EncodeDate(2000, 1, 1) + EncodeTime(1, 0, 0, 0);
lResult := TryStrToDateTime(lStrVal, lDateTimeVal) and TryStrToDate(lStrVal, lDateVal);
if lResult then
ListBox1.Items.Add(' - Combined : TRUE')
else
ListBox1.Items.Add(' - Combined : FALSE');
ListBox1.Items.Add('DateTime value: ' + DateTimeToStr(lDateTimeVal));
ListBox1.Items.Add('');
lStrVal := '01/01/2019 10:00';
ListBox1.Items.Add('String value : ' + lStrVal);
lResult := TryStrToDateTime(lStrVal, lDateTimeVal) and TryStrToDate(lStrVal, lDateVal);
if lResult then
ListBox1.Items.Add(' - Combined : TRUE')
else
ListBox1.Items.Add(' - Combined : FALSE');
try
lDateTimeVal := VarToDateTime(lStrVal);
lResult := true;
except
lResult := false;
end;
if lResult then
ListBox1.Items.Add(' - Using Variant : TRUE')
else
ListBox1.Items.Add(' - Using Variant : FALSE');
This leaves me with the question if this means TryStrToDateTime is incorrectly implemented in comparison to TryStrToDate? As a minimum there seems to be a contradiction on the "design" of the functions; "string value is a Date" versus "string value begins with a DateTime". Unless I am missing something...
This question is in regards to a program I'm working on for an intro to programming class in Ada. The only packages I can use are the basic text, integer, and float packages. I'm writing a procedure right now to remove the spaces from the string and I keep getting a constraint error on the same line. This is the line:
if inputString(count) /= Character'Val(32) then
I have count set to start at 1 and then increment through a loop until the end of the string, which would in turn check each character of the string to see if it was different than the character value of a blank space (32). I was then going to include this line:
noSpace(count..count) := inputString(count..count)
where noSpace is to be the string with no spaces. My line of thinking may be completely off and I may need to approach this from a completely different angle.
Update: I've now managed to get past the error message and successfully remove the spaces from my string. But now when I print the new string there is a random variety of characters and shaded spaces after it. I believe the problem is caused because the new string is shorter than the previous one and the last characters are different.
Probably the bug is hid somewhere else.
Try this:
with Ada.Text_IO;
procedure String_Remove_Space is
function Count_Space( S : String ) return Natural is
Sum_Of_Space : Natural := 0;
begin
for I in S'range loop
if S(I) = ' ' then
Sum_Of_Space := Sum_Of_Space + 1;
end if;
end loop;
return Sum_Of_Space;
end Count_Space;
Input_String : String := "Apple is on the tree";
Input_No : String := "AppleIsOnTheTree";
No_Space : String(1..Input_String'Length - Count_Space(Input_String));
Index : Positive := 1;
begin
for I in Input_String'range loop
if Input_String(I) /= ' ' then
No_Space(Index) := Input_String(I);
Index := Index + 1;
end if;
end loop;
Ada.Text_IO.Put_Line(Input_String);
Ada.Text_IO.Put_Line(No_Space);
end String_Remove_Space;
If there are spaces in your input string, the valid part of the output string must be shorter. I imagine you wrote something like
noSpace : String := inputString;
to start things off with noSpace as long as it would need to be if there were no spaces in inputString? then, as you squeeze the spaces out, you seem to be writing garbage (undefined characters) to the end of noSpace. This doesn’t matter so long as you only deal with the valid part.
I tried this:
with Ada.Text_IO; use Ada.Text_IO;
procedure Lareaper is
function Strip_Space (S : String) return String is
Result : String := S;
Current : Positive := Result'First;
Last : Natural := Result'Last;
begin
loop
exit when Current > Last; -- processed the whole string
if Result (Current) = ' ' then
-- slide the rest of the string back one
Result (Current .. Last - 1) := Result (Current + 1 .. Last);
-- which reduces the length by 1 too
Last := Last - 1;
else
-- non-space character, skip
Current := Current + 1;
end if;
end loop;
-- return only the part of the result that doesn't contain spaces
return Result (Result'First .. Last);
end Strip_Space;
begin
Put_Line ('|' & Strip_Space ("") & '|');
Put_Line ('|' & Strip_Space (" ") & '|');
Put_Line ('|' & Strip_Space ("a") & '|');
Put_Line ('|' & Strip_Space ("ab") & '|');
Put_Line ('|' & Strip_Space (" a") & '|');
Put_Line ('|' & Strip_Space ("a ") & '|');
Put_Line ('|' & Strip_Space ("a b") & '|');
Put_Line ('|' & Strip_Space (" a b ") & '|');
end Lareaper;
which outputs
$ ./lareaper
||
||
|a|
|ab|
|a|
|a|
|ab|
|ab|
Count should start at InputString'First, not at 1.
In particular, when InputString is created as a slice/substring of another string, it is very likely that its first character is not at index 1.
Just to show you another possible solution, although one I would never use in real life:
function Strip_Space (S : String) return String is
begin
if S'Length = 0 then
return "";
elsif S (S'First) = ' ' then
return Strip_Space (S (S'First + 1 .. S'Last));
else
return S (S'First) & Strip_Space (S (S'First + 1 .. S'Last));
end if;
end Strip_Space;
And then there's the way I would really do it. I didn't read Simon's answer carefully enough to realize that he was sliding substrings around. The most efficient answer, I think, is similar to Mark's, but you don't actually need to count the spaces in the string first.
function Strip_Space (Input_String : String) return String is
No_Space : String(1..Input_String'Length);
Index : Positive := 1;
begin
for I in Input_String'range loop
if Input_String(I) /= ' ' then
No_Space(Index) := Input_String(I);
Index := Index + 1;
end if;
end loop;
return No_Space(1 .. Index - 1);
end Strip_Space;
I have defined
subtype String10 is String(1..10);
and I am attempting to get keyboard input to it without having to manually enter whitespace before hitting enter. I tried get_line() but from some reason it wouldn't actually wait for input before outputting the get put() command, and I also think it will just leave whatever was in the string before there and not fill it with white space.
I know about and have used Bounded_String and Unbounded_String, but I am wondering if there is a way to make this work.
I've tried making a function for it:
--getString10--
procedure getString10(s : string10) is
c : character;
k : integer;
begin
for i in integer range 1..10 loop
get(c);
if Ada.Text_IO.End_Of_Line = false then
s(i) := c;
else
k := i;
exit;
end if;
end loop;
for i in integer range k..10 loop
s(i) := ' ';
end loop;
end getString10;
but, here, I know the s(i) doesn't work, and I don't think the
"if Ada.Text_IO.End_Of_Line = false then"
does what I'm hoping it will do either. It's kinda just a placeholder while I look for the actual way to do it.
I been searching for a couple hours now, but Ada documentation isn't as available or clear as other languages. I've found a lot about getting strings, but not what I'm looking for.
Just pre-initialize the string with spaces before calling Get_Line.
Here's a little program I just threw together:
with Ada.Text_IO; use Ada.Text_IO;
procedure Foo is
S: String(1 .. 10) := (others => ' ');
Last: Integer;
begin
Put("Enter S: ");
Get_Line(S, Last);
Put_Line("S = """ & S & """");
Put_Line("Last = " & Integer'Image(Last));
end Foo;
and the output I get when I run it:
Enter S: hello
S = "hello "
Last = 5
Another possibility, rather than pre-initializing the string, is to set the remainder to spaces after the Get_Line call:
with Ada.Text_IO; use Ada.Text_IO;
procedure Foo is
S: String(1 .. 10);
Last: Integer;
begin
Put("Enter S: ");
Get_Line(S, Last);
S(Last+1 .. S'Last) := (others => ' ');
Put_Line("S = """ & S & """");
Put_Line("Last = " & Integer'Image(Last));
end Foo;
For very large arrays, the latter approach might be more efficient because it doesn't assign the initial portion of the string twice, but in practice the difference is unlikely to be significant.
As an alternative, use either function Get_Line, which returns a fixed-length String that "has a lower bound of 1 and an upper bound of the number of characters read." The example Line_By_Line uses the variation that reads from a file. If need be, you can then use procedure Move to copy the Source string to the Target string; the procedure automatically pads with space by default.
Addendum: For example, this Line_Test pads with * and silently truncates long lines on the right.
with Ada.Integer_Text_IO;
with Ada.Strings.Fixed;
with Ada.Text_IO;
procedure Line_Test is
Line_Count : Natural := 0;
Buffer: String(1 .. 10);
begin
while not Ada.Text_IO.End_Of_File loop
declare
Line : String := Ada.Text_IO.Get_Line;
begin
Line_Count := Line_Count + 1;
Ada.Integer_Text_IO.Put(Line_Count, 0);
Ada.Text_IO.Put_Line(": " & Line);
Ada.Strings.Fixed.Move(
Source => Line,
Target => Buffer,
Drop => Ada.Strings.Right,
Justify => Ada.Strings.Left,
Pad => '*');
Ada.Integer_Text_IO.Put(Line_Count, 0);
Ada.Text_IO.Put_Line(": " & Buffer);
end;
end loop;
end Line_Test;
I need to write a procedure to normalize a record that have multiple tokens concatenated by one char. I need to obtain these tokens splitting the string and insert each one as a new record in a table. Does Oracle have something like a "split" function?
There is apex_util.string_to_table - see my answer to this question.
Also, prior to the existence of the above function, I once posted a solution here on my blog.
Update
In later versions of APEX, apex_util.string_to_table is deprecated, and a similar function apex_string.split is preferred.
If APEX_UTIL is not available, you have a solution using REGEXP_SUBSTR().
Inspired from http://nuijten.blogspot.fr/2009/07/splitting-comma-delimited-string-regexp.html :
DECLARE
I INTEGER;
TYPE T_ARRAY_OF_VARCHAR IS TABLE OF VARCHAR2(2000) INDEX BY BINARY_INTEGER;
MY_ARRAY T_ARRAY_OF_VARCHAR;
MY_STRING VARCHAR2(2000) := '123,456,abc,def';
BEGIN
FOR CURRENT_ROW IN (
with test as
(select MY_STRING from dual)
select regexp_substr(MY_STRING, '[^,]+', 1, rownum) SPLIT
from test
connect by level <= length (regexp_replace(MY_STRING, '[^,]+')) + 1)
LOOP
DBMS_OUTPUT.PUT_LINE(CURRENT_ROW.SPLIT);
MY_ARRAY(MY_ARRAY.COUNT) := CURRENT_ROW.SPLIT;
END LOOP;
END;
/
You have to roll your own. E.g.,
/* from :http://www.builderau.com.au/architect/database/soa/Create-functions-to-join-and-split-strings-in-Oracle/0,339024547,339129882,00.htm
select split('foo,bar,zoo') from dual;
select * from table(split('foo,bar,zoo'));
pipelined function is SQL only (no PL/SQL !)
*/
create or replace type split_tbl as table of varchar2(32767);
/
show errors
create or replace function split
(
p_list varchar2,
p_del varchar2 := ','
) return split_tbl pipelined
is
l_idx pls_integer;
l_list varchar2(32767) := p_list;
l_value varchar2(32767);
begin
loop
l_idx := instr(l_list,p_del);
if l_idx > 0 then
pipe row(substr(l_list,1,l_idx-1));
l_list := substr(l_list,l_idx+length(p_del));
else
pipe row(l_list);
exit;
end if;
end loop;
return;
end split;
/
show errors;
/* An own implementation. */
create or replace function split2(
list in varchar2,
delimiter in varchar2 default ','
) return split_tbl as
splitted split_tbl := split_tbl();
i pls_integer := 0;
list_ varchar2(32767) := list;
begin
loop
i := instr(list_, delimiter);
if i > 0 then
splitted.extend(1);
splitted(splitted.last) := substr(list_, 1, i - 1);
list_ := substr(list_, i + length(delimiter));
else
splitted.extend(1);
splitted(splitted.last) := list_;
return splitted;
end if;
end loop;
end;
/
show errors
declare
got split_tbl;
procedure print(tbl in split_tbl) as
begin
for i in tbl.first .. tbl.last loop
dbms_output.put_line(i || ' = ' || tbl(i));
end loop;
end;
begin
got := split2('foo,bar,zoo');
print(got);
print(split2('1 2 3 4 5', ' '));
end;
/
You can use regexp_substr(). Example:
create or replace type splitTable_Type is table of varchar2(100);
declare
l_split_table splitTable_Type;
begin
select
regexp_substr('SMITH,ALLEN,WARD,JONES','[^,]+', 1, level)
bulk collect into
l_split_table
from dual
connect by
regexp_substr('SMITH,ALLEN,WARD,JONES', '[^,]+', 1, level) is not null;
end;
The query iterates through the comma separated string, searches for the comma (,) and then splits the string by treating the comma as delimiter. It returns the string as a row, whenever it hits a delimiter.
level in statement regexp_substr('SMITH,ALLEN,WARD,JONES','[^,]+', 1, level) refers to a pseudocolumn in Oracle which is used in a hierarchical query to identify the hierarchy level in numeric format: level in connect by
This only works in Oracle 10G and greater.
Basically, you use regex_substr to do a split on the string.
https://blogs.oracle.com/aramamoo/entry/how_to_split_comma_separated_string_and_pass_to_in_clause_of_select_statement
Edit:
Archived link: http://web.archive.org/web/20170304121704/https://blogs.oracle.com/aramamoo/entry/how_to_split_comma_separated_string_and_pass_to_in_clause_of_select_statement
The code:
select * from emp where ename in (
select regexp_substr('SMITH,ALLEN,WARD,JONES', '[^,]+', 1, level) from dual
connect by regexp_substr('SMITH,ALLEN,WARD,JONES', '[^,]+', 1, level) is not null
);
You could use a combination of SUBSTR and INSTR as follows :
Example string : field = 'DE124028##$1048708##$000##$536967136##$'
The seperator being ##$.
To get the '1048708' for example :
If the field is of fixed length ( 7 here ) :
substr(field,instr(field,'##$',1,1)+3,7)
If the field is of variable length :
substr(field,instr(field,'##$',1,1)+3,instr(field,'##$',1,2) - (instr(field,'##$',1,1)+3))
You should probably look into SUBSTR and INSTR functions for more flexibility.
Please find next an example you may find useful
--1st substring
select substr('alfa#bravo#charlie#delta', 1,
instr('alfa#bravo#charlie#delta', '#', 1, 1)-1) from dual;
--2nd substring
select substr('alfa#bravo#charlie#delta', instr('alfa#bravo#charlie#delta', '#', 1, 1)+1,
instr('alfa#bravo#charlie#delta', '#', 1, 2) - instr('alfa#bravo#charlie#delta', '#', 1, 1) -1) from dual;
--3rd substring
select substr('alfa#bravo#charlie#delta', instr('alfa#bravo#charlie#delta', '#', 1, 2)+1,
instr('alfa#bravo#charlie#delta', '#', 1, 3) - instr('alfa#bravo#charlie#delta', '#', 1, 2) -1) from dual;
--4th substring
select substr('alfa#bravo#charlie#delta', instr('alfa#bravo#charlie#delta', '#', 1, 3)+1) from dual;
Best regards
Emanuele
function numinstr(p_source in varchar2,p_token in varchar2)
return pls_integer
is
v_occurrence pls_integer := 1;
v_start pls_integer := 1;
v_loc pls_integer;
begin
v_loc:=instr(p_source, p_token, 1, 1);
while v_loc > 0 loop
v_occurrence := v_occurrence+1;
v_start:=v_loc+1;
v_loc:=instr(p_source, p_token, v_start, 1);
end loop;
return v_occurrence-1;
end numinstr;
--
--
--
--
function get_split_field(p_source in varchar2,p_delim in varchar2,nth pls_integer)
return varchar2
is
v_num_delims pls_integer;
first_pos pls_integer;
final_pos pls_integer;
len_delim pls_integer := length(p_delim);
ret_len pls_integer;
begin
v_num_delims := numinstr(p_source,p_delim);
if nth < 1 or nth > v_num_delims+1 then
return null;
else
if nth = 1 then
first_pos := 1;
else
first_pos := instr(p_source, p_delim, 1, nth-1) + len_delim;
end if;
if nth > v_num_delims then
final_pos := length(p_source);
else
final_pos := instr(p_source, p_delim, 1, nth) - 1;
end if;
ret_len := (final_pos - first_pos) + 1;
return substr(p_source, first_pos, ret_len);
end if;
end get_split_field;
I needed a function that splits a clob and makes sure the function is usable in sql.
create or replace type vchar_tab is table of varchar2(4000)
/
create or replace function split(
p_list in clob,
p_separator in varchar2 default '|'
) return vchar_tab pipelined is
C_SQL_VCHAR_MAX constant integer:=4000;
C_MAX_AMOUNT constant integer:=28000;
C_SEPARATOR_LEN constant integer:=length(p_separator);
l_amount integer:=C_MAX_AMOUNT;
l_offset integer:=1;
l_buffer varchar2(C_MAX_AMOUNT);
l_list varchar2(32767);
l_index integer;
begin
if p_list is not null then
loop
l_index:=instr(l_list, p_separator);
if l_index > C_SQL_VCHAR_MAX+1 then
raise_application_error(-20000, 'item is too large for sql varchar2: len='||(l_index-1));
elsif l_index > 0 then -- found an item, pipe it
pipe row (substr(l_list, 1, l_index-1));
l_list:=substr(l_list, l_index+C_SEPARATOR_LEN);
elsif length(l_list) > C_SQL_VCHAR_MAX then
raise_application_error(-20001, 'item is too large for sql varchar2: length exceeds '||length(l_list));
elsif l_amount = C_MAX_AMOUNT then -- more to read from the clob
dbms_lob.read(p_list, l_amount, l_offset, l_buffer);
l_list:=l_list||l_buffer;
else -- read through the whole clob
if length(l_list) > 0 then
pipe row (l_list);
end if;
exit;
end if;
end loop;
end if;
return;
exception
when no_data_needed then -- this happens when you don't fetch all records
null;
end;
/
Test:
select *
from table(split('ASDF|IUYT|KJHG|ASYD'));
In Oracle, below SQL will split myString to substring:
WITH rws AS (
SELECT
'str1,STR2,stR3,StR4' myString
FROM
dual
) SELECT
regexp_substr(
myString,
'[^,]+',
1,
level
) value
FROM
rws
CONNECT BY
level <= length(myString) - length(
replace(
myString,
','
)
) + 1;
Result is:
str1
STR2
stR3
StR4
I like the look of that apex utility. However its also good to know about the standard oracle functions you can use for this: subStr and inStr
http://download.oracle.com/docs/cd/B19306_01/server.102/b14200/functions001.htm
There is a simple way folks. Use REPLACE function. Here is an example of comma separated string ready to be passed to IN clause.
In PL/SQL:
StatusString := REPLACE('Active,Completed', ',', ''',''');
In SQL Plus:
Select REPLACE('Active,Completed', ',', ''',''') from dual;