Oracle SQL procedure to check input length - sql

I am trying to check the input length, to see if it's less than 7. It should show an error message, but the code below doesn't work. What's wrong with it?
CREATE OR REPLACE PROCEDURE prc_staffContact(IN_staffID IN CHAR, IN_staffContact IN VARCHAR) IS
v_staffName VARCHAR(50);
v_staffID CHAR(6);
v_staffContact VARCHAR(11);
BEGIN
SELECT s.staffName, s.staffID, s.staffContact
INTO v_staffName, v_staffID, v_staffContact
FROM staff s
WHERE staffID = IN_staffID;
IF (LENGTH(IN_staffContact) < 7 )
THEN
DBMS_OUTPUT.PUT_LINE('Error. Contact number at least 7 digits.');
ELSE
UPDATE staff
SET staffContact = IN_staffContact
WHERE staffID = IN_staffID;
DBMS_OUTPUT.PUT_LINE('=============================================================================');
DBMS_OUTPUT.PUT_LINE('The contact number of [ ' ||v_staffName || ' ] has been updated successfully.');
DBMS_OUTPUT.PUT_LINE('New contact number: [ ' ||v_staffContact || ' ].');
DBMS_OUTPUT.PUT_LINE('=============================================================================');
END IF;
END;
/

You said something that appears to be contradictory:
it shows [PL/SQL procedure successfully completed.] but doesn't checking even my input is less than 7 characters. and the data doesn't update also.
You described that you're doing as:
i save it under procedure1.sql and i start it in sql plus. that is my 1st call. after that i call the exec prc_staffContact('100001', '0000')
Together those suggest that when you say the data isn't updated, what you really mean is you don't get the contact number/new contact number message from the else branch, and I think you're assuming that means the update doesn't happen either, so it didn't execute either branch. But you must have gone into either the if or the else, by definition.
So if you didn't get either message, then you haven't done:
set serveroutput on
in SQL*Plus before calling exec. That setting is off by default, unless you have it turned on in your login.sql or glogin.sql, so you have to turn it on explicitly if you want to see dbms_output messages.
In this case, for the validation, (a) you probably want the select inside the elsef` too, partly because (b) if the passed values doesn't exist you'll get a no_data_found exception, and (c) you might want to consider throwing an exception if the length is less than 7 rather than (only) displaying a message. Someone else calling this might not have serverout on either, or might be using a different client that doesn't have that option.
You've also got v_staffID defined as char(6). Apart from wondering why that isn't a varchar2, the length you've given it means that if IN_staffID is 7 chars or more, the select into will get a 'character string buffer too small' error. I'd declare that as:
v_staffID staff.staffID%TYPE;
... to avoid issues like that, and the same for the other fields that relate to table columns.
And your 'success' message is showing the old contact number, not the new one. Not sure you need v_staffContact at all.

Look at your code carefully. May be your variable types not compatible or stored procedure parameters not compatible with other variables (or table columns, column's types). I tested your code in my database, all success. but may be error not data found in your select statement, or may be buffer too small error. Hope this help you. thanks

Related

TADOQuery - Edit mode inserts new record rather than editing

I'm puzzled at the behavior of the TADOQuery, let's just call Q. When I use Q.Edit, populate some fields, then Post, it ends up actually inserting a new record.
The code is simple, and reading the ID from an object:
Q.SQL.Text := 'select * from SomeTable where ID = :id';
Q.Parameters.ParamValues['id'] := MyObject.ID;
Q.Open;
try
Q.Edit;
try
Q['SomeField']:= MyObject.SomeField;
finally
Q.Post;
end;
finally
Q.Close;
end;
To my surprise, rather than updating the intended record, it decided to insert a new record. Stepping through the code, immediately after Q.Edit, the query is actually in Insert mode.
What could I be doing wrong here?
I think the comments that this behaviour is documented are off the point. What the docs don't make clear (possibly because the point never occurred to the author) is that this behaviour is not guaranteed to be deterministic.
The innards of TDataSet.Edit have scarcely changed in decades. Here is the Seattle version:
procedure TDataSet.Edit;
begin
if not (State in [dsEdit, dsInsert]) then
if FRecordCount = 0 then Insert else
begin
CheckBrowseMode;
CheckCanModify;
DoBeforeEdit;
CheckParentState;
CheckOperation(InternalEdit, FOnEditError);
GetCalcFields(ActiveBuffer);
SetState(dsEdit);
DataEvent(deRecordChange, 0);
DoAfterEdit;
end;
end;
Now, notice that the if .. then .. is predicated on the value of FRecordCount, which at various points in the TDataSet code is forced to have a given assumed value (variously 1, 0 or something else) by code such as in SetBufferCount and that behaviour isn't documented at all. So on reflection I think Jerry was probably right to expect that attempting to edit a non-existent record should be treated as an error condition, and not be fudged around by silently calling Insert whether or not it is documented.
I'm posting both a question and an answer, because the cause of the problem was totally unexpected behavior, and surely someone else had the same bewildering thing happen.
This happens in the event that the dataset you're trying to edit doesn't have any records. Personally, I would think it should produce an exception that you can't edit when there's no records. But the TADOQuery decides to append a new record instead.
The very root cause of this issue was that the object where I supplied the ID actually had a value of 0, and therefore since there's no record in the database with ID 0, it returned nothing.

Can you print variables that are dynamically generated?

I am trying to setup a program that takes user input for database ddl generation. I have it working to the point where it can ask a user for the name of the table, the number of columns and any attributes that might be needed. The problem comes when I try to print a string that includes the variables used for the column names. Due to trying to let users have as many columns as they want I used variables similar to this newvar(number that increases every time you enter a column name). This works fine and I can get the values if i do send %newvar1% but it doesn't work to do send newvar%increasing number%. I need to know if this is possible or if I'm just missing something obvious. Also I don't have the code with me but I can post it once I get back to my main computer.
I have tried quite a few things like, send %newvar%%number%, send newvar%number%, othervar = newvar%number% send %othervar%.
I'll show some once I have access to it in about 2 hours.
I expect to be able to output names for increasing variables using an ever increasing number. Class is starting I'll clarify some things later.
You can use a lone percent % beginning the first argument for the send command to achieve what you want. This will make everything after it to be evaluated (up to the next comma). Here is an example:
f1::
newvar1 := "This " , newvar2 := "is just a " , newvar3 := "test."
Loop , 3
Send , % newvar%A_Index%
Return
See: https://www.autohotkey.com/docs/Language.htm#-expression

Output user input in PL/SQL using SQL developer

Hi everyone I'm new to PL/SQL ,however I'm wrting a small code that a prompt a user to input a 2 numbers and display the numbers using DBMS_output.Put_line .
but I get a compilation error ,below is my code ,I'm using "Oracle SQL developer"
SET SERVEROUTPUT ON SIZE 1000000;
DECLARE
n_no_1 number(8,2);
n_no_2 number(8,2);
BEGIN
DBMS_output.put_line('Enter Value for no 1');
&n_no_1;
DBMS_output.put_line('Enter value for no 2');
&n_no_2;
DBMS_OUTPUT.PUT_LINE('The value of No 1 is' || n_no_1 );
DBMS_OUTPUT.PUT_LINE('The value of No 2 is' || n_no_2 );
END;
/
These 2 lines are your problem, however, not for the reasons mentioned in other answer:
&n_no_1;
&n_no_2;
In SQL, you can use the ampersand (&) to trigger something called "Macro substitution".
When the compiler comes across something like this (ie &n_no1), it prompts the user to input a value for it to substitute in it's place.
So if you enter "Hello". Your code becomes:
DBMS_output.put_line('Enter Value for no 1');
Hello;
And as you can see, that would fail, if you had just typed that out.
What you want to do is to assign that value to a variable, like this:
n_no_1 := '&n_no_1';
That gets "replaced" by this:
n_no_1 := 'Hello';
which compiles - and runs - just fine.
That all said, this is NOT the best way to do this, although this appears to be a learning excercise ?
Look up the PROMPT and ACCEPT keywords .. you can use those in SQL (outside your BEGIN / DECLARE / END block) to capture the values first in a neater fashion :)
http://docs.oracle.com/cd/E11882_01/server.112/e16604/ch_twelve032.htm#SQPUG052
http://docs.oracle.com/cd/E11882_01/server.112/e16604/ch_twelve005.htm#SQPUG026
Found an additional link here worth a good read. Explains a lot more than what you're looking at, but it discusses substitution variables, and other similiar things (and some other unrelated things :) )
https://blogs.oracle.com/opal/entry/sqlplus_101_substitution_varia
The &variable syntax is not PL/SQL: it is part of SQL Developer. I see what you are trying to do and syntax errors, but there's no point in correcting them because it's not going to work in the end.
The reason is: you cannot accept user input via PL/SQL at runtime.

Call a stored procedure, into another stored procedure

I have a procedure, that insert a line into one of my table.
After the INSERT in the procedure, I want to find all the lines into another table, and then, call the insert procedure of the second table.
So I have all the first procedure that works fine
P_INSERT_TABLE1
INSERT INTO TABLE1
...
COMMIT;
FOR record_po IN (SELECT C3, ...
FROM T_TABLE2
WHERE id = v_id)
LOOP
P_INSERT_TABLE2(record_po.C3, ...);
END LOOP;
All "in parameters" for P_INSERT_TABLE2 are VARCHAR2, so I make a "to_char" for each column are not varchar2 :
P_INSERT_TABLE2(pi_id,
record_po.C3,
record_po.C4,
record_po.C5,
record_po.C6,
record_po.C7,
to_char(record_po.C8, 'DD/MM/YYYY');
Here, pi_id, is one of the in parameters of P_INSERT_TABLE1, in VARCHAR2.
So now, I have this error message :
Erreur(357,1): PLS-00306: number or args types wrong in the call of P_INSERT_TABLE2
I don't understand, why P_INSERT_TABLE2 don't accept parameters, while there are all the good types in the good order?
If I call the procedure like "call P_INSERT_TABLE2(...)" I have an error like :
Erreur(357,9): PLS-00103: Symbol "P_INSERT_TABLE2" instead one of this symbols : := . ( # % ; immediate Symbole ":="
create or replace
PROCEDURE P_INSERT_TABLE2 (
pi_id IN VARCHAR2
,pi_C3 IN VARCHAR2
,pi_C4 IN VARCHAR2
,pi_C5 IN VARCHAR2
,pi_C6 IN VARCHAR2
,pi_C7 IN VARCHAR2
,pi_C8 IN VARCHAR2
,pmessage OUT NOCOPY VARCHAR2
)
Thanks for helping.
The declaration of P_INSERT_TABLE2 is invalid. You can't have 5 input parameters all named pi_C4. Since you're not getting a compilation error creating that procedure, I'll guess that this was a bug that was introduced posting the question here rather than something that is actually in the code.
According to the declaration of P_INSERT_TABLE2, the procedure takes 7 input parameters and one output parameter. In the code you posted, you appear to be passing in 7 input parameters but you are not passing in a variable for the output parameter. It appears that you need something like
P_INSERT_TABLE2(pi_id,
record_po.C3,
record_po.C4,
record_po.C5,
record_po.C6,
record_po.C7,
to_char(record_po.C8, 'DD/MM/YYYY'),
<<some local variable for the output parameter>> );
Beyond the syntax errors, I am extremely dubious when I see someone taking a perfectly good DATE, casting it to a string, and then passing that to a procedure. That implies either that P_INSERT_TABLE2 is going to turn around and convert the string back to a date, which means that you're doing extra work and have introduced additional points where the conversions can fail, or that you are going to write the string representation of a date to a table. Neither of these implications are good.
I am also highly dubious of any procedure that has an OUT parameter named pMessage. That tends to imply that you're not using exceptions properly and that you're passing an error message back rather than throwing an exception if your code encounters an error. That virtually always leads to much more brittle code that is much more difficult to debug than when you use proper exceptions.

Selecting large sequence value into global variable not working in PL/SQL code

I have a script where I would like to have a global variable to store a transaction number for later use. The code is working fine on one schema where the sequence value that I'm fetching is relatively low. It is not working on another schema with a higher sequence value where I get a "Numeric Overflow". If I change that sequence value to a lower number it is working as well but that is not an option.
VAR TRANSACTIONNR NUMBER;
BEGIN
--Works with NEXTVAL being around 946713241
--Doesn't work with NEXTVAL being around 2961725541
SELECT MY_SEQUENCE.NEXTVAL INTO :TRANSACTIONNR FROM DUAL;
MY_PACKAGE.STARTTRANSACTION(:TRANSACTIONNR);
END;
/
-- SQL Statements
BEGIN
MY_PACKAGE.ENDTRANSACTION;
MY_PACKAGE.DO_SOMETHING(:TRANSACTIONNR);
END;
/
What is also working is selecting the sequence into a variable declared in the DECLARE block:
DECLARE
TRANSACTIONNR NUMBER;
BEGIN
SELECT MY_SEQUENCE.NEXTVAL INTO TRANSACTIONNR FROM DUAL;
MY_PACKAGE.STARTTRANSACTION(TRANSACTIONNR);
END;
/
But that means I won't be able to reuse it in the block at the end. Setting the size of the number is not possible.
VAR TRANSACTIONNR NUMBER(15)
is not valid.
Any ideas what I could try or other ways to store global state?
On further investigation this looks like it might be a SQL Developer bug (making assumptions about what you're doing again, of course...). I can get the same error with:
VAR TRANSACTIONNR NUMBER;
BEGIN
SELECT 2961725541 INTO :TRANSACTIONNR FROM DUAL;
END;
/
It appears that SQL Developer's NUMBER is limited to 2^31, which isn't the case normally for Oracle.
A possibly workaround is to use BINARY_FLOAT to store the value, but you'll run into precision problems eventually (not sure where, but looks OK up to 2^53-ish), and you'll need to cast() it back to NUMBER when using it.
VAR TRANSACTIONNR BINARY_DOUBLE;
BEGIN
SELECT 2961725541 INTO :TRANSACTIONNR FROM DUAL;
-- dbms_output.put_line(cast(:TRANSACTIONNR as NUMBER)); -- null for some reason
END;
/
...
BEGIN
dbms_output.put_line(cast(:TRANSACTIONNR as NUMBER));
END;
/
For some reason I don't seem to be able to refer to the bind variable again in the anonymous block I set it in - it's null in the commented-out code - which seems to be another SQL Developer quirk, regardless of the var type; but as you were doing so in your code, I may again have assumed too much...
Original answer for posterity, as it may still be relevant in other circumstances...
Presumably you're doing something to end the current transaction, e.g. a commit in endtransaction; otherwise you could just refer to my_sequence.currval in the do_something call. A number variable is fine for this size of number though, it won't have a problem with a sequence that size, and it won't make any difference that it is from a sequence rather than manually assigned. I don't think the problem is with the storage or the sequence.
It seems rather more likely that the error is coming from one of the package procedures you're calling, though I can't quite imagine what you might be doing with it; something like this will cause the same error though:
create sequence my_sequence start with 2961725541;
create package my_package as
procedure starttransaction(v_num number);
procedure endtransaction;
procedure do_something(v_num number);
end my_package;
/
create package body my_package as
procedure starttransaction(v_num number) is
begin
dbms_output.put_line('starttransaction(): ' || v_num);
for i in 1..v_num loop
null;
end loop;
end starttransaction;
procedure endtransaction is
begin
dbms_output.put_line('endtransaction()');
end endtransaction;
procedure do_something(v_num number) is
begin
dbms_output.put_line('do_something(): ' || v_num);
end do_something;
end my_package;
/
When your code is run against that it throws your error:
BEGIN
*
ERROR at line 1:
ORA-01426: numeric overflow
ORA-06512: at "STACKOVERFLOW.MY_PACKAGE", line 6
ORA-06512: at line 5
endtransaction()
do_something():
Note the error is reported against line 6 in the package, which is the for ... loop line, not from the assignment in your anonymous block.
Looping like that would be an odd thing to do of course, but there are probably other ways to generate that error. The breakpoint for it working is if the nextval is above 2^31. If I start the sequence with 2147483647 it works, with 2147483648 it errors.
I'm assuming you are actually getting an ORA-01426 from the original question; if it's actually a ORA-1438 or ORA-06502 then it's easier to reproduce, by trying to assign the value to a number(9) column or variable. 'Numeric overflow' is pretty specific though.