PL/SQL anonymous block to test a procedure - stored-procedures

pL/SQL PROCEDURE
This is the code for a procedure that takes the term,lineno,component name,student id,score as input and processes the student score.
The procedure should add the score into the scores table if there are no exceptions.
CODE
CREATE OR REPLACE PROCEDURE score_details
(aterm IN scores.Term%type,
alineno IN scores.Lineno%type,
acompname IN scores.Compname%type,
asid IN scores.sid%type,
apoints IN scores.points%type)
AS sterm scores.Term%type;
slineno scores.Lineno%type;
scompname scores.compname%type;
ssid scores.sid%type;
spoints scores.points%type;
BEGIN
SELECT term,lineno,compname,sid,points
INTO sterm,slineno,scompname,ssid,spoints
FROM scores
WHERE aterm=term AND alineno=Lineno AND acompname=compname AND asid=sid AND apoints=points;
EXCEPTION
when no_data_found THEN
dbms_output.put_line('Invalid details');
ANONYMOUS BLOCK
The below is the code for anonymous block to test the above procedure.
I'm not able to get the correct result.Please help me with the code.
ACCEPT prompt 'pterm','plineno','pcompname','psid','ppoints'
DECLARE pterm scores.Term%type;
plineno scores.Lineno%type;
pcompname scores.Compname%type;
psid scores.sid%type;
ppoints scores.points%type;
BEGIN
score_details(pterm,plineno,pcompname,psid,ppoints);
END

I'm going to assume that you've intentionally missed out most of the body of your stored procedure, as it fetches some values out of a table into some local variables and then does nothing with these values.
I'm guessing that you're wondering why the values you entered in the ACCEPT line didn't make it into the stored procedure call.
ACCEPT is a SQL*Plus statement that you can use to set substitution variables. The following example creates a substitution variable named colour and displays the result. The line Yellow was typed in by me:
SQL> ACCEPT &colour
Yellow
SQL> PROMPT Your favourite colour is &colour
Your favourite colour is Yellow
You can also provide a prompt to the user to clarify what you're asking for:
SQL> ACCEPT colour PROMPT 'Enter a colour > '
Enter a colour > Yellow
and you can also use substitution variables in SQL:
SQL> select '&colour' from dual;
old 1: select '&colour' from dual
new 1: select 'Yellow' from dual
'YELLO
------
Yellow
It seems there's two things not quite right with your PL/SQL block at the moment:
Your ACCEPT statement isn't working. You haven't quite got the order of the parts of it right (if used, PROMPT must come after the variable name). Also, I don't think you can set more than one substitution variable in a single ACCEPT.
You're not using the substitution variables that contain the values entered.
I'd therefore imagine that you'd want the call to your PL/SQL block to look a bit more like the following:
ACCEPT pterm PROMPT 'Enter a term > '
ACCEPT plineno PROMPT 'Enter a line number > '
-- and similarly for the others.
DECLARE
pterm scores.Term%type := '&pterm';
plineno scores.Lineno%type := '&plineno';
pcompname scores.Compname%type := '&pcompname';
psid scores.sid%type := '&psid';
ppoints scores.points%type := '&ppoints';
BEGIN
score_details(pterm,plineno,pcompname,psid,ppoints);
END;

Related

DB2 LUW - Get Error Line in Stored Procedure

