oracle stored procedure - local variable behaving mysteriously - sql

This is how I am delcaring the local variables:
team_counter number (38) := 0;
username varchar2(50) := '';
This is how I am trying to use/see their value after using some select into statement:
dbms_output.put_line(team_counter||'.'||username);
if team_counter< 30 AND username <>'' then
begin
dbms_output.put_line('yuhj');
end;
end if;
The second output is not being printed!
The first output is being printed as '1.tuser' which I was expecting.

This is because you're trying to compare a string with a 0 length string using an inequality operator.
Oracle assumes that 0 length strings are equivalent to NULL and will not evaluate comparisons that don't use the NULL specific conditional. To quote:
Oracle Database currently treats a character value with a length of
zero as null. However, this may not continue to be true in future
releases, and Oracle recommends that you do not treat empty strings
the same as nulls.
Simply put this means that your IF statement should be:
if team_counter < 30 and username is not null then
...
As an additional note there's no need for the begin ... end around the dbms_output.put_line. As you're not catching any exceptions explicitly related to this call or declaring additional variables etc there's no real need.

Related

reading null value in NUMBER variable using SELECT INTO query, but oracle is not considering it as null. what is the value present in the variable..?

Executing following block in Oracle Procedure
val NUMBER := 0;
BEGIN
SELECT VALIDITY / 24
INTO val
FROM validity_table
WHERE id = 123;
DBMS_OUTPUT.PUT_LINE('Validity : '||val);
IF(val = NULL)
THEN
val := 0;
DBMS_OUTPUT.PUT_LINE('Validity : '||val);
END IF;
EXCEPTION
WHEN NO_DATA_FOUND
THEN
NULL;
END;
Output:
Validity :
in the validity_table value of the validity is null for the id 123 but while executing it, control is not going inside the if block. output is printed only once with no value. want to know what value the 'val' variable is containing.
The problem is not Oracle's (along with all other RDBMS, and the SQL Standard) consideration of Nulls, but your misunderstanding of them. Nulls actually introduce 3-valued logic: the result of any compassion may be true, false, or null. If this particular case the result the comparison (val=Null) is Null; since null is not true your if condition is not satisfied and therefore not processed. See this fiddle for comparisons involving Null.
It's too bad Oracle has yet to see the benefits of boolean values in SQL. Guess they figure if it wasn't needed it 1979 why should it be needed in 2019 (after all how much can change in a mere 40 years)?

Why is Oracle not treating '' as NULL in a BETWEEN with NVL comparison

