Passing value from parameter to SQL Query - sql

I've been digging around the internet, including Stack, for the better part of the morning trying to figure out how to create a parameter and then set its value and pass it into various locations within my where clause in my Oracle SQL. I can't seem to find exactly what I want. it's quite possible that I am not asking the question correctly, so I apologize if this is a redundant question (it seems likely that it is).
Here is the SQL I am running in Oracle SQL Developer:
SELECT t.mta_tenure_sub_type_code , t.mta_tenure_type_code , COUNT(t.tenure_number_id), SUM(t.area_in_hectares)
FROM MTA_TENURE t
WHERE
t.mta_tenure_type_code in ('M','P') AND
to_char(t.issue_date, 'YYYY') <= '&year' AND
(
to_char(t.termination_date, 'YYYY') > '&year' OR
t.termination_date IS NULL OR
t.mta_termination_type_code = 'PROT'
)
GROUP BY t.mta_tenure_sub_type_code, t.mta_tenure_type_code;
As you can see, I have '&year' located twice within my query which prompts the user to enter a value each time that variable occurs. The value is always the same year at each variable (i.e. every time the prompt pops up, the user will always enter the same year twice) Being a bit of a python programmer, I had thought that I could make this query more efficient by setting a parameter to '&year' so the user only has to enter it once and the parameter will pass the value into the where clause. Is this even possible? I really thought this would have been an easy google search, but maybe my feeble brain overlooked something. Thanks.

You can replace the &year references with &&year as #Glenn suggested; see Avoiding Unnecessary Prompts for Values in the SQL*Plus docs (most of which apply to SQL Developer too).
You could also explicitly define the substitution variable, which allows you to tailor the prompt:
accept year number prompt 'Enter year'
SELECT t.mta_tenure_sub_type_code , t.mta_tenure_type_code ,
COUNT(t.tenure_number_id), SUM(t.area_in_hectares)
FROM MTA_TENURE t
WHERE
t.mta_tenure_type_code in ('M','P') AND
to_char(t.issue_date, 'YYYY') <= '&year' AND
(
to_char(t.termination_date, 'YYYY') > '&year' OR
t.termination_date IS NULL OR
t.mta_termination_type_code = 'PROT'
)
GROUP BY t.mta_tenure_sub_type_code, t.mta_tenure_type_code;
or you could define the variable with a specific value so it doesn't prompt at all:
define year=2018
If you know the value you could also use a bind variable:
var year number
exec :year := 2018;
SELECT t.mta_tenure_sub_type_code , t.mta_tenure_type_code ,
COUNT(t.tenure_number_id), SUM(t.area_in_hectares)
FROM MTA_TENURE t
WHERE
t.mta_tenure_type_code in ('M','P') AND
to_char(t.issue_date, 'YYYY') <= :year AND
(
to_char(t.termination_date, 'YYYY') > :year OR
t.termination_date IS NULL OR
t.mta_termination_type_code = 'PROT'
)
GROUP BY t.mta_tenure_sub_type_code, t.mta_tenure_type_code;
Notice the prefix for the variable has changed from & to :.
You could combine both if you wanted to:
var year number
exec :year := &year;
... and then use the bind instead of substitution variable in the query.
In either case I'd probably either convert the to_char(...) value to a number, or use extract instead:
...
extract(year from t.issue_date) <= :year AND
(
extract(year from t.termination_date) > :year OR
...

Related

ORA-01745: invalid host/bind variable name issue starts at &&start_year and &&end_year

define start_year = 2017;
define end_year = 2020;
with af as (
select
dd.retail_year as year
, dd.retail_quarter_of_year as quarter
, dd.retail_month_of_year as month
, dd.retail_week_of_year as week
, 'BN Asset' as asset_type
, count(bn_sku) as units
, sum(bn_actual_cost) as cost
from o_diamond.diamond_asset_file af
left join o_warehouse.date_dim dd on af.receipt_date = dd.full_date
where 1=1
and dd.retail_year between &&start_year and &&end_year
The error message is compatible with an undefined substitution variable prefix. Thus you should execute
set define &
as the first command in your script / interactively. The prefix may be set to a different character in the glogin.sql of the sqlplus client. Use of substitution variables may have been switched off altogether.
This resource may be of use (though a bit dated, it should get you started).

Apex parse error when creating SQL query with sql function

I have the following function:
CREATE OR REPLACE FUNCTION calc_a(BIDoctor number) RETURN number
IS
num_a number;
BEGIN
select count(NAppoint)
into num_a
from Appointment a
where BIDoctor = a.BIDoctor;
RETURN num_a;
END calc_a;
What we want is adding a column to a report that shows us the number of appointments that doc have.
select a.BIdoctor "NUM_ALUNO",
a.NameP "Nome",
a.Address "Local",
a.Salary "salary",
a.Phone "phone",
a.NumberService "Curso",
c.BIdoctor "bi",
calc_media(a.BIdoctor) "consultas"
FROM "#OWNER#"."v_Doctor" a, "#OWNER#"."Appointment" c
WHERE a.BIdoctor = c.BIdoctor;
and we got this when we are writing the region source on apex.
But it shows a parse error, I was looking for this about 2 hours and nothing.
Apex shows me this:
PARSE ERROR ON THE FOLLOWING QUERY
This is probably because of all your double quotes, you seem to have randomly cased everything. Double quotes indicate that you're using quoted identifiers, i.e. the object/column must be created with that exact name - "Hi" is not the same as "hi". Judging by your function get rid of all the double quotes - you don't seem to need them.
More generally don't use quoted identifiers. Ever. They cause far more trouble then they're worth. You'll know when you want to use them in the future, if it ever becomes necessary.
There are a few more problems with your SELECT statement.
You're using implicit joins. Explicit joins were added in SQL-92; it's time to start using them - for your future career where you might interact with other RDBMS if nothing else.
There's absolutely no need for your function; you can use the analytic function, COUNT() instead.
Your aliases are a bit wonky - why does a refer to doctors and c to appointments?
Putting all of this together you get:
select d.bidoctor as num_aluno
, d.namep as nome
, d.address as local
, d.salary as salary
, d.phone as phone
, d.numberservice as curso
, a.bidoctor as bi
, count(nappoint) over (partition by a.bidoctor) as consultas
from #owner#.v_doctor a
join #owner#.appointment c
on d.bidoctor = a.bidoctor;
I'm guessing at what the primary keys of APPOINTMENT and V_DOCTOR are but I'm hoping they're NAPPOINT and BIDOCTOR respectively.
Incidentally, your function will never have returned the correct result because you haven't limited the scope of the parameter in your query; you would have just counted the number of records in APPOINTMENT. When you're naming parameters the same as columns in a table you have to explicitly limit the scope to the parameter in any queries you write, for instance:
select count(nappoint) into num_a
from appointment a
where calc_a.bidoctor = a.bidoctor; -- HERE

Crystal Reports 8.5 showing multi-values from parameters on report footer

In Crystal Reports 8.5 when I have setup a parameter for multi-value the user enters 90654-90658A. Normally I would use Join() but being that this is not just text but numeric I have tried a few things but with no results.
Local NumberVar i;
Local NumberVar j;
Local StringVar param_values;
if 0 in {?CPT} then
"CPT #s: All CPTs"
else
(
for i := 1 to UBound ({?CPT}) do
for j := Minimum ({?CPT}[ i ]) to Maximum ({?CPT}[ i ]) do
param_values := param_values + "," + CStr (j, "#");
"CPT #s: " + Mid (param_values, 2)
)
This works fine for 90654-90658 but when the user selects 90654-90658A it fails.
Also the selection criteria will not pass to SQL in the query sent to SQL with the correct where clause. Meaning there is not indication that I am even asking for a where. It should show in the select for sql a where table.data >= '90654' and table.data <= '90658A'
I am lost as to where I am going wrong with this. Any help would be great this is my first time seeking an answer on this site but I have not received any help on this request.
Thanks
I tried a similar query with the Xtreme.mdb database, referencing the Customer table. I created a string, range parameter that accepted multiple values (i.e. multiple ranges).
When I supplied it with two ranges, the follow query was generated:
SELECT `Customer`.`Postal Code`
FROM `Customer` `Customer`
WHERE (
(`Customer`.`Postal Code`>='04000' AND `Customer`.`Postal Code`<='04999') OR
(`Customer`.`Postal Code`>='55000' AND `Customer`.`Postal Code`<='55999')
)
As you can see, Crystal Reports will build the necessary BETWEEN or >= <= statements.
In you situation, try:
( "0" IN {?CPT} OR {TABLE.FIELD} IN {?CPT} )
You could adapt your formula field to display the values of the parameter, if you want.
I do appreciate everyones input but I was able to work through the problem. For the record selection I put in the following. {TABLE.FIELD} in CStr({#MinCPT}) to CStr({#MaxCPT}). This pulled the range after I created two formulas. One MinCPT and the other MaxCPT. Here is the formula. Left (ToText (Minimum ({?CPT})),2 ) & Mid (ToText (Minimum ({?CPT})),4 ,3 ) and the same for Max. The report works fine now.
Thanks Again.

Getting dates outside of range I specify

I have a report that I am trying to fix with SSRS because when you run it for a specific range say one month of year. It will give you all previous years too even if its outside of parameter bounds.
SELECT
to_char(app.RECEIVED_DATE, 'mm-dd-yyyy') AS received_date
, res.RESIDENCETYPE_NAME || ' - ' || act.ACTIONTYPE_NAME type
, sts.APPLSTSTYPE_NAME AS Status
, COUNT(*) AS Total_Count
FROM
ODILIC_ADMIN.LICENSEAPPL app
, ODILIC_ADMIN.LICENSEDEF def
, ODILIC_ADMIN.ACTIONTYPE act
, ODILIC_ADMIN.APPLSOURCE src
, ODILIC_ADMIN.RESIDENCETYPE res
, ODILIC_ADMIN.LICENSETYPE ltype
, ODILIC_ADMIN.LICENSINGENTITYTYPE etype
, ODILIC_ADMIN.APPLSTSTYPE sts
WHERE app.LICENSEDEF_ID = def.LICENSEDEF_ID
AND app.ACTIONTYPE_ID = act.ACTIONTYPE_ID
AND app.APPLSOURCE_ID = src.APPLSOURCE_ID
AND def.RESIDENCETYPE_ID = res.RESIDENCETYPE_ID
AND def.LICENSETYPE_ID = ltype.LICENSETYPE_ID
AND def.LICENSINGENTITYTYPE_ID = etype.LICENSINGENTITYTYPE_ID
AND app.APPLSTSTYPE_ID = sts.APPLSTSTYPE_ID
AND (app.RECEIVED_DATE BETWEEN '01-JUN-2013' AND '30-JUN-2013')
and sts.APPLSTSTYPE_NAME in ('Completed')
GROUP BY
to_char(app.RECEIVED_DATE, 'mm-dd-yyyy')
, res.RESIDENCETYPE_NAME
, act.ACTIONTYPE_NAME
, sts.APPLSTSTYPE_NAME
order by 1
So this query will filter between jun 1 and jun 30 of this year. When I run it in plsql it works fine but as soon as I put it into ssrs it will give me june counts for 2012 and 2011
On this line: AND (app.RECEIVED_DATE BETWEEN '01-JUN-2013' AND '30-JUN-2013')
I would be setting up parameters in SSRS directly to handle this as this may handle the translation of types more expicitly to specify (DateTime) as the parameter type and then changing the line to be:
AND (app.RECEIVED_DATE BETWEEN #Start AND #End)
I have not played with plsql with SSRS but I have seen translation of types problems with dealing with WCF and other channels. My usual attempt is to first specify a parameter to pass into the query execution and see if there is still an issue.

The speed of 'extract(year from sysdate)'

I have two queries:
with tmp as (
select asy.aim_student_id, ast.aim_test, asq.response
from aim_student_test ast
join aim_student_qst asq on (asq.aps_yr = ast.aps_yr and asq.aim_test = ast.aim_test and asq.aim_id = ast.aim_id)
join aim_student_yr asy on (asy.aps_yr = ast.aps_yr and asy.aim_student_yr_id = ast.aim_student_yr_id)
where asq.aps_yr = '2012'
and asq.qst_num = 1)
select aim_student_id, aim_test, response
from tmp
where response is null
-- execution-time: 0.032 seconds
define this_year = extract(year from sysdate)
with tmp as (
select asy.aim_student_id, ast.aim_test, asq.response
from aim_student_test ast
join aim_student_qst asq on (asq.aps_yr = ast.aps_yr and asq.aim_test = ast.aim_test and asq.aim_id = ast.aim_id)
join aim_student_yr asy on (asy.aps_yr = ast.aps_yr and asy.aim_student_yr_id = ast.aim_student_yr_id)
where asq.aps_yr = &this_year
and asq.qst_num = 1)
select aim_student_id, aim_test, response
from tmp
where response is null
-- execution-time: 82.202 seconds
The only difference is that in one I use '2012' and the other I implement extract(year from sysdate).
I can only imagine that Oracle is computing extract(year from sysdate) for EVERY record it checks, and that I just can't figure out how to make it compute this once and use it as a variable. Searching has not returned me the answers I seek... so I come to the magicians of SO.com. HOW do I properly use
extract(year from sysdate)
as a variable?
Using &this_year in the query causes a substitution of the string extract(year from sysdate), so the second query actually has:
where asq.aps_yr = extract(year from sysdate)
which you can see from the second explain plan. That in itself probably isn't the problem; what's possibly slowing it down is that doing this is changing the plan from an index range scan to an index skip scan against aim_student_qstp1. The real difference is that in the fast version you're comparing asq.aps_yr to a string ('2012'), in the second it's a number (2012), and - as also shown in the explain plan - this is causing it to do to_number(asq.aps_yr) which is stopping the index being used as you expect.
You could fix this in your code by making it:
where asq.aps_yr = to_char(&this_year)
If you want to calculate it once before the query runs and then use it as a variable, there are at least two ways (in SQL*Plus/SQL Developer). Sticking with substitution variables you can use the column commands instead of the define:
column tmp_this_year new_value this_year
set termout off
select extract(year from sysdate) as tmp_this_year from dual;
set termout on
set verify off
... which makes &this_year=2012 (the termout changes just make the actual retrieval invisible, and the verify stops it telling you when it uses the substitution; both so you don't get extra output in your script), and change your query to have:
where asq.aps_yr = '&this_year'
... so the value is treated as a string, making a to_char() unnecessary.
Or you can use bind variables:
var this_year varchar2(4);
set feedback off;
exec :this_year := extract(year from sysdate);
... and then your query has:
where asq.aps_yr = :this_year
Note that in this case you don't need the quotes because the bind variable is defined as a string already - there's an implicit conversion in the exec that sets it.
I doubt the difference is due to extracting the year from the date. I'm pretty sure Oracle would only be extracting the year once, since it is using a variable in the second case.
The difference is due to the execution path used by the query. You would need to post the execution plans to really see the difference. Using an explicit constant gives the optimizer more information for choosing an optimal query plan.
For instance, if the data is partitioned by year, then with a constant year, Oracle can determine which partition has the data. In the second case, Oracle might not recognize the value as a constant, and require reading all data partitions. This is just an example of what might happen -- I'm not sure what Oracle does in this case.