I am doing a job interview PL/SQL question, and I am really stuck. Could someone help?
Create stored procedure to return substrings from input string value. Substring delimiter shall be input parameter.
Task description
Stored procedure shall have input parameters:
• STRING
• DELIMITER //any symbol
• STRING_NUMBER //number of substring to be returned
Input example: STRING => ‘one,two,three’,
DELIMITER => ‘e’,
STRING_NUMBER => null
Output shall be: ‘on’
‘,two,thr’
‘’
‘’
If STRING_NUMBER => 2, output shall be: ‘,two,thr’
EDIT:
First, I am trying to trim a string using TRIM function, but that doesn't work. Why?
CREATE OR REPLACE PROCEDURE substring
(STRNG IN VARCHAR2,DELIMITER IN VARCHAR2)
IS
instring VARCHAR2(100);
BEGIN
instring:= TRIM(DELIMITER FROM STRNG);
DBMS_OUTPUT.PUT_LINE(instring);
END;
set serveroutput on
BEGIN
substring('marc','a');
END;
EDIT 2:
This does the part on the job:
CREATE OR REPLACE PROCEDURE substring
(STRNG IN VARCHAR2,DELIMITER IN VARCHAR2)
IS
instring VARCHAR2(100);
BEGIN
instring:= REPLACE(STRNG,DELIMITER);
DBMS_OUTPUT.PUT_LINE(instring);
END;
set serveroutput on
BEGIN
substring('marc','a');
END;
Here is my own solution:
CREATE OR REPLACE PROCEDURE substring
(STRNG IN VARCHAR2,DELIMITER IN VARCHAR2, STRING_NUMBER IN NUMBER)
IS
replace_string VARCHAR2(100);
instring NUMBER;
comma_counter NUMBER;
substring VARCHAR2(100);
BEGIN
replace_string:= REPLACE(STRNG,DELIMITER);
comma_counter:=REGEXP_COUNT(STRNG,',');
instring:=INSTR(STRNG,',',1,comma_counter-STRING_NUMBER+1);
substring:=SUBSTR(replace_string,instring);
DBMS_OUTPUT.PUT_LINE(replace_string);
DBMS_OUTPUT.PUT_LINE(','||substring);
END;
set serveroutput on
BEGIN
substring('one,two,three,four','e',2);
END;
Related
CREATE OR REPLACE PROCEDURE numeros (entra1 NUMBER, entra2 NUMBER)
IS
v_num1 NUMBER;
v_num2 NUMBER;
BEGIN
v_num1:=entra1;
v_num2:=entra2;
END;
-----------------------------------------------------------------------
CREATE OR REPLACE PROCEDURE somando
IS
v_soma NUMBER;
v_num1 NUMBER;
v_num2 NUMBER;
BEGIN
numeros(40,60);
v_soma:=(v_num1+v_num2);
DBMS_OUTPUT.PUT_LINE('O valor da soma de ' ||v_num1||' e '||v_num2||' é:'||v_soma);
END somando;
Why I can't catch the values from the first procedure "numeros", when I execute the procedure "somando" the result is empty " ".
Assuming this is PL/SQL, I believe there are a few possible workarounds for this. The easiest being to make output parameter variables in the procedure to get the numbers like so:
CREATE OR REPLACE PROCEDURE numeros(
entra1 IN NUMBER,
entra2 IN NUMBER,
out_entra1 OUT NUMBER,
out_entra2 OUT NUMBER)
IS
v_num1 NUMBER;
v_num2 NUMBER;
BEGIN
v_num1 := entra1;
v_num2 := entra2;
out_entra1 := v_num1;
out_entra2 := v_num2;
END numeros;
----------------------------
CREATE OR REPLACE PROCEDURE somando IS
v_soma NUMBER;
v_num1 NUMBER;
v_num2 NUMBER;
BEGIN
dbms_output.enable();
numeros(40,60,v_num1,v_num2);
v_soma := (v_num1+v_num2);
DBMS_OUTPUT.PUT_LINE('O valor da soma de ' ||v_num1||' e '||v_num2||' é:'||v_soma);
END somando;
I believe a more recommended method would be to place these two procedures in a package and declare global variables in the header, but I'm not sure what your set up is like.
You can either change the procedure to a function that returns the value, or specify one of the arguments to the procedure to be IN OUT
I use the StrUtils in to split a string into a TStringDynArray, but the output was not as expected. I will try to explain the issue:
I have a string str: 'a'; 'b'; 'c'
Now I called StrUtils.SplitString(str, '; '); to split the string and I expected an array with three elements: 'a', 'b', 'c'
But what I got is an array with five elements: 'a', '', 'b', '', 'c'.
When I split with just ';' instead of '; ' I get three elements with a leading blank.
So why do I get empty strings in my first solution?
This function is designed not to merge consecutive separators. For instance, consider splitting the following string on commas:
foo,,bar
What would you expect SplitString('foo,,bar', ',') to return? Would you be looking for ('foo', 'bar') or should the answer be ('foo', '', 'bar')? It's not clear a priori which is right, and different use cases might want different output.
If your case, you specified two delimiters, ';' and ' '. This means that
'a'; 'b'
splits at ';' and again at ' '. Between those two delimiters there is nothing, and hence an empty string is returned in between 'a' and 'b'.
The Split method from the string helper introduced in XE3 has a TStringSplitOptions parameter. If you pass ExcludeEmpty for that parameter then consecutive separators are treated as a single separator. This program:
{$APPTYPE CONSOLE}
uses
System.SysUtils;
var
S: string;
begin
for S in '''a''; ''b''; ''c'''.Split([';', ' '], ExcludeEmpty) do begin
Writeln(S);
end;
end.
outputs:
'a'
'b'
'c'
But you do not have this available to you in XE2 so I think you are going to have to roll your own split function. Which might look like this:
function IsSeparator(const C: Char; const Separators: string): Boolean;
var
sep: Char;
begin
for sep in Separators do begin
if sep=C then begin
Result := True;
exit;
end;
end;
Result := False;
end;
function Split(const Str, Separators: string): TArray<string>;
var
CharIndex, ItemIndex: Integer;
len: Integer;
SeparatorCount: Integer;
Start: Integer;
begin
len := Length(Str);
if len=0 then begin
Result := nil;
exit;
end;
SeparatorCount := 0;
for CharIndex := 1 to len do begin
if IsSeparator(Str[CharIndex], Separators) then begin
inc(SeparatorCount);
end;
end;
SetLength(Result, SeparatorCount+1); // potentially an over-allocation
ItemIndex := 0;
Start := 1;
CharIndex := 1;
for CharIndex := 1 to len do begin
if IsSeparator(Str[CharIndex], Separators) then begin
if CharIndex>Start then begin
Result[ItemIndex] := Copy(Str, Start, CharIndex-Start);
inc(ItemIndex);
end;
Start := CharIndex+1;
end;
end;
if len>Start then begin
Result[ItemIndex] := Copy(Str, Start, len-Start+1);
inc(ItemIndex);
end;
SetLength(Result, ItemIndex);
end;
Of course, all of this assumes that you want a space to act as a separator. You've asked for that in the code, but perhaps you actually want just ; to act as a separator. In that case you probably want to pass ';' as the separator, and trim the strings that are returned.
SplitString is defined as
function SplitString(const S, Delimiters: string): TStringDynArray;
One would thought that Delimiters denote single delimiter string used for splitting string, but it actually denotes set of single characters used to split string. Each character in Delimiters string will be used as one of possible delimiters.
SplitString
Splits a string into different parts delimited by the specified
delimiter characters. SplitString splits a string into different parts
delimited by the specified delimiter characters. S is the string to be
split. Delimiters is a string containing the characters defined as
delimiters.
It is because the second parameter of SplitString is a list of single character delimiters, so '; ' means split at a ';' OR split at a ' '. So the string is split at every ';' and at every space, and between the ';' and the ' ' there is nothing, hence the empty strings.
So I have a CheckListBox with 6 Items :
Items.Strings = (
'Banana'
'Apple'
'Pomelo'
'Orange'
'Peach'
'BlueBarry')
If I want to show them then into a ShowMessage dialog, the message printed is.
'anana','pple','omelo','range','each','lueBarry'.
The procedure I use is this.
procedure TForm1.Button1Click(Sender: TObject);
var I : Integer;
begin
for I := 0 to CheckListBox1.Items.Count - 1 do
ShowMessage(CheckListBox1.Items.ValueFromIndex[I]);
end;
Why can't I get the first char from my Item?
Try to insert items in the right way like this
procedure TForm1.Button1Click(Sender: TObject);
begin
CheckListBox1.Items.Add('Banana');
CheckListBox1.Items.Add('Apple');
CheckListBox1.Items.Add('Pomelo');
CheckListBox1.Items.Add('Orange');
CheckListBox1.Items.Add('Peach');
CheckListBox1.Items.Add('BlueBarry');
end;
the result will be:
then...
procedure TForm1.Button2Click(Sender: TObject);
var I : Integer;
begin
for I := 0 to CheckListBox1.Items.Count - 1 do
ShowMessage(CheckListBox1.Items[I]);
end;
You cannot use ValueFromIndex for your porpouse.
TStrings.ValueFromIndex
Return the value part of a string based on it's index.
Declaration
public property TStrings.ValueFromIndex : string
read GetValueFromIndex
write SetValueFromIndex;
Description
ValueFromIndex returns the value part of a string based on the string index. The value part are all characters in the string after the NameValueSeparator character, or all characters if the NameValueSeparator character is not present.
TStrings.NameValueSeparator
Value of the character used to separate name,value pairs
Declaration
public property TStrings.NameValueSeparator : Char
read FNameValueSeparator
write SetNameValueSeparator;
Description
NameValueSeparator is the character used to separate name,value pair. By default, this is the equal sign (=), resulting in Name=Value pairs.
It can be set to a colon for Name : Value pairs.
thake a look at the vcl source :O
function TStrings.GetValueFromIndex(Index: Integer): string;
begin
if Index >= 0 then
Result := Copy(Get(Index), Length(Names[Index]) + 2, MaxInt) else
Result := '';
end;
I have a text file that has, on any given row, data that are expressed both in text format and in numeric format. Something like this:
Dog 5 4 7
How do I write a file reading routine in Delphi that reads this row and assigns the read values into the correct variables ("Dog" into a string variable and "5", "4" and "7" into real or integer variables)?
You can use SplitString from StrUtils to split the string into pieces. And then use StrToInt to convert to integer.
uses
StrUtils;
....
var
Fields: TStringDynArray;
....
Fields := SplitString(Row, ' ');
StrVar := Fields[0];
IntVar1 := StrToInt(Fields[1]);
IntVar2 := StrToInt(Fields[2]);
IntVar3 := StrToInt(Fields[3]);
And obviously substitute StrToFloat if you have floating point values.
Take TJclStringList from Jedi Code Library.
On 1st step you take one list and do .LoadFromFile to split the file to rows.
On second step you iterated through those rows and set secondary stringlist by those lines with space as delimiter. Then you iterate through secondary string list and do what u want.
http://wiki.delphi-jedi.org/wiki/JCL_Help:IJclStringList.Split#string#string#Boolean
Split a string into an array of strings based on a delimiter
https://stackoverflow.com/search?q=%5Bdelphi%5D+string+split
Like that
var slF, slR: IJclStringList; ai: TList<integer>; s: string; i: integer;
action: procedure(const Name: string; Const Data: array of integer);
slF := TJclStringList.Create; slF.LoadFromFile('some.txt');
slR := TJclStringList.Create;
for s in slF do begin
slR.Split(s, ' ', true);
ai := TList<Integer>.Create;
try
for i := 1 to slR.Count - 1 do
ai.Add(StrToInt(slR[i]));
action(slR[0], ai.ToArray);
finally ai.Free; end;
end;
You can use File of TRecord, with TRecord. For example:
type TRecord = packed record
FName : String[30];
Val1: Integer;
Val2: Integer;
Val3: Integer;
end;
And simple procedure:
procedure TMainForm.Button1Click(Sender: TObject);
var
F: file of TRecord;
Rec : TRecord;
begin
AssignFile(F, 'file.dat');
try
Reset(F);
Read(F, Rec);
finally
CloseFile(F);
end;
end;
Is there a standard way to convert between TVarRec and Variant values?
I want to parse an 'array of const' and use the values to populate parameters in a TMSQuery. To do this I'm using a list of column names (generated from TMSQuery.KeyFields), and matching the values in the array with the column names in KeyFields (by position), then using the column name to set the corresponding parameter using ParamByName.
The code below is what I've come up with, but VarRecToVariant doesn't seem very elegant. Is there a better solution?
keyFields: TStringList;
// List of table column names (keyFields.DelimitedText := query.KeyFields;)
// e.g. Name, Age
query: TMSQuery;
// Parametrized query with a parameter for each field in keyFields
// SELECT * FROM People WHERE Age=:Age AND Name=:Name
// If keyValues is ['Bob', 42] the resulting query should be
// SELECT * FROM People WHERE Age=42 AND Name='Bob'
procedure Read(keyValues: array of const);
var
i: Integer;
name: string;
value: Variant;
begin
...
for i := 0 to keyFields.Count - 1 do
begin
name := keyFields[i];
value := VarRecToVariant(keyValues[i]);
query.ParamByName(name).Value := value;
end;
query.Open
...
end;
function VarRecToVariant(varRec: TVarRec): Variant;
begin
case varRec.VType of
vtInteger: result := varRec.VInteger;
vtBoolean: result := varRec.VBoolean;
vtChar: result := varRec.VChar;
vtExtended: result := varRec.VExtended^;
vtString: result := varRec.VString^;
...
end;
end;
Notes:
The values in the array of const depend on the parameters in the query. The caller knows what these are, but the method that uses the array doesn't know how many or what type to expect. I.e. I can't change the method to Read(name: string; age: integer).
The parameters are not necessarily used in the same order that the values are specified in the array of const. In the example, keyFields are specified as "Name,Age" but the query uses Age before Name. This means Params[i].Value := keyValues[i] won't work. I think VarRecToVariant would still be needed anyway, which I'm trying to avoid).
Replace
procedure Read(keyValues: array of const);
with
procedure Read(keyValues: array of Variant);
Then you will not need to convert TVarRec to Variant.