I have this query, not filling in any data as the variables. Expecting a row; I get nothing.
select 1 from dual
where SYSDATE BETWEEN NVL(:START_DATE, SYSDATE) AND NVL(:END_DATE, SYSDATE)
I try this, expecting a row, and I get a row.
select 1 from dual
where SYSDATE BETWEEN SYSDATE AND SYSDATE
Something is not working as expected. An NVL should treat '' as NULL.
I try this, expecting a row, and I get a row.
select 1 from dual
where '' is null
If '' IS NULL, which we have now proven, this should return a row. But it's not.
select 1 from dual
where SYSDATE BETWEEN NVL('', SYSDATE) AND NVL('', SYSDATE)
There are some interesting answers here, but I'm not sure anyone has yet explained quite why your query returns no rows. (Apologies if they have and I missed it - just ignore me then!)
It is because NVL('', SYSDATE) is returning a character string, since the first argument '' is a character strng. So NVL('', SYSDATE) is equivalent to NVL('', TO_CHAR(SYSDATE)). And since we haven't specified the format for the TO_CHAR, Oracle will use the default which usually doesn't include a time component.
So this:
where SYSDATE BETWEEN NVL(:START_DATE, SYSDATE) AND NVL(:END_DATE, SYSDATE)
Is being treated somethinlg like this:
where SYSDATE BETWEEN '25-APR-2020' AND '25-APR-2020'
Oracle will then convert those strings back into dates to perform the BETWEEN, so will assume 00:00:00 for the time of day. Therefore unless you run this at exactly midnight, it will not return a row.
However, if you set your default date format to include the time like this:
alter session set nls_date_format = 'DD-MON-YYYY HH24:MI:SS';
You will find it now works, because the time component of the date no longer gets lost in the implicit conversions.
Probably the best way to deal with this though is to ensure you are dealing with dates and not strings at all:
select 1 from dual
where SYSDATE BETWEEN NVL(TO_DATE(:START_DATE), SYSDATE)
AND NVL(TO_DATE(:END_DATE), SYSDATE)
It ain't what you don't know that'll kill you. It's "what you know" that just ain't so...
The reason that Oracle isn't treating '' as NULL is because '' is not a NULL - it's a zero-length string constant. Now, right here people are going to jump in and tell me I'm wrong, and that I don't know what I'm talking about, and question my parentage, and probably start a petition to deny my Social Security and kick me out of the Old Farts Home, but I'm right. Here's a demonstration:
DECLARE
vStr VARCHAR2(10) := '';
cStr CHAR(10) := '';
vNULL VARCHAR2(10) := NULL;
cNULL CHAR(10) := NULL;
nStr NUMBER := '';
nNULL NUMBER := NULL;
BEGIN
DBMS_OUTPUT.PUT_LINE('LENGTH(vStr) = ' || LENGTH(vStr));
DBMS_OUTPUT.PUT_LINE('LENGTH(cStr) = ' || LENGTH(cStr));
DBMS_OUTPUT.PUT_LINE('LENGTH(vNULL) = ' || LENGTH(vNULL));
DBMS_OUTPUT.PUT_LINE('LENGTH(cNULL) = ' || LENGTH(cNULL));
DBMS_OUTPUT.PUT_LINE('LENGTH(nStr) = ' || LENGTH(nStr));
DBMS_OUTPUT.PUT_LINE('LENGTH(nNULL) = ' || LENGTH(nNULL));
END;
Now without looking down below (yes, I know - it's hard. Try... :-) what output do you expect from the code above? If you're like most people (including me, until I tripped over this a few years ago) you'd expect it to be:
LENGTH(vStr) =
LENGTH(cStr) =
LENGTH(vNULL) =
LENGTH(cNULL) =
LENGTH(nStr) =
LENGTH(nNULL) =
That is, you'd expect the LENGTH function to return NULL when applied to all those variables - because they should all be NULL, right?
But that's not what you get (see this db<>fiddle). What you actually get is:
LENGTH(vStr) =
LENGTH(cStr) = 10
LENGTH(vNULL) =
LENGTH(cNULL) =
LENGTH(nStr) =
LENGTH(nNULL) =
Whoa! Wait!! What's that 10 doing in there?!?
Well, it's pretty simple. Think about the semantics of the CHAR data type in PL/SQL. If you assign a string to a CHAR variable or field, and the length of the string assigned is shorter than the defined length of the variable or field, the value assigned to the variable or field is padded on the right to the full defined width of the variable/field - in this case, 10 characters. So when a '' - that is, a zero-length string constant - is assigned to the variable cStr, the value assigned to the variable is padded on the right to the defined width of the variable, so cStr ends up filled with 10 blanks. But when a NULL is assigned to that same character variable it ends up being set to NULL, as expected, and the LENGTH function returns NULL, as expected.
The semantics of VARCHAR2 (and, for the moment, VARCHAR - at least until Oracle gets around to supporting ANSI semantics for VARCHAR - which they're going to do Real Soon Now (tm)) are different in Oracle. When a variable/field of type VARCHAR2 is assigned a value it doesn't do any padding; instead, it assigns only the significant characters of the source string to the variable or field, and if the resultant length of the string assigned to the variable is zero then the variable or field is set to NULL, in accordance with the Oracle rule that "zero-length string values are the same as NULL". But this happens at the time that the value is assigned to the variable. '' on its own is still a zero-length string constant.
Just remember - it ain't what you don't know that'll kill you. It's "what you know" that just ain't so... :-)
The issue is that some user interfaces (including, annoyingly, SQL*Plus and SQL Developer) don't support the DATE data type for bind variables.
This is a limitation of those user interfaces. Oracle SQL is perfectly capable of working with DATE bind variables, and if you are able to pass to it NULL of data type DATE, it will process that as expected. See an illustration below.
Some answers, posted already, deal with the secondary issue - if you pass in NULL of data type CHAR or VARCHAR2, what happens? I don't think understanding the precise reason is important; what is exceptionally important is to understand this limitation of front-end programs, and - if you are forced to use them to interact with the database - make sure your bind variables are string data type, and the query reflects that (by using TO_DATE with proper date format model).
If you are using PL/SQL, and/or perhaps other calling environments (not sure about ApEx for example, since I don't use it), you can pass in NULL of DATE data type directly, and get the expected behavior. Here is an illustration: I build your query as a dynamic query, with bind variables, and I invoke it and pass to it NULL of data type DATE. Look what happens:
declare
l_sql clob;
l_n number;
begin
l_sql := 'select 1
from dual
where sysdate between nvl(:start_date, sysdate)
and nvl(:end_date , sysdate)';
execute immediate l_sql into l_n using cast(null as date), cast(null as date);
dbms_output.put_line(l_n);
end;
/
1
PL/SQL procedure successfully completed.
You have to use like the below to match null with null to get a row
select 1 from dual
where nvl('',SYSDATE) BETWEEN NVL('', SYSDATE) AND NVL('', SYSDATE)

Randomly insert 1 of 3 declared variables

I have three variables that are declared and have an integer value assigned to them.
I am trying to randomly assign the integer value to a field in an UPDATE statement, but get an error.
This is statement I am trying to execute:
FOR user_record IN (SELECT * FROM users_to_add) LOOP
UPDATE
customer."user"
SET
primary_site_id = ({site_GRO, site_WHS, site_SHR}[])[ceil(random()*3)],
WHERE
userid = (SELECT userID FROM customer.user
WHERE emailaddress=user_record.email_address);
END LOOP;
I am getting:
SyntaxError: syntax error at or near "{"
This same format works if the value being randomly selected is a string but since these are variables, the inside curly brackets can't be enclosed in quotes.
Use an ARRAY constructor instead of the (invalid) array literal.
(ARRAY[site_GRO, site_WHS, site_SHR])[ceil(random()*3)]
However, a set-based solution is typically more efficient than looping:
UPDATE customer."user" u
SET primary_site_id = CASE trunc(random()*3)::int
WHEN 0 THEN site_gro -- your variables here
WHEN 1 THEN site_whs
WHEN 2 THEN site_shr
END
FROM users_to_add ua
WHERE u.userid = ua.email_address;
Should achieve the same. Works inside a PL/pgSQL block or as standalone SQL DML command (then you need to interpolate variable values yourself).
A single multi-row UPDATE is much cheaper than many updates in a loop.
trunc() is slightly more correct than ceil(), as random() returns a value in the domain [0,1) (1 excluded). It's also faster.
And a CASE construct is substantially faster than building an array just to extract a single element from it.
Asides:
Avoid reserved words like user as identifiers. Always requires double-quoting, and can lead to confusing errors when forgotten.
Also avoid random capitalization in identifiers. This goes for SQL as well as for PL/pgSQL. See:
Are PostgreSQL column names case-sensitive?
Perhaps you can try splitting the index and array out into their own vars?
FOR user_record IN (SELECT * FROM users_to_add) LOOP
a := ARRAY[site_GRO, site_WHS, site_SHR];
i := ceil(random()*3);
UPDATE
customer."user"
SET
primary_site_id = a[i]
WHERE
userid = (SELECT userID FROM customer.user WHERE emailaddress=user_record.email_address);
END LOOP;

Trying to use a where statement in cursor creation (PL/SQL)

I'm trying to create a block that accepts input from a prompt and uses that input to filter the result set for the cursor. Keep in mind I'm a novice here so I maybe making a very routine mistake, and thank you for your help. My current code is below.
Set serveroutput on
DECLARE
ACCEPT a PROMPT “Please Enter a Date, eg. Format - 01 or 30"
datev char
datev := &a;
CURSOR cur_day_cursor IS
SELECT Arrival_Date Adate
FROM FLIGHT
WHERE TO_CHAR(Arrival_Date, ‘DD’) = datev;
cur_day_cursor_var cur_day_cursor%ROWTYPE;
BEGIN
OPEN Cur_day_cursor;
LOOP
Fetch Cur_day_cursor
INTO cur_day_cursor_var;
EXIT WHEN cur_day_cursor%NOTFOUND;
DBMS_OUTPUT.PUT_LINE (cur_day_cursor_var.Adate);
END LOOP;
IF cur_day_cursor%ISOPEN THEN
CLOSE cur_day_cursor;
END IF;
END;
The where statement is causing my errors, so I was thinking that I may have to let the cursor collect all the data and then filter it when displaying, but I'm not sure if I can even do that.
The error I keep receiving ERROR at line 9:-
ORA-06550: line 9, column 1:
PLS-00103: Encountered the symbol "WHERE" when expecting one of the following:
begin function pragma procedure subtype type
current cursor delete
exists prior
I don't know exactly why Oracle is reporting the error at the WHERE. Sometimes the parser gets pretty confused by bad syntax and doesn't point to the real problem. You have several syntax errors before the cursor definition.
ACCEPT is a SQLPlus command, not a PL/SQL statement. Move your ACCEPT line above the DECLARE.
Also, your variable declaration and initialization are incorrect. The assignment should be part of the declaration line; you need to provide a length for the CHAR datatype; and the substitution value should be in quotes to be treated as a string. A valid version of your lines would be:
datev char(2) := '&a';
I ran the same query as above, and got the results perfectly fine.
You have few syntax as well as logical error which I corrected in your query. The syntax error(s) are -
datev char
datev := &a;
You can't do such an initialization in PL/SQL. You probably have to complete it in a single line like below -
datev char := &a;
The logical mistake(s) are -
Why use a CHAR variable to store data when you know that the value being returned is NUMBER.
You expect numbers from 1-31; then why do you choose the default size of char which as 1. It will fail if you provide a 2-digit number
Even if you increase the size of CHAR to CHAR(2), you will not get results when the users enters a number like 1 or 01, because for character wise comparison, '1' != '1 '(Mark the extra space at the end, because of char(2)); and also '1' != '01'.
The only solution for above is to use a NUMBER datatype.
Now here I am posting my query which is similar to your query, with a change of column name and table name. Please replace with your required names and try -
(Take care not to execute the ACCEPT....) with the PL/SQL block. It should be done in the SQL prompt first and then the other DECLARE section should be run.
--ACCEPT a NUMBER PROMPT 'Please Enter a Date, eg. Format - 01 or 30 :'
--Run the above line first in SQL Prompt and then execute the rest as whole
DECLARE
datev NUMBER(2) := &a;
CURSOR cur_day_cursor IS
SELECT Ename, HireDate Adate
FROM Emp
WHERE TO_CHAR(HireDate, 'D') = datev;
cur_day_cursor_var cur_day_cursor%ROWTYPE;
BEGIN
OPEN Cur_day_cursor;
LOOP
Fetch Cur_day_cursor
INTO cur_day_cursor_var;
EXIT WHEN cur_day_cursor%NOTFOUND;
DBMS_OUTPUT.PUT_LINE (cur_day_cursor_var.Adate);
END LOOP;
IF cur_day_cursor%ISOPEN THEN
CLOSE cur_day_cursor;
END IF;
END;
/
It appears that the problem is that the single-quotes around ‘DD’ aren't single-quotes. It looks like the code was created in an editor which changes apostrophes into those special "look kind of like single quotes but aren't really" characters. Replace the original version of the WHERE clause with the following:
WHERE TO_CHAR(Arrival_Date, 'DD') = datev;
and I suspect you'll be fine.
And get yourself a good code editor. :-)

SQL not finding results

This query currently is returning no results, and it should. Can you see anything wrong with this query
field title are NEED_2_TARGET, ID, and CARD
NEED_2_TARGET = integer
CARD = string
ID = integer
value of name is 'Ash Imp'
{this will check if a second target is needed}
//**************************************************************************
function TFGame.checkIf2ndTargetIsNeeded(name: string):integer;
//**************************************************************************
var
targetType : integer; //1 is TCard , 2 is TMana , 0 is no second target needed.
begin
TargetType := 0;
Result := targetType;
with adoquery2 do
begin
close;
sql.Clear;
sql.Add('SELECT * FROM Spells WHERE CARD = '''+name+''' and NEED_2_TARGET = 1');
open;
end;
if adoquery2.RecordCount < 1 then
Result := 0
else
begin
Adoquery2.First;
TargetType := adoquery2.FieldByName(FIELD_TARGET_TYPE).AsInteger;
result := TargetType;
end;
end;
sql db looks like below
ID CARD TRIGGER_NUMBER CATEGORY_NUMBER QUANTITY TARGET_NUMBER TYPE_NUMBER PLUS_NUMBER PERCENT STAT_TARGET_NUMBER REPLACEMENT_CARD_NUMBER MAX_RANDOM LIFE_TO_ADD REPLACED_DAMAGE NEED_2_TARGET TYPE_OF_TARGET
27 Ash Imp 2 2 15 14 1 1
There are a number of things that could be going wrong.
First and most important in your trouble-shooting is to take your query and run it directly against your database. I.e. first confirm your query is correct by eliminating possibilities of other things going wrong. More things confirmed working, the less "noise" to distract you from solving the problem.
As others having pointed out if you're not clearing your SQL statement, you could be returning zero rows in your first result set.
Yes I know, you've since commented that you are clearing your previous query. The point is: if you're having trouble solving your problem, how can you be sure where the problem lies? So, don't leave out potentially relevant information!
Which bring us neatly to the second possibility. I can't see the rest of your code, so I have to ask: are you refreshing your data after changing your query? If you don't Close and Open your query, you may be looking at a previous execution's result set.
I'm unsure whether you're even allowed to change your query text while the component is Active, or even whether that depends on exactly which data access component you're using. The point is, it's worth checking.
Is your application connecting to the correct database? Since you're using Access, it's very easy to be connected to a different database file without realising it.
You can check this by changing your query to return all rows (i.e. delete the WHERE clause).
You my want to change the quotes used in your SQL query. Instead of: ...CARD = "'+name+'" ORDER... rather use ...CARD = '''+name+''' ORDER...
As far as I'm aware single quotes is the ANSI standard. Even if some databases permit double quotes, using them limits portability, and may produce unexpected results when passed through certain data access drivers.
Check the datatype of your CARD column. If it's a fixed length string, then the data values will be padded. E.g. if CARD is char(10), then you might actually need to look for 'Ash Imp '.
Similarly, the actual value may contain spaces before / after the words. Use select without WHERE and check the actual value of the column. You could also check whether SELECT * FROM Spells WHERE CARD LIKE '%Ash Imp%' works.
Finally, as others have suggested, you're better off using a parameterised query rather dynamically building the query up yourself.
Your code will be more readable and flexible.
You can make your code strongly typed; and so avoid converting things like numbers and dates into strings.
You won't need to worry about the peculiarities of date formatting.
You eliminate some security concerns.
#GordonLinoff all fields in db are all caps
If that is true then that is your problem. SQL usually performs case sensitive comparisons of character/string values unless you tell it not to do so, such as with STRCMP() (MySQL 4+), LOWER() or UPPER() (SQLServer, Firebird), etc. I would also go as far as wrapping the conditions in parenthesis as well:
sql.Text := 'SELECT * FROM Spells WHERE (NEED_2_TARGET = 1) AND (STRCMP(CARD, "'+name+'") = 0) ORDER by ID';
sql.Text := 'SELECT * FROM Spells WHERE (NEED_2_TARGET = 1) AND (LOWER(CARD) = "'+LowerCase(name)+'") ORDER by ID';
sql.Text := 'SELECT * FROM Spells WHERE (NEED_2_TARGET = 1) AND (UPPER(CARD) = "'+UpperCase(name)+'") ORDER by ID';
This is or was an issue with the
With Adoquery2 do
begin
...
end
when using name in the sql, it was really getting adoquery2.name not the var name. I fixed this by changing name to Cname had no more issues after that.