I'm trying to determine the line in a stored procedure or the last SQL-statement which is causing an error. As a workaround I'm using temporary variables which I manually set to determine in which part of my stored procedure an error occurs.
See the following:
-- Create an ErrorLog table
Create Table SCHEMA.ErrorLog_lrc_test
(
ErrSQLCODE Integer ,
Codepart Char(1),
Type Char(1) ,
MsgText VarChar(1024));
CREATE OR REPLACE PROCEDURE SCHEMA.test_error(IN divisor INT)
LANGUAGE SQL
BEGIN
-- Define variables
DECLARE codepart_var Char(1);
DECLARE test_INT INT;
-- Define sqlcode
DECLARE SQLCODE INTEGER;
--Define Error-Handler
DECLARE CONTINUE HANDLER FOR SQLEXCEPTION
BEGIN
INSERT INTO SCHEMA.ErrorLog_lrc_test(ErrSQLCODE, Codepart, TYPE, MsgText)
VALUES(SQLCODE, codepart_var, 'E', SYSPROC.SQLERRM(SQLCODE));
END;
--Define Warning-Handler
DECLARE CONTINUE HANDLER FOR SQLWARNING, NOT FOUND
BEGIN
INSERT INTO SCHEMA.ErrorLog_lrc_test(ErrSQLCODE, Codepart, TYPE, MsgText)
VALUES(SQLCODE, codepart_var, 'W', SYSPROC.SQLERRM(SQLCODE));
END;
-- Set temporary variable to 'a' to get part of code where error occured
SET codepart_var = 'a';
-- Create Error
sELECT 1/divisor into test_INT
FROM SYSIBM.SYSDUMMY1;
SET codepart_var = 'b';
-- Create Error
sELECT 1/divisor into test_INT
FROM SYSIBM.SYSDUMMY1;
SET codepart_var = 'c';
-- Create Not Found (Sqlcode 100)
INSERT INTO SCHEMA.ErrorLog_lrc_test
SELECT NULL, NULL, NULL, NULL FROM "SYSIBM".SYSDUMMY1
WHERE 1 = 0 ;
END
call SCHEMA.test_error(0);
SELECT *
FROM SCHEMA.ErrorLog_lrc_test;
I get the following:
ERRSQLCODE
CODEPART
TYPE
MSGTEXT
-801
a
E
SQL0801N Division by zero was attempted.
-801
b
E
SQL0801N Division by zero was attempted.
100
c
W
SQL0100W No row was found for FETCH, UPDATE or DELETE; or the result of a query is an empty table.
So I am able to get the part of the code where an error or warning occurs, but it would be better to get the line or the SQL statement as I don't want to specify every part of the code with a temporary variable.
I already found this SQLCA --> sqlerrd(3): "...If an error is encountered during the compilation of an SQL routine, trigger, or dynamic compound SQL (inlined or compiled) statement, sqlerrd(3) contains the line number where the error was encountered". For now I didn't manage to make use of SQLCA variables. I don't know how to implement them in DB2 LUW in a stored procedure.
Is there another/better way to log the specific line or SQL-statement in a stored procedure which is causing an error?
My DB2 version is 10.5.0.
Thank you!
If your Db2-server platform is Linux/Unix/Windows, and you are using a recent version, then consider using DBMS_UTILITY.FORMAT_ERROR_BACKTRACE which may help you.
Documentation here. The documentation includes a worked example.
When using this for stored procedures or routines, it is wise to always create those routines with a meaningful specific name with the SPECIFIC clause on the create or replace statement. Otherwise the routine will have a system generated name which will not be meaningful to users when it appears in the output of DBMS_UTILITY.FORMAT_ERROR_BACKTRACE. There are other reasons you should always use a specific name for your routines.
The SQLCA is for calling programs (i.e. the program that calls the stored procedure).

Graphing empty input boxes using TChart

I'm getting the user to enter coefficients (as strings) for terms from a constant (e.g. 2) all the way up to a sextic (e.g. 3X^6).
The user enters each coefficient without the X-term attached to it, so 3X^6 is entered just as 3.
Upon clicking an okay button, the idea is to transfer the coefficients which are valid (checked using a RegEx expression) to a procedure ('CreateGraph') which actually creates the graph using TChart.
Upon clicking the okay button, a loop for i = 1 to 7 (number of coefficients) is used to check if each term is valid. The issue I am having is that I get a string conversion error when converting the coefficients in the CreateGraph procedure as the other coefficients except those with data in, are set to '', which TChart won't accept.
Here's the current procedure:
procedure TfrmGetFunction.btnAddFunctionClick(Sender: TObject);
var
i : integer; // Loop counter.
begin
for i := 1 to 7 do
begin
if CheckCoefficientBoxesValidInput(CoefficientEdit[i].CoEditBox) then
frmGraphingMode.CreateGraph(CoefficientEdit[i].CoEditBox);
end;
end;
Is the best way to just set the inactive coefficient edit boxes to '0'?
The (potential) issue I have with that is that when the user wants to enter another set of coefficients or comes back to the 'Enter Function' form, the values are all set to 0 which may be confusing.
The next issue (related to the first) that I am having, is that the CreateGraph procedure is called each time i is incremented, which means that a load of empty input boxes are passed, which TChart doesn't like.
Here's my current procedure:
var
i : integer; // Loop counter.
Coeff : array[1..7] of string;
begin
for i := 1 to 7 do
begin
Coeff[i] := CoefficientEdit[i].CoEditBox.Text;
if Coeff[i] = '' then
Coeff[i] := '0';
frmGraphingMode.CreateGraph(Coeff[i]);
end;
end;
What is the easiest way to solve this problem? I was thinking of having a boolean variable which is set to true when the loop is complete (i.e. i = 7 is when all the empty (if so) boxes would be filled in); is there a better way?
Simply check for the empty string and replace with '0'.
var
Coeff: string;
....
Coeff := CoefficientEdit[i].CoEditBox.Text;
if Coeff = '' then
Coeff := '0';
I'm assuming that CoEditBox is an edit control. If so then I would comment that you should not pass an edit control to CheckCoefficientBoxesValidInput and CreateGraph. Those functions should receive string arguments. You are making them needlessly coupled to a particular GUI design.
Perhaps this is the root cause of your problems. You comment that it is confusing for the edit controls to be changed to contain zeros. Well, you don't need to, and should now, change the edit controls. Separate the GUI from the charting. Don't pass around edit controls. Pull the contents from the controls, optionally process it, and pass it on.
Finally, instead of using a regex to check if a value is a number, call TryStrToFloat.

