Passing variable to a Select query in Oracle - oracle11g

I am working on Oracle 11g Db, Having trouble on writing Oracle syntax.
I am trying to pass a number variable to my select query and populate the select query to a cursor.
Declare yr_nr NUMBER;
Begin
yr_nr := 2014;
SELECT DCD.CCY ID, DCD.CCYCDDSC DSC
FROM CCYDCD DCD, CCYEXC EXC
WHERE DCD.CCY = EXC.CCY
AND EXC.YEARNR = yr_nr
End
This select query returns 80 records. How to rewrite this syntax.

Ok, so what you have here is an anonymous block and everything that happens in the block stays in that block. Kinda like Vegas.
In other words there is nothing to handle the result set from your query. When you do this:
declare
[varName] [type]
begin
select foo from bar where column = var ; <--- this has no place to go!
end
When you are at an sqlPlus prompt, sqlPlus has a default record set handler which then processes the returned record set and prints it to the screen.
When you use any third party tool like JDBC or Oracle's own OCI library those provide a record set handler then parse them to you with the appropriate calls to get the data, e.g.:
rs.getInteger([query],[column] ) //which returns the specific value.
That anonymous block is essentially a stored procedure. So you have to have something to do with the result set. This is the cause of the missing "into" error you are getting.
If on the other hand you did something like:
declare
[varName] [type]
result number ;
begin
select count(foo) into result from bar where column = var ;
end
The variable result would have the value of 80 since that is the number of records fetched.
declare
[varName] [type]
cursor thisCursor(p1 in number ) is select foo from bar where column = p1 ;
begin
for rec in thisCursor(varName) loop
If rec.column = [some value] then
doSomething
end if ;
end loop ;
end
Do this would allow you to do something with the result set.

Related

Is there a way to INSERT Null value as a parameter using FireDAC?

I want to leave some fields empty (i.e. Null) when I insert values into table. I don't see why would I want to have a DB full of empty strings in fields.
I use Delphi 10, FireDAC and local SQLite DB.
Edit: Provided code is just an example. In my application values are provided by user input and functions, any many of them are optional. If value is empty, I would like to keep it at Null or default value. Creating multiple variants of ExecSQL and nesting If statements isn't an option too - there are too many optional fields (18, to be exact).
Test table:
CREATE TABLE "Clients" (
"Name" TEXT,
"Notes" TEXT
);
This is how I tried it:
var someName,someNote: string;
begin
{...}
someName:='Vasya';
someNote:='';
FDConnection1.ExecSQL('INSERT OR REPLACE INTO Clients(Name,Notes) VALUES (:nameval,:notesval)',
[someName, IfThen(someNote.isEmpty, Null, somenote)]);
This raises an exception:
could not convert variant of type (Null) into type (OleStr)
I've tried to overload it and specify [ftString,ftString] and it didn't help.
Currently I have to do it like this and I hate this messy code:
FDConnection1.ExecSQL('INSERT OR REPLACE INTO Clients(Name,Notes) VALUES ('+
IfThen(someName.isEmpty,'NULL','"'+Sanitize(someName)+'"')+','+
IfThen(someNote.isEmpty,'NULL','"'+Sanitize(someNote)+'"')+');');
Any recommendations?
Edit2: Currently I see an option of creating new row with "INSERT OR REPLACE" and then use multiple UPDATEs in a row for each non-empty value. But this looks direly ineffective. Like this:
FDConnection1.ExecSQL('INSERT OR REPLACE INTO Clients(Name) VALUES (:nameval)',[SomeName]);
id := FDConnection1.ExecSQLScalar('SELECT FROM Clients VALUES id WHERE Name=:nameval',[SomeName]);
if not SomeString.isEmpty then
FDConnection1.ExecSQL('UPDATE Clients SET Notes=:noteval WHERE id=:idval)',[SomeNote,id]);
According to Embarcadero documentation ( here ):
To set the parameter value to Null, specify the parameter data type,
then call the Clear method:
with FDQuery1.ParamByName('name') do begin
DataType := ftString;
Clear;
end;
FDQuery1.ExecSQL;
So, you have to use FDQuery to insert Null values, I suppose. Something like this:
//Assign FDConnection1 to FDQuery1's Connection property
FDQuery1.SQL.Text := 'INSERT OR REPLACE INTO Clients(Name,Notes) VALUES (:nameval,:notesval)';
with FDQuery1.ParamByName('nameval') do
begin
DataType := ftString;
Value := someName;
end;
with FDQuery1.ParamByName('notesval') do
begin
DataType := ftString;
if someNote.IsEmpty then
Clear;
else
Value := someNote;
end;
if not FDConnection1.Connected then
FDConnection.Open;
FDQuery1.ExecSql;
It's not very good idea to execute query as String without parameters because this code is vulnerable to SQL injections.
Some sources tells that it's not enough and you should do something like this:
with FDQuery1.ParamByName('name') do begin
DataType := ftString;
AsString := '';
Clear;
end;
FDQuery1.ExecSQL;
but I can't confirm it. You can try it if main example won't work.

