ORACLE QUERY to pass date as variable in TOAD

I'm new to oracle and trying to run a simple query to pass date dynamically

DEFINE startdate = TO_DATE(select TRUNC(LAST_DAY(ADD_MONTHS( max(nav_last_calc_dt) ,-1))+1) from tb);

DEFINE enddate = TO_DATE(select TRUNC(LAST_DAY(ADD_MONTHS(max(nav_last_calc_dt),0)))  from tb);

begin
select Count(1)
FROM  tb
WHERE  DATE BETWEEN &startdate  AND  &enddate;
 
end;

I received the below error when executing using script (F5), TOAD script runner.

ORA-06550: line 4, column 78:
PL/SQL: ORA-00936: missing expression
ORA-06550: line 2, column 1:
PL/SQL: SQL Statement ignored

But when using SQL PLUS , it returned 7 as result. So I'm confused.


SQL*Plus isn't returning 7 as the result; you haven't completed the anonymous PL/SQL block, so it is showing you 7 as the next line number (as 'begin' is line 1 and 'end;' is line 6, and is waiting for input. If you enter a slash and hit return it will then execute the block; which will throw the same ORA-00936 error.

In both clients the problem is the DATE keyword - it's expecting that to be the start of a date literal, and doesn't see the rest of the literal value as it expects. That should be a column name, presumably:

WHERE nav_last_calc_dt DATE BETWEEN &startdate  AND  &enddate;

But the block will still fail, possibly for different reasons in the two clients; in SQL*Plus it will still get ORA-00936 because the defined value ends at the first space (which you can fix by enclosing in double quotes), and Toad may throw that error or complain that your select has no 'into' clause. (Or it might do something else; SQL Developer against 12cR1 is throwing an internal error.) The outer TO_DATE in your defined expressions is also not ideal - it will implicitly convert the date you have to a string and then convert that string back again to a real date, both using your session NLS settings; which might work, but isn't needed.

It's not clear why you are using PL/SQL there, or really why you are using a substitution variable - it's not really dynamic, it just makes the final statement a bit more obscure.

The date calculation also seems a bit complicated. It looks like you're trying to count rows from the last month with any data; and you're finding the first day of that month using add_months and last_day - which could be done more simply just by truncating the maximum date using the 'MM' date component:

select count(*)
from tb
where nav_last_calc_dt >= (select trunc(max(nav_last_calc_dt), 'MM') from tb)

Assuming the column used for the filter is `nav_last_calc_dt, and not some other column, you don't need an upper bound - you know the maximum date is in that month, so it has to be within the month.

If if was another column, with later dates, and you used between then you would exclude any values after midnight on the last day of that month. It's safer to use a full month range:

select count(*)
from tb
where some_date >= (select trunc(max(nav_last_calc_dt), 'MM') from tb)
and some_date < (select add_months(trunc(max(nav_last_calc_dt), 'MM'), 1) from tb)

which would find all values at or after midnight on the first day of the target month, and before midnight on the first day of the following month.

It might then be worth getting the maximum date once, but you could do that in a CTE or an inline view rather than via define, which wouldn't actually save you anything the way you are trying to use it - since both defined queries would be substituted into the query/block before it's executed.