ADOQuery1 parameter 'card' not found

i've used the parameter method but now i have a problem. i want to insert all my data inside the table. i need to insert 2 table at once. so heres my full coding. need help. why it says like that?
ADOQuery1.Close();
ADOQuery1.SQL.Clear;
ADOQuery1.SQL.Add('INSERT INTO STUDENT (CARD_ID,NAMA,MATRIC_ID,SUBJEK,KURSUS,FAKULTI,Seksyen,TAHUN) VALUES ');
ADOQuery1.SQL.Add('(card,nama,matric,subjek,kursus,fakulti,seksyen,tahun)');
ADOQuery1.SQL.Add('INSERT INTO subjek2 (CARD_ID, MATRIC_ID,NAMA,SUBJEK) VALUES');
ADOQuery1.SQL.Add('(card,matric,nama,subjek)');
ADOQuery1.Parameters.ParamByName('card').Value:= card1.Text;
ADOQuery1.Parameters.ParamByName('nama').Value:= Edit1.Text;
ADOQuery1.Parameters.ParamByName('matric').Value:= Edit2.Text;
ADOQuery1.Parameters.ParamByName('kursus').Value:= Edit3.Text;
ADOQuery1.Parameters.ParamByName('fakulti').Value:= Edit4.Text;
ADOQuery1.Parameters.ParamByName('seksyen').Value:= ComboBox1.Text;
ADOQuery1.Parameters.ParamByName('tahun').Value:= Edit5.Text;
ADOQuery1.Open();
There are some issues in your code.
SQL-Statement
If you want to execute more than one statement, then you have to use the statement delimiter (most times ;). You missed that in your statements.
INSERT INTO STUDENT (CARD_ID,NAMA,MATRIC_ID,SUBJEK,KURSUS,FAKULTI,Seksyen,TAHUN) VALUES
(card,nama,matric,subjek,kursus,fakulti,seksyen,tahun); -- missed ;
INSERT INTO subjek2 (CARD_ID, MATRIC_ID,NAMA,SUBJEK) VALUES
(card,matric,nama,subjek); -- optional on last statement
Parameters
Parameters in SQL-Statements must start with : otherwise they were treated as normal fields
INSERT INTO STUDENT (CARD_ID,NAMA,MATRIC_ID,SUBJEK,KURSUS,FAKULTI,Seksyen,TAHUN) VALUES
(:card,:nama,:matric,:subjek,:kursus,:fakulti,:seksyen,:tahun);
INSERT INTO subjek2 (CARD_ID, MATRIC_ID,NAMA,SUBJEK) VALUES
(:card,:matric,:nama,:subjek);
BTW: You did not provide any data to the parameter subjek in your code.
Executing Statement
Some statements return a cursor to data (SELECT) others do not (INSERT,DELETE,...).
If you are executing a statement, that did not return a cursor then you must not use Open. Instead you have to ExecSQL.
Mutiple Statements / Access / TADOQuery
You simply can not execute multiple statements using TADOQuery and Access. You have to execute the statements separately.
If you want to achieve, that all data is written or if any error occurs no data is written, then you have to start a transaction before the statements and you can commit or rollback.
Following all the advices you come to the following code (without transaction)
ADOQuery1.Close();
ADOQuery1.SQL.Clear;
ADOQuery1.SQL.Add('INSERT INTO STUDENT (CARD_ID,NAMA,MATRIC_ID,SUBJEK,KURSUS,FAKULTI,Seksyen,TAHUN) VALUES ');
ADOQuery1.SQL.Add('(:card,:nama,:matric,:subjek,:kursus,:fakulti,:seksyen,:tahun)');
ADOQuery1.Parameters.ParamByName('card').Value:= card1.Text;
ADOQuery1.Parameters.ParamByName('nama').Value:= Edit1.Text;
ADOQuery1.Parameters.ParamByName('matric').Value:= Edit2.Text;
ADOQuery1.Parameters.ParamByName('subjek').Value:= '????'; // I don't know what
ADOQuery1.Parameters.ParamByName('kursus').Value:= Edit3.Text;
ADOQuery1.Parameters.ParamByName('fakulti').Value:= Edit4.Text;
ADOQuery1.Parameters.ParamByName('seksyen').Value:= ComboBox1.Text;
ADOQuery1.Parameters.ParamByName('tahun').Value:= Edit5.Text;
ADOQuery1.ExecSQL;
ADOQuery1.SQL.Clear;
ADOQuery1.SQL.Add('INSERT INTO subjek2 (CARD_ID, MATRIC_ID,NAMA,SUBJEK) VALUES');
ADOQuery1.SQL.Add('(:card,:matric,:nama,:subjek)');
ADOQuery1.Parameters.ParamByName('card').Value:= card1.Text;
ADOQuery1.Parameters.ParamByName('nama').Value:= Edit1.Text;
ADOQuery1.Parameters.ParamByName('matric').Value:= Edit2.Text;
ADOQuery1.Parameters.ParamByName('subjek').Value:= '????'; // I don't know what
ADOQuery1.ExecSQL;
You've not provided the parameters correctly (as I showed you in my previous answer). Note the colon (:) before the name of each parameter:
ADOQuery1.SQL.Add('(:card, :matric, :nama, :subjek)');
Also note (from that same previous answer) that you do not use the colon when assigning values to the paramters:
ADOQuery1.Parameters.ParamByName('card').Value := card1.Text;
And, once again, use white space in your SQL!! You save one keystroke by not putting spaces in between the comma-separated things, and make it much more difficult to read and maintain later without them. It's much easier to read (:card, :matric, :nama, :subjek) than it is to read (:card,:matric,:nama,:subjek). Start learning to do things properly now and save yourself (and others) headaches later.