PL/SQL variable scope in nested blocks

I need to run some SQL blocks to test them, is there an online app where I can insert the code and see what outcome it triggers?
Thanks a lot!
More specific question below:
<<block1>>
DECLARE
var NUMBER;
BEGIN
var := 3;
DBMS_OUTPUT.PUT_LINE(var);
<<block2>>
DECLARE
var NUMBER;
BEGIN
var := 200;
DBMS_OUTPUT.PUT_LINE(block1.var);
END block2;
DBMS_OUTPUT.PUT_LINE(var);
END block1;
Is the output:
3
3
200
or is it:
3
3
3
I read that the variable's value is the value received in the most recent block so is the second answer the good one? I'd love to test these online somewhere if there is a possibility.
Also, is <<block2>> really the correct way to name a block??
Later edit:
I tried this with SQL Fiddle, but I get a "Please build schema" error message:
Thank you very much, Dave! Any idea why this happens?
create table log_table
( message varchar2(200)
)
<<block1>>
DECLARE
var NUMBER;
BEGIN
var := 3;
insert into log_table(message) values (var)
select * from log_table
<<block2>>
DECLARE
var NUMBER;
BEGIN
var := 200;
insert into log_table(message) values (block1.var || ' 2nd')
select * from log_table
END block2;
insert into log_table(message) values (var || ' 3rd')
select * from log_table
END block1;
In answer to your three questions.
You can use SQL Fiddle with Oracle 11g R2: http://www.sqlfiddle.com/#!4. However, this does not allow you to use dbms_output. You will have to insert into / select from tables to see the results of your PL/SQL scripts.
The answer is 3 3 3. Once the inner block is END-ed the variables no longer exist/have scope. You cannot access them any further.
The block naming is correct, however, you aren't required to name blocks, they can be completely anonymous.
EDIT:
So after playing with SQL Fiddle a bit, it seems like it doesn't actually support named blocks (although I have an actual Oracle database to confirm what I said earlier).
You can, however, basically demonstrate the way variable scope works using stored procedures and inner procedures (which are incidentally two very important PL/SQL features).
Before I get to that, I noticed three issues with you code:
You need to terminate the insert statements with a semi-colon.
You need to commit the the transactions after the third insert.
In PL/SQL you can't simply do a select statement and get a result, you need to select into some variable. This would be a simple change, but because we can't use dbms_output to view the variable it doesn't help us. Instead do the inserts, then commit and afterwards select from the table.
In the left hand pane of SQL Fiddle set the query terminator to '//' then paste in the below and 'build schema':
create table log_table
( message varchar2(200)
)
//
create or replace procedure proc1 as
var NUMBER;
procedure proc2 as
var number;
begin
var := 200;
insert into log_table(message) values (proc1.var || ' 2nd');
end;
begin
var := 3;
insert into log_table(message) values (var || ' 1st');
proc2;
insert into log_table(message) values (var || ' 3rd');
commit;
end;
//
begin
proc1;
end;
//
Then in the right hand panel run this SQL:
select * from log_table
You can see that proc2.var has no scope outside of proc2. Furthermore, if you were to explicitly try to utilize proc2.var outside of proc2 you would raise an exception because it is out-of-scope.

PLS-00487 Error-Invalid reference to Variable 'CHAR'

