ADA - Records in a hashmap. Problems with printing the hash map - hashmap

I'm new to programming and this is my first attempt at a data container with records. I'm having difficulty with printing the hashmap between line 65-70. I'm guessing I need to break down the record and print each of its attributes individually but I'm not sure on the best way to do that. The error state 'no candidates match the actuals: missing argument for parameter.
with Ada.Text_IO; use Ada.Text_IO;
with Ada.Integer_Text_IO; use Ada.Integer_Text_IO;
with Ada.Strings.Unbounded; use Ada.Strings.Unbounded;
with Ada.Text_IO.Unbounded_IO; use Ada.Text_IO.Unbounded_IO;
with Ada.Strings.Hash;
with Ada.Containers.Hashed_Maps;
with Ada.Characters.Handling;
procedure Hashmap_Records is
type Guest is record
Name : Unbounded_String := Null_Unbounded_String;
Attending : Boolean := False;
Vegitarian : Boolean := False;
Kids : Natural := 0;
end record;
function Equivalent_Keys(Left : Unbounded_String;
Right : Unbounded_String)
return Boolean is
begin
return Left = Right;
end Equivalent_Keys;
function U_To_L(Key : in Unbounded_String)
return Unbounded_String is
begin
return To_Unbounded_String(Ada.Characters.Handling.To_Lower(To_String(Key)));
end U_To_L;
function Hash_Func(Key : in Unbounded_String)
return Ada.Containers.Hash_Type is
begin
return Ada.Strings.Hash(To_String(Key));
end Hash_Func;
package Guest_Tracker is new Ada.Containers.Hashed_Maps(Key_Type => Unbounded_String,
Element_Type => Guest,
Hash => Hash_Func,
Equivalent_Keys => Equivalent_Keys);
Tracked_Guests : Guest_Tracker.Map;
User_Input : Natural := 0;
Name_Input : Unbounded_String := Null_Unbounded_String;
Name : Unbounded_String := Null_Unbounded_String;
Attendance_Input : Unbounded_String := Null_Unbounded_String;
Vegitarian_Input : Unbounded_String := Null_Unbounded_String;
Kids_Input : Natural := 0;
Temp_Attendance : Boolean := False;
Temp_Vegi : Boolean := False;
Procedure Populate_Hash_Map is
begin
Tracked_Guests.Insert(Key => To_Unbounded_String("John Smith"),
New_Item => (To_Unbounded_String("John"), True, False, 1));
Tracked_Guests.Insert(Key => To_Unbounded_String("Robert Johnson"),
New_Item => (To_Unbounded_String("Rob"), True, True, 2));
end Populate_Hash_Map;
procedure Print_Hash_Map(Position : Guest_Tracker.Cursor) is ---
begin
Put_Line("The key: " & To_String(Guest_Tracker.Key(Position)) &
" the data item: ");
Put(Guest_Tracker.Element(Position)); ----THIS IS WHERE ERRORS OCCUR
end Print_Hash_Map; ----
begin
Populate_Hash_Map;
loop
Put_Line(" - Menu - ");
Put_Line(" - 1 - Enter new value.");
Put_Line(" - 2 - Delete Existing Value.");
Put_Line(" - 3 - Print entire hashmap.");
Put_Line(" - 4 - Exit Application.");
New_Line;
Put(" - > ");
declare
begin
Get(User_Input);
exception
when Data_Error =>
Put_Line("ERROR : The entered value is not an integer, please try again!");
User_Input := 0;
when others =>
Put_Line("ERROR: An unknown error has occured!");
end;
Skip_Line;
New_Line;
if User_Input = 1 then
Put_Line("Enter a new value.");
Put(" Name - > ");
Name_Input := Get_Line;
Name := Name_Input;
New_Line;
Put(" Attending? (yes/y/no/n) - > ");
Attendance_Input := Get_Line;
New_Line;
Put(" Vegitarian? (yes/y/no/n) - > ");
Vegitarian_Input := Get_Line;
New_Line;
Put("How many children attending? - > ");
Get(Kids_Input);
New_Line;
if (U_To_L(Attendance_Input) = To_Unbounded_String("no"))
or (U_To_L(Attendance_Input) = To_Unbounded_String("n")) then
Temp_Attendance := False;
elsif (U_To_L(Attendance_Input) = To_Unbounded_String("y"))
or (U_To_L(Attendance_Input) = To_Unbounded_String("yes")) then
Temp_attendance := True;
else
Put_Line("WARNING: The confirmation that you entered is not recognized.");
end if;
if (U_To_L(Vegitarian_Input) = To_Unbounded_String("no"))
or (U_To_L(Vegitarian_Input) = To_Unbounded_String("n")) then
Temp_Vegi := False;
elsif (U_To_L(Vegitarian_Input) = To_Unbounded_String("y"))
or (U_To_L(Vegitarian_Input) = To_Unbounded_String("yes")) then
Temp_Vegi := True;
else
Put_Line("WARNING: The confirmation that you entered is not recognized.");
end if;
Guest_Tracker.Insert(Container => Tracked_Guests,
Key => Name_Input,
New_item => (Name, Temp_Attendance, Temp_Vegi, Kids_Input));
elsif User_Input = 2 then
Put("Delete a value - > ");
Name_Input := Get_Line;
New_Line;
declare
begin
Guest_Tracker.Delete(Container => Tracked_Guests,
Key => Name_Input);
exception
when Constraint_Error =>
Put_Line("The name: '" & To_String(Name_Input) & "' is not found.");
when others =>
Put_Line("ERROR: Another error has been discovered!");
end;
elsif User_Input = 3 then
Tracked_Guests.Iterate(Print_Hash_Map'access);
New_Line;
elsif User_Input = 4 then
exit;
end if;
end loop;
end Hashmap_Records;

This has nothing to do with the hash map. You are assuming that there is a Put procedure that outputs your record type Guest but there is none. You only have the Put subroutines from Ada.Text_IO, Ada.Integer_Text_IO and Ada.Text_IO.Unbounded_IO available, none of which take a value of type Guest as parameter.
Ada does not automatically generate a subroutine to pretty-print a record value when you define the record type. You have to do it yourself, e.g.
procedure Put (Value : Guest) is
begin
Put ("Guest(Name: "); -- supplied by Ada.Text_IO
Put (Value.Name); -- supplied by Ada.Text_IO.Unbounded_IO
Put (", Attending: ");
Put (Value.Attending'Img); -- 'Img is GNAT-specific; standard is
-- Boolean'Image (Value.Attending)
Put (", Vegitarian: ");
Put (Value.Vegitarian'Img);
Put (", Kids: ");
Put (Value.Kids); -- supplied by Ada.Integer_Text_IO
Put (")");
end Put;

Related

ORACLE PL-SQL How to SPLIT a string and RETURN the list using a Function

How to Split the given String for the given Delimiter.
Ex:
INPUT
String => '1,2,3,4,5'
Delimiter => ','
OUTPUT
1
2
3
4
5
What about this? The regular expression allows for null list elements too.
SQL> with tbl(str) as (
2 select '1,2,,4,5' from dual
3 )
4 select regexp_substr(str, '(.*?)(,|$)', 1, level, null, 1) element
5 from tbl
6 connect by level <= regexp_count(str, ',')+1;
ELEMENT
--------
1
2
4
5
SQL>
See this post for a function that returns a list element: REGEX to select nth value from a list, allowing for nulls
I have found my own way to split the given String using a FUNCTION
A TYPE should be declared as belows:
TYPE tabsplit IS TABLE OF VARCHAR2 (50)
INDEX BY BINARY_INTEGER;
And the FUNCTION should be written like this:
FUNCTION fn_split (mp_string IN VARCHAR2, mp_delimiter IN VARCHAR2)
RETURN tabsplit
IS
ml_point NUMBER (5, 0) := 1;
ml_sub_str VARCHAR2 (50);
i NUMBER (5, 0) := 1;
taboutput tabsplit;
ml_count NUMBER (5, 0) := 0;
BEGIN
WHILE i <= LENGTH (mp_string)
LOOP
FOR j IN i .. LENGTH (mp_string)
LOOP
IF SUBSTR (mp_string, j, 1) = mp_delimiter
THEN
ml_sub_str := SUBSTR (mp_string, ml_point, j - ml_point);
ml_point := j + 1;
i := ml_point;
i := i - 1;
taboutput (ml_count) := ml_sub_str;
ml_count := ml_count + 1;
EXIT;
END IF;
END LOOP;
i := i + 1;
END LOOP;
ml_sub_str := SUBSTR (mp_string, ml_point, LENGTH (mp_string));
taboutput (ml_count) := ml_sub_str;
RETURN taboutput;
END fn_split;
This FUNCTION can be used as belows:
DECLARE
taboutput tabsplit;
BEGIN
taboutput := fn_split ('1,2,3,4,5', ',');
FOR i IN 0 .. taboutput.COUNT - 1
LOOP
DBMS_OUTPUT.put_line (taboutput (i));
END LOOP;
END;
SELECT LEVEL AS id, REGEXP_SUBSTR('A,B,C,D', '[^,]+', 1, LEVEL) AS data
FROM dual
CONNECT BY REGEXP_SUBSTR('A,B,C,D', '[^,]+', 1, LEVEL) IS NOT NULL;

Ada case statement with strings

I'm trying to use a string in a case statement, however it is giving me expected a discrete type. Found type Standard.String I understand that strings are not discrete. I'm wondering if there is a work around or not. Here is my code:
function Is_Valid_Direction(Direction_To_Go : in String) return Integer is
Room : Integer := 0;
begin
--if (Direction_To_Go = "NORTH" or Direction_To_Go = "N") then
-- Room := Building(currentRoom).exits(NORTH);
--elsif (Direction_To_Go = "SOUTH" or Direction_To_Go = "S") then
-- Room := Building(currentRoom).exits(SOUTH);
--elsif (Direction_To_Go = "EAST" or Direction_To_Go = "E") then
-- Room := Building(currentRoom).exits(EAST);
--elsif (Direction_To_Go = "WEST" or Direction_To_Go = "W") then
-- Room := Building(currentRoom).exits(WEST);
--elsif (Direction_To_Go = "UP" or Direction_To_Go = "U") then
-- Room := Building(currentRoom).exits(UP);
--elsif (Direction_To_Go = "DOWN" or Direction_To_Go = "D") then
-- Room := Building(currentRoom).exits(DOWN);
--end if;
case Direction_To_Go is
when "NORTH" | "N" => Room := Building(currentRoom).exits(NORTH);
when "SOUTH" | "S" => Room := Building(currentRoom).exits(SOUTH);
when "EAST" | "E" => Room := Building(currentRoom).exits(EAST);
when "WEST" | "W" => Room := Building(currentRoom).exits(WEST);
when "UP" | "U" => Room := Building(currentRoom).exits(UP);
when "DOWN" | "D" => Room := Building(currentRoom).exits(DOWN);
when others => Room := 0;
end case;
return Room;
end Is_Valid_Direction;
The commented section is doing exactly what I want, but with if statements. I'm just trying to see if it's possible with a case statement.
You could map your strings to a discrete type. The easiest being an enumerated type:
procedure Light (Colour : in String) is
type Colours is (Red, Green, Blue);
begin
case Colours'Value (Colour) is -- ' <- magic ;-)
when Red =>
Switch_Red_LED;
when Green =>
Switch_Green_LED;
when Blue =>
Switch_Blue_LED;
end case;
exception
when Constraint_Error =>
raise Constraint_Error with "There is no " & Colour & " LED.";
end Light;
I frequently use an actual map to do this kind of mapping, as it gives you more flexibility than enumerations. Your "names" don't have to conform to enumeration syntax, and you can easily provide variations that all map to a single value.
For the desired function definition, as provided in a package:
package Case_Map is
function Is_Valid_Direction(Direction_To_Go : in String) return Integer;
end Case_Map;
This (non-compiling due to missing game-specific declarations) implementation uses a mapping of strings to an enum that is in turn the case expression:
with Ada.Characters.Handling;
with Ada.Containers.Indefinite_Ordered_Maps;
package body Case_Map is
use Ada.Characters.Handling;
type Directions is (Go_North, Go_South, Go_East, Go_West, Go_Up, Go_Down);
package Direction_Management is new Ada.Containers.Indefinite_Ordered_Maps
(String, Directions);
Direction_Map : Direction_Management.Map;
function Is_Valid_Direction(Direction_To_Go : in String) return Integer is
Room : Integer := 0;
begin
case Direction_Map(To_Upper(Direction_To_Go)) is
when Go_North => Room := Building(CurrentRoom).Exits(NORTH);
when Go_South => Room := Building(CurrentRoom).Exits(SOUTH);
when Go_East => Room := Building(CurrentRoom).Exits(EAST);
when Go_West => Room := Building(CurrentRoom).Exits(WEST);
when Go_Up => Room := Building(CurrentRoom).Exits(UP);
when Go_Down => Room := Building(CurrentRoom).Exits(DOWN);
end case;
return Room;
exception
when Constraint_Error =>
return 0;
end Is_Valid_Direction;
begin
Direction_Map.Insert("NORTH", Go_North);
Direction_Map.Insert("N", Go_North);
Direction_Map.Insert("SOUTH", Go_South);
Direction_Map.Insert("S", Go_South);
Direction_Map.Insert("EAST", Go_East);
Direction_Map.Insert("E", Go_East);
Direction_Map.Insert("WEST", Go_West);
Direction_Map.Insert("W", Go_West);
Direction_Map.Insert("UP", Go_Up);
Direction_Map.Insert("U", Go_Up);
Direction_Map.Insert("DOWN", Go_Down);
Direction_Map.Insert("D", Go_Down);
end Case_Map;
The GNAT compiler itself uses a hash table that maps strings (identifiers, keywords,...) to integer. This is the package namet.ads, and GNATCOLL.Symbolic provides a similar API. This simplifies a number of things (string comparison for instance is much faster), and allow the use of case statements as in your example. So if you are using a limited (or at least slow-growing) list of strings, GNATCOLL.Symbolic might be a suitable approach

Ada: Getting user input to a String(1..10) and filling the rest with whitespace

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;

InnoSetup: don't uninstall changed files

How to tell InnoSetup to not uninstall (text) files which had been changed by the user (== are different from those installed by InnoSetup)?
Or maybe more difficult: when installing a new version over an existing, InnoSetup should ask the user whether to overwrite the changed file, but on a pure uninstall, it should uninstall it without asking.
I recently had a similar problem. This was my solution to detect if a text file (profile) has been changed from the one installed during the last installation run:
Use ISPP (Inno Setup Pre-Processor) to create the list of text files and their hashes at compile time:
[Files]
; ...
#define FindHandle
#define FindResult
#define Mask "Profiles\*.ini"
#sub ProcessFoundFile
#define FileName "Profiles\" + FindGetFileName(FindHandle)
#define FileMd5 GetMd5OfFile(FileName)
Source: {#FileName}; DestDir: {app}\Profiles; Components: profiles; \
Check: ProfileCheck('{#FileMd5}'); AfterInstall: ProfileAfterInstall('{#FileMd5}');
#endsub
#for {FindHandle = FindResult = FindFirst(Mask, 0); FindResult; FindResult = FindNext(FindHandle)} ProcessFoundFile
At the top of the "Code" section I define some useful things:
[Code]
var
PreviousDataCache : tStringList;
function InitializeSetup() : boolean;
begin
// Initialize global variable
PreviousDataCache := tStringList.Create();
result := TRUE;
end;
function BoolToStr( Value : boolean ) : string;
begin
if ( not Value ) then
result := 'false'
else
result := 'true';
end;
In the "Check" event handler I compare the hashes of previous install and current file:
function ProfileCheck( FileMd5 : string ) : boolean;
var
TargetFileName, TargetFileMd5, PreviousFileMd5 : string;
r : integer;
begin
result := FALSE;
TargetFileName := ExpandConstant(CurrentFileName());
Log('Running check procedure for file: ' + TargetFileName);
if not FileExists(TargetFileName) then
begin
Log('Check result: Target file does not exist yet.');
result := TRUE;
exit;
end;
try
TargetFileMd5 := GetMd5OfFile(TargetFileName);
except
TargetFileMd5 := '(error)';
end;
if ( CompareText(TargetFileMd5, FileMd5) = 0 ) then
begin
Log('Check result: Target matches file from setup.');
result := TRUE;
exit;
end;
PreviousFileMd5 := GetPreviousData(ExtractFileName(TargetFileName), '');
if ( PreviousFileMd5 = '' ) then
begin
r := MsgBox(TargetFileName + #10#10 +
'The existing file is different from the one Setup is trying to install. ' +
'It is recommended that you keep the existing file.' + #10#10 +
'Do you want to keep the existing file?', mbConfirmation, MB_YESNO);
result := (r = idNo);
Log('Check result: ' + BoolToStr(result));
end
else if ( CompareText(PreviousFileMd5, TargetFileMd5) <> 0 ) then
begin
r := MsgBox(TargetFileName + #10#10 +
'The existing file has been modified since the last run of Setup. ' +
'It is recommended that you keep the existing file.' + #10#10 +
'Do you want to keep the existing file?', mbConfirmation, MB_YESNO);
result := (r = idNo);
Log('Check result: ' + BoolToStr(result));
end
else
begin
Log('Check result: Existing target has no local modifications.');
result := TRUE;
end;
end;
In the "AfterInstall" event handler I mark the file hash to be stored in
Registry later. Because in my tests the event was triggered even if the file move failed (target file is read-only) I compare the hash again to find out if the file move was successful:
procedure ProfileAfterInstall( FileMd5 : string );
var
TargetFileName, TargetFileMd5 : string;
begin
TargetFileName := ExpandConstant(CurrentFileName());
try
TargetFileMd5 := GetMd5OfFile(TargetFileName);
except
TargetFileMd5 := '(error)';
end;
if ( CompareText(TargetFileMd5, FileMd5) = 0 ) then
begin
Log('Storing hash of installed file: ' + TargetFileName);
PreviousDataCache.Add(ExtractFileName(TargetFileName) + '=' + FileMd5);
end;
end;
procedure RegisterPreviousData( PreviousDataKey : integer );
var
Name, Value : string;
i, n : integer;
begin
for i := 0 to PreviousDataCache.Count-1 do
begin
Value := PreviousDataCache.Strings[i];
n := Pos('=', Value);
if ( n > 0 ) then
begin
Name := Copy(Value, 1, n-1);
Value := Copy(Value, n+1, MaxInt);
SetPreviousData(PreviousDataKey, Name, Value);
end;
end;
end;
Inno can't do this check natively.
To not replace changed files during install, you'll need to use custom [Code] to do a checksum and compare against a known good value that is precomputed or saved from the previous install.
To avoid removing them during uninstall, you'll need to disable Inno's own uninstall for that file and check against the same checksum before removing them, again in [Code].
Note that it's better to keep any files the user can edit outside of the setup to handle this situation better and to correctly adhere to the application guidelines.

Is there a function to split a string in Oracle PL/SQL?

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;

Resources