Difference of execution in SQL Developer and SQL *Plus

The following code runs good as expected in SQL Developer without any errors, but when I run the code in SQL*Plus it is throwing an error.
declare
v_num1 integer := '&a';
v_num2 integer := '&b';
v_result number;
begin
v_result := v_num1 / v_num2;
dbms_output.put_line ('v_result: '||v_result);
end;
The error raised is
ERROR at line 1:
ORA-06502: PL/SQL: numeric or value error: character to number conversion error
ORA-06512: at line 2
Please point out where the error I made.
Your SQL*Plus session has substitution variables turned off, so the '&a' is being treated as a literal string, and it isn't prompting you for the value. In a simplified version:
set define off
declare
i integer := to_number('&a');
begin
null;
end;
/
declare
*
ERROR at line 1:
ORA-06502: PL/SQL: numeric or value error: character to number conversion error
ORA-06512: at line 2
To fix it, turn substitution variables back on:
set define &
Then it works:
declare
i integer := to_number('&a');
begin
null;
end;
/
Enter value for a:
If I enter 1:
PL/SQL procedure successfully completed.
You don't need the quotes, or the to_number(), unless you're using group seperators (and then you'd need to supply a format model too). You can just do:
declare
i integer := &a;
begin
null;
end;
/
As you have it you're doing an unnecessary implicit string-to-number conversion.
If you haven't explicitly turned them off, then you probably have a site or user profile that is doing the set define off, among other configuration settings. it's probably doing that for a reason, but if you currently only have a site profile then you could create your own user profile to override that. Just be aware of the effect of anything you change; you may have other scripts that rely on it being off, e.g. if you have data that has ampersands and it's been disabled to prevent issues interpreting those.

Getting a Field List from a DBExpress TSQLQuery

I am having a problem getting a list of fields from a query defined at run time by the users of my program. I let my users enter a SQL query into a memo control and then I want to let them go through the fields that will return and do such things as format the output, sum column values and so forth. So, I have to get the column names so they have a place to enter the additional information.
I would do fine if there were no parameters, but I also have to let them define filter parameters for the query. So, if I want to set the parameters to null, I have to know what the parameter's datatype is.
I am using Delphi 2006. I connect to a Firebird 2.1 database using the DBExpress component TSQLConnection and TSQLQuery. Previously, I was successful using:
for i := 0 to Qry.Params.Count - 1 do Qry.Params[i].value := varNull;
I discovered I had a problem when I tried to use a date parameter. It was just a coincidence that all my parameters up until then had been integers (record IDs). It turns out that varNull is just an enumerated constant with a value of 1 so I was getting acceptable results (no records) was working okay.
I only need a list of the fields. Maybe I should just parse the SELECT clause of the SQL statement. I thought setting Qry.Prepared to True would get me a list of the fields but no such luck. It wants values for the parameters.
If you have an idea, I would sure like to hear it. Thanks for any help.
Replied again 'coz I'm interested. My methods works (with my queries) because they have been pre-defined with the params' datatypes preset to the correct type:)
I'm not sure how you are expecting the query to know or derive the datatype of the param given that you are not even selecting the field that it operates against.
So I think your query setup and user input method will need more attention. I've just looked up how I did this a while ago. I do not use a parameterised query - I just get the "parameter values" from the user and put them directly into the SQL. So your sql would then read:
SELECT s.hEmployee, e.sLastName
FROM PR_Paystub s
INNER JOIN PR_Employee e ON e.hKey = s.hEmployee
WHERE s.dtPaydate > '01/01/2008'
therefore no parameter type knowledge is necessary. Does not stop your users entering garbage but that goes back to input control :)
Although a slightly different dataset type this is what I use with TClientDataset simple and effective :)
for i := 0 to FilterDataSet.Params.Count -1 do
begin
Case FilterDataSet.Params.Items[i].Datatype of
ftString:
ftSmallint, ftInteger, ftWord:
ftFloat, ftCurrency, ftBCD:
ftDate:
ftTime:
ftDateTime:
.
.
.
end;
end;
can you not do something similar with the query?
You guys are making this way too hard:
for i := 0 to Qry.Params.Count - 1 do begin
Qry.Params[i].Clear;
Qry.Params[i].Bound := True;
end;
I'm not sure what version of Delphi you are using. In the Delphi 2006 help under Variant Types, it says:
Special conversion rules apply to the
Borland.Delphi.System.TDateTime type
declared in the System unit. When a
Borland.Delphi.System.TDateTime is
converted to any other type, it
treated as a normal Double. When an
integer, real, or Boolean is converted
to a Borland.Delphi.System.TDateTime,
it is first converted to a Double,
then read as a date-time value. When a
string is converted to a
Borland.Delphi.System.TDateTime, it is
interpreted as a date-time value using
the regional settings. When an
Unassigned value is converted to
Borland.Delphi.System.TDateTime, it is
treated like the real or integer value
0. Converting a Null value to Borland.Delphi.System.TDateTime raises
an exception.
The last sentence seems important to me. I would read that as varNull cannot be converted to a TDateTime to put into the field, and hence you get the exception that you're experiencing.
It also implies that this is the only special case.
Couldn't you do something like:
for i := 0 to Qry.Params.Count - 1 do
begin
if VarType(Qry.Params[i].value) and varTypeMask = varDate then
begin
Qry.Params[i].value := Now; //or whatever you choose as your default
end
else
begin
Qry.Params[i].value := varNull;
end;
end;
What I ended up doing was this:
sNull := 'NULL';
Qry.SQL.Add(sSQL);
for i := 0 to Qry.Params.Count - 1 do begin
sParamName := Qry.Params[i].Name;
sSQL := SearchAndReplace (sSQL, ':' + sParamName, sNull, DELIMITERS);
end;
I had to write SearchAndReplace but that was easy. Delimiters are just the characters that signal the end of a word.
TmpQuery.ParamByName('MyDateTimeParam').DataType := ftDate;
TmpQuery.ParamByName('MyDateTimeParam').Clear;
TmpQuery.ParamByName('MyDateTimeParam').Bound := True;

Resources