I'm designing a function that is part of a larger package. The function is intended to take a District Code and return a collection of unique IDs for 10-15 stores that are assigned to that district. The function is intended to return a collection that can be queried like a table, i.e., using the TABLE function in a SQL statement.
I've created the following Types:
Schema Level type:
create or replace TYPE HDT_CORE_ORGIDS AS TABLE OF CHAR(20);
and a Type inside the Package
TYPE CORE_ORGIDS IS TABLE OF CHAR(20) INDEX BY BINARY_INTEGER;
Here's the function code:
FUNCTION FindDistrictOrgs(
ParamOrgCode VARCHAR2
)
RETURN HDT_CORE_ORGIDS
AS
ReturnOrgs HDT_CORE_ORGIDS := HDT_CORE_ORGIDS();
FDOTemp HDT_CORE_MAIN.CORE_ORGIDS;
i BINARY_INTEGER := 0;
CURSOR FDOCurr IS
SELECT org.id AS OrgID
FROM tp2.tpt_company org
WHERE LEVEL = 2
START WITH org.name = ParamOrgCode
CONNECT BY PRIOR org.id = org.parent_id;
BEGIN
OPEN FDOCurr;
LOOP
i := i +1;
FETCH FDOCurr INTO FDOTemp(i);
EXIT WHEN FDOCurr%NOTFOUND;
END LOOP;
IF FDOTemp.EXISTS(FDOTemp.FIRST) THEN
ReturnOrgs.EXTEND(FDOTemp.LAST);
FOR x IN FDOTemp.FIRST .. FDOTemp.LAST LOOP
ReturnOrgs(x) := FDOTemp(x).OrgID;
END LOOP;
END IF;
CLOSE FDOCurr;
RETURN ReturnOrgs;
END FindDistrictOrgs ;
I'm getting the PLS-00487:Invalid Reference to variable 'CHAR' at the line:
ReturnOrgs(x) := FDOTemp(x).OrgID;
I've double-checked at the value returned by the SQL (the org.id AS OrgID) is of the CHAR(20 BYTE) datatype.
So...what's causing the error?
Any help is appreciated! :)
OrgID is the alias you gave the column in your cursor, it has no meaning to the collection. Since both collections are of simple types you should just be doing:
ReturnOrgs(x) := FDOTemp(x);
The syntax you're using is implying FDOTemp is a collection of objects and you're trying to reference the OrgID attribute of an object; but since CHAR isn't an object type, this errors. The error message even makes some sense when viewed like that, though it's not terribly helpful if you don't already know what's wrong... and not entirely helpful when you do.
Incidentally, you could use a bulk collect to populate the collection without the cursor or loops, or the extra collection:
SELECT org.id
BULK COLLECT INTO ReturnOrgs
FROM tp2.tpt_company org
WHERE LEVEL = 2
START WITH org.name = ParamOrgCode
CONNECT BY PRIOR org.id = org.parent_id;
RETURN ReturnOrgs;

A generic procedure that can execute any procedure/function

input
Package name (IN)
procedure name (or function name) (IN)
A table indexed by integer, it will contain values that will be used to execute the procedure (IN/OUT).
E.g
let's assume that we want to execute the procedure below
utils.get_emp_num(emp_name IN VARCHAR
emp_last_name IN VARCHAR
emp_num OUT NUMBER
result OUT VARCHAR);
The procedure that we will create will have as inputs:
package_name = utils
procedure_name = get_emp_num
table = T[1] -> name
T[2] -> lastname
T[3] -> 0 (any value)
T[4] -> N (any value)
run_procedure(package_name,
procedure_name,
table)
The main procedure should return the same table that has been set in the input, but with the execution result of the procedure
table = T[1] -> name
T[2] -> lastname
T[3] -> 78734 (new value)
T[4] -> F (new value)
any thought ?
You can achieve it with EXECUTE IMMEDIATE. Basically, you build a SQL statement of the following form:
sql := 'BEGIN utils.get_emp_num(:1, :2, :3, :4); END;';
Then you execute it:
EXECUTE IMMEDIATE sql USING t(1), t(2), OUT t(3), OUT t(4);
Now here comes the tricky part: For each number of parameters and IN/OUT combinations you need a separate EXECUTE IMMEDIATE statement. And to figure out the number of parameters and their direction, you need to query the ALL_ARGUMENTS table first.
You might be able to simplify it by passing the whole table as a bind argument instead of a separate bind argument for each table element. But I haven't quite figured out how you would do that.
And the next thing you should consider: the elements of the table T your using will have a type: VARCHAR, NUMBER etc. So the current mixture where you have both numbers and strings won't work.
BTW: Why do you want such a dynamic call mechanism anyway?
Get from the all_arguments table the argument_name, data_type, in_out, and the position
Build the PLSQL block
DECLARE
loop over argument_name and create the declare section
argument_name data_type if in_out <> OUT then := VALUE OF THE INPUT otherwise NULL
BEGIN
--In the case of function create an additional argument
function_var:= package_name.procedure_name( loop over argument_name);
--use a table of any_data, declare it as global in the package
if function then
package_name.ad_table.EXTEND;
package_name.ad_table(package_name.ad_table.LAST):= function_var;
end if
--loop over argument_name IF IN_OUT <> IN
package_name.ad_table.EXTEND;
package_name.ad_table(package_name.ad_table.LAST):=
if data_type = VARCHAR2 then := ConvertVarchar2(argument_name)
else if NUMBER then ConvertNumber
else if DATE then ConvertDate
...
END;
The result is stored in the table.
To get value use Access* functions

DBMS_OUTPUT.PUT_LINE not printing

When executing the following code, it just says the procedure is completed and doesn't print the infomation i want it to (firstName, lastName) and then the other values from the select query in a table below.
CREATE OR REPLACE PROCEDURE PRINT_ACTOR_QUOTES (id_actor char)
AS
CURSOR quote_recs IS
SELECT a.firstName,a.lastName, m.title, m.year, r.roleName ,q.quotechar from quote q, role r,
rolequote rq, actor a, movie m
where
rq.quoteID = q.quoteID
AND
rq.roleID = r.roleID
AND
r.actorID = a.actorID
AND
r.movieID = m.movieID
AND
a.actorID = id_actor;
BEGIN
FOR row IN quote_recs LOOP
DBMS_OUTPUT.PUT_LINE('a.firstName' || 'a.lastName');
end loop;
END PRINT_ACTOR_QUOTES;
/
When setting server output on, I get
a.firstNamea.lastName
a.firstNamea.lastName
a.firstNamea.lastName
a.firstNamea.lastName
multiple times!
What is "it" in the statement "it just says the procedure is completed"?
By default, most tools do not configure a buffer for dbms_output to write to and do not attempt to read from that buffer after code executes. Most tools, on the other hand, have the ability to do so. In SQL*Plus, you'd need to use the command set serveroutput on [size N|unlimited]. So you'd do something like
SQL> set serveroutput on size 30000;
SQL> exec print_actor_quotes( <<some value>> );
In SQL Developer, you'd go to View | DBMS Output to enable the DBMS Output window, then push the green plus icon to enable DBMS Output for a particular session.
Additionally, assuming that you don't want to print the literal "a.firstNamea.lastName" for every row, you probably want
FOR row IN quote_recs
LOOP
DBMS_OUTPUT.PUT_LINE( row.firstName || ' ' || row.lastName );
END LOOP;
Ensure that you have your Dbms Output window open through the view option in the menubar.
Click on the green '+' sign and add your database name.
Write 'DBMS_OUTPUT.ENABLE;' within your procedure as the first line.
Hope this solves your problem.
Set Query as below at first line
SET SERVEROUTPUT ON
this statement
DBMS_OUTPUT.PUT_LINE('a.firstName' || 'a.lastName');
means to print the string as it is.. remove the quotes to get the values to be printed.So the correct syntax is
DBMS_OUTPUT.PUT_LINE(a.firstName || a.lastName);
For SQL Developer
You have to execute it manually
SET SERVEROUTPUT ON
After that if you execute any procedure with DBMS_OUTPUT.PUT_LINE('info'); or directly .
This will print the line
And please don't try to add this
SET SERVEROUTPUT ON
inside the definition of function and procedure, it will not compile and will not work.
In Oracle SQL Developer, you can follow steps by steps as the below image:
I am using Oracle SQL Developer,
In this tool, I had to enable DBMS output to view the results printed by dbms_output.put_line
You can find this option in the result pane where other query results are displayed.
so, in the result pane, I have 7 tabs. 1st tab named as Results, next one is Script Output and so on. Out of this you can find a tab named as "DBMS Output" select this tab, then the 1st icon (looks like a dialogue icon) is Enable DBMS Output. Click this icon. Then you execute the PL/SQL, then select "DBMS Output tab, you should be able to see the results there.
All of them are concentrating on the for loop but if we use a normal loop then we had to use of the cursor record variable. The following is the modified code
CREATE OR REPLACE PROCEDURE PRINT_ACTOR_QUOTES (id_actor char)
AS
CURSOR quote_recs IS
SELECT a.firstName,a.lastName, m.title, m.year, r.roleName ,q.quotechar from quote q, role r,
rolequote rq, actor a, movie m
where
rq.quoteID = q.quoteID
AND
rq.roleID = r.roleID
AND
r.actorID = a.actorID
AND
r.movieID = m.movieID
AND
a.actorID = id_actor;
recd quote_recs%rowtype;
BEGIN
open quote_recs;
LOOP
fetch quote_recs into recs;
exit when quote_recs%notfound;
DBMS_OUTPUT.PUT_LINE(recd.firstName||recd.lastName);
end loop;
close quote_recs;
END PRINT_ACTOR_QUOTES;
/

Resources