ORACLE: Parameter reference in WHERE doesn't work - sql

I have created a simple static function in oracle 10g to get the reference of an object based on his pk.
STATIC FUNCTION getRef(nome IN VARCHAR2)
RETURN REF folder_typ IS
fl_r REF folder_typ := null;
BEGIN
SELECT REF(fl)
INTO fl_r
FROM folder_tab fl
WHERE fl.nome = nome;
RETURN fl_r;
END getRef;
This gives me an error because it could't fetch a row.
If insted of WHERE fl.nome = nome; I write WHERE fl.nome = 'folder1'; -- it works.
I think im not using the parameter in the right way.
How can I use it?

This is a scoping problem.
Here is my version of your set-up, extrapolated from what you posted. Note that the parameter name is different from the attribute name.
create or replace type folder_t as object
(name varchar2(128))
/
create table folders of folder_t
/
create or replace function getRef
(nome in varchar2)
return ref folder_t
is
fl_r REF folder_t;
begin
select ref(fl)
into fl_r
from folders fl
where fl.name = nome;
return fl_r;
end getRef;
/
As you can see, given this test data ...
SQL> insert into folders values (folder_t('temp'))
2 /
1 row created.
SQL> insert into folders values (folder_t('work'))
2 /
1 row created.
SQL>
... I can query the two REFs:
SQL> select getRef('temp') from dual
2 /
GETREF('TEMP')
--------------------------------------------------------------------------------
00002802091051432318864AF594741916D743E1291CF597373A4F4D7A93F159DA53A73FC0010372
2D0000
SQL> select getRef('work') from dual
2 /
GETREF('WORK')
--------------------------------------------------------------------------------
0000280209F31778C18D5740FBA0CB90929E1B6FBD1CF597373A4F4D7A93F159DA53A73FC0010372
2D0001
SQL>
But, if I change the function declaration so the parameter name is the same as the attribute name, this happens:
SQL> create or replace function getRef
2 (name in varchar2)
3 return ref folder_t
4 is
5 fl_r REF folder_t;
6 begin
7 select ref(fl)
8 into fl_r
9 from folders fl
10 where fl.name = name;
11
12 return fl_r;
13 end getRef;
14 /
Function created.
SQL> select getRef('temp') from dual
2 /
GETREF('TEMP')
------------------------------------------------------------
SQL>
The SQL engine applies scope from the table outwards. Because the unqualified NAME matches a column on the table it doesn't check to see that whether there is a parameter of that name too. This is why it is a good idea to give the parameters a distinct name. Persoanlly I favour the practice of prefixing parameters with a P_, so there's no chance of the paremeter clashing with local variables or object names.

Best practice is to always provide the scope for all variables, not just for those from table aliases:
FUNCTION getRef(nome IN VARCHAR2)
RETURN REF folder_typ IS
fl_r REF folder_typ := null;
BEGIN
SELECT REF(fl)
INTO fl_r
FROM folder_tab fl
WHERE fl.nome = getRef.nome;
RETURN fl_r;
END getRef;

Related

check if two values are present in a table with plsql in oracle sql

I'm trying to create a procedure, that checks if two values are present in a table.
The logic is as follows: Create a function called get_authority. This function takes two parameters (found in the account_owner table): cust_id and acc_id, and returns 1 (one), if the customer has the right to make withdrawals from the account, or 0 (zero), if the customer doesn't have any authority to the account. I'm writing plsql and using oracle live sql. I can't figure out how to handle the scenario where a customer has two accounts!
account_owner is seen here:
create or replace function get_authority(
p_cust_id in account_owner.cust_id%type,
p_acc_id in account_owner.acc_id%type
)
return varchar2
as
v_return number(1);
v_acc_id account_owner.acc_id%type;
v_cust_id account_owner.cust_id%type;
begin
for v_ in (select account_owner.cust_id,
account_owner.acc_id
from account_owner
where p_cust_id = cust_id)
LOOP
if p_cust_id = v_cust_id and p_acc_id = v_acc_id then
v_return := v_return + 1;
else
v_return := v_return + 0;
end if;
return v_return;
END LOOP;
end;
/
When I check for the cust_id I get the return 0 - but it should be 1??
select get_authority('650707-1111',123) from dual;
return:
GET_AUTHORITY('650707-1111',123)
0
What do I do wrong?
You got 0? How come; should be NULL.
v_return number(1);
so it is initially NULL. Later on, you're adding "something" to it, but - adding anything to NULL will be NULL:
SQL> select 25 + null as result from dual;
RESULT
----------
SQL>
Therefore, set its default value to 0 (zero):
v_return number(1) := 0;
Also, you declared two additional variables:
v_acc_id account_owner.acc_id%type;
v_cust_id account_owner.cust_id%type;
Then you compare them to values passed as parameters; as they are NULL, ELSE is executed.
Furthermore, there's a loop, but you don't do anything with it. If you meant that this:
for v_ in (select account_owner.cust_id,
(rewritten as for v_ in (select cust_id) evaluates to v_cust_id - it does not. Cursor variables are referred to as v_.cust_id (note the dot in between).
Also, if there's only one row per p_cust_id and p_acc_id, why do you use cursor FOR loop at all? To avoid no_data_found or too_many_rows? I wouldn't do that; yes, it fixes such "errors", but is confusing. You'd rather properly handle exceptions.
Here's what you might have done:
Sample data:
SQL> select * From account_owner;
ACCOW_ID CUST_ID ACC_ID
---------- ----------- ----------
1 650707-1111 123
2 560126-1148 123
3 650707-1111 5899
Function; if there are more rows per parameters' combination, max function will make sure that too_many_rows is avoided (as it bothers you). You don't really care what it returns - important is that select returns anything to prove that authority exists for that account.
SQL> create or replace function get_authority
2 (p_cust_id in account_owner.cust_id%type,
3 p_acc_id in account_owner.acc_id%type
4 )
5 return number
6 is
7 l_accow_id account_owner.accow_id%type;
8 begin
9 select max(o.accow_id)
10 into l_accow_id
11 from account_owner o
12 where o.cust_id = p_cust_id
13 and o.acc_id = p_acc_id;
14
15 return case when l_accow_id is not null then 1
16 else 0
17 end;
18 end;
19 /
Function created.
Testing:
SQL> select get_authority('650707-1111', 123) res_1,
2 get_authority('650707-1111', 5899) res_2
3 from dual;
RES_1 RES_2
---------- ----------
1 1
SQL>

Function returns max of whole table instead of someting particular - PLSQL

I got a PLSQL question related to my output.
Suppose i have a table like this:
Rates:
Company | Country | Level
AA | US | 5
BB | UK | 4
CC | FRANCE | 2
DD | FRANCE | 3
EE | US | 4
FF | UK | 5
I need to create a FUNCTION that gets as parameter the country and provides me it's maximal level.
this is what i wrote:
CREATE OR REPLACE
FUNCTION getMaxLevel(Country VARCHAR2)
RETURN NUMBER
IS
MaxLevel NUMBER;
BEGIN
SELECT max(R.Level) INTO MaxLevel
FROM Rates R
WHERE R.country=Country;
RETURN MaxLevel;
END getMaxLevel;
/
/*CALL TO CHECK*/
DECLARE
X NUMBER;
BEGIN
X:=getMaxLevel('FRANCE');
dbms_output.put_line(X);
END;
and my output is the maximal rate of whole Rates table which here for instance is 6, but i needed to get 3.
Will be thankful for finding my bug :)
Thanks!
You problem is the parameter name. I would recommend prefixing it with something like in:
CREATE OR REPLACE
FUNCTION getMaxLevel(
in_Country VARCHAR2
)
RETURN NUMBER IS
MaxLevel NUMBER;
BEGIN
SELECT max(R.Level) INTO MaxDegree
FROM Rates R
WHERE R.country = in_Country;
RETURN MaxLevel;
END getMaxLevel;
/
You think that Country in your expression:
R.country = Country
is referring to the parameter. But that is not how SQL scoping rules work. It is referring to R.country -- hence the non-sensical result.
You should give your parameter another name that the name of the column in the table, otherwise the condition in the WHERE clause is ambiguous.
In condition R.country = Country, the database thinks that the second Country refers to the (unqualified) column name, not to the parameter. This condition is always true (unless Country is null), and the query ends up returning the maximum level from the whole table.
CREATE OR REPLACE
FUNCTION getMaxLevel(pCountry VARCHAR2)
RETURN NUMBER
IS
MaxLevel NUMBER;
BEGIN
SELECT max(R.Level) INTO MaxLevel
FROM Rates R
WHERE R.country = pCountry;
RETURN MaxLevel;
END getMaxLevel;
/
/*CALL TO CHECK*/
DECLARE
X NUMBER;
BEGIN
X:=getMaxLevel('FRANCE');
dbms_output.put_line(X);
END;
Side note: there is a typo in the query, you meant SELECT .. INT0 MaxLevel instead of SELECT .. INT0 MaxDegree.
You forgot tо prefix the function parameter, this is the name of the function itself.
Rename the column level in the table (e.g. lvl), it is the key word.
Consider this working example:
create or replace function getMaxLevel (Country varchar2) return number is
begin
for r in (
select max (r.Lvl) MaxLevel
from Rates r
where R.country=getMaxLevel.Country
) loop return r.MaxLevel;
end loop;
raise program_error;
end getMaxLevel;
/
var x number
exec :x := getMaxLevel ('FRANCE');
X
-
3

Oracle Object inside object inside table

I have problem with oracle objects. I am writing a Function. which have SELECT:
CURSOR cResultValues (p_vrc_mnemo VARCHAR2,
p_pdt_mnemo VARCHAR2,
p_table t_crt_list_prdt_conf_tab) IS
SELECT pdt_grp_mnemo,
pdt_mnemo,
pdt_variant,
FROM TABLE(p_table)
WHERE pdt_mnemo = p_pdt_mnemo AND
pdt_variant = p_vrc_mnemo;
and to make more clear global types:
CREATE OR REPLACE TYPE t_pdt_config_rec IS OBJECT(
pdt_grp_mnemo VARCHAR2(30),
pdt_mnemo VARCHAR2(30),
pdt_variant VARCHAR2(30),
/
CREATE OR REPLACE TYPE t_pdt_config_tab IS TABLE OF t_pdt_config_rec
/
-------------------------------------------------------------------------------------
CREATE OR REPLACE TYPE t_list_conf_rec IS OBJECT(
pdt_conf t_pdt_config_rec, -- product info
pdt_childs t_pdt_config_tab) -- products compinations
/
CREATE OR REPLACE TYPE t_list_conf_tab IS TABLE OF t_list_conf_rec
/
And so on. Before changes there was only t_pdt_config_tab and I have no problems.
How could I reach information inside pdt_conf object?
It is not clear what your problem is. I can only guess that you changed the type of the procedure parameter from t_pdt_config_rec to a more complex object type t_list_conf_rec and now you can't access the values in it.
Let's build a similar example:
SQL> CREATE OR REPLACE TYPE t_pdt_config_rec IS OBJECT(
2 pdt_mnemo VARCHAR2(30),
3 pdt_variant VARCHAR2(30))
4 /
Type created
SQL> CREATE OR REPLACE TYPE t_pdt_config_tab IS TABLE OF t_pdt_config_rec
2 /
Type created
SQL> CREATE OR REPLACE TYPE t_list_conf_rec IS OBJECT(
2 pdt_conf t_pdt_config_rec, -- product configuration and info
3 pdt_childs t_pdt_config_tab) -- similar or same products cobinations
4 /
Type created
SQL> CREATE OR REPLACE TYPE t_list_conf_tab IS TABLE OF t_list_conf_rec
2 /
Type created
Accessing sub-objects in PL/SQL is not unlike java:
SQL> SET SERVEROUTPUT ON
SQL> DECLARE
2 l_conf_1 t_pdt_config_rec := t_pdt_config_rec('conf 1','A');
3 l_conf_2 t_pdt_config_rec := t_pdt_config_rec('conf 2','B');
4 l_child_1 t_pdt_config_rec := t_pdt_config_rec('conf 1 old', 'AA');
5 l_child_2 t_pdt_config_rec := t_pdt_config_rec('conf 1 old old','AB');
6 l_children_1 t_pdt_config_tab := t_pdt_config_tab(l_child_1, l_child_2);
7 l_children_2 t_pdt_config_tab := t_pdt_config_tab();
8 l_obj_1 t_list_conf_rec := t_list_conf_rec(l_conf_1, l_children_1);
9 l_obj_2 t_list_conf_rec := t_list_conf_rec(l_conf_2, l_children_2);
10 l_tab t_list_conf_tab := t_list_conf_tab(l_obj_1, l_obj_2);
11 BEGIN
12 FOR cc IN (SELECT o.pdt_conf.pdt_mnemo pdt_mnemo,
13 o.pdt_conf.pdt_variant pdt_variant
14 FROM TABLE(l_tab) o
15 WHERE o.pdt_conf.pdt_mnemo = 'conf 1'
16 AND o.pdt_conf.pdt_variant = 'A') LOOP
17 dbms_output.put_line('record found');
18 END LOOP;
19 END;
20 /
record found
PL/SQL procedure successfully completed
Before changes there was only t_pdt_config_tab and I have no
problems.
Of course.
You now have a NESTED array. so your outer TABLE(p_table) will be selecting the rows of t_crt_list_prdt_conf_tab (whatever that is, did you mean to type t_list_conf_tab??).
ill answer assuming you meant t_list_conf_tab and not t_crt_list_prdt_conf_tab. if t_crt_list_prdt_conf_tab is a type that contains t_list_conf_tab, then you'll need another level:
select list_conf.id list_conf_id,
list_conf.pdt_conf.pdt_grp_mnemo,
list_conf.pdt_conf.pdt_mnemo,
list_conf.pdt_conf.pdt_name,
list_conf.pdt_conf.pdt_variant,
list_conf.pdt_conf.det_info_xsr_id ,
list_conf.pdt_conf.det_info_view_template_name ,
list_conf.pdt_conf.det_info_download_xsl_id,
list_conf.pdt_conf.det_info_ctrl_url,
list_conf.pdt_conf.det_info_ctrl_action,
list_conf.pdt_conf.create_ctrl_url,
list_conf.pdt_conf.create_ctrl_action,
list_conf.pdt_conf.change_contract_name_enabled,
list_conf.pdt_conf.period_selector,
list_conf.pdt_conf.period_selector_hide_all_opt,
pdt_child.pdt_grp_mnemo,
pdt_child.pdt_mnemo,
pdt_child.pdt_name,
pdt_child.pdt_variant,
pdt_child.det_info_xsr_id,
pdt_child.det_info_view_template_name,
pdt_child.det_info_download_xsl_id,
pdt_child.det_info_ctrl_url,
pdt_child.det_info_ctrl_action,
pdt_child.create_ctrl_url,
pdt_child.create_ctrl_action,
pdt_child.change_contract_name_enabled,
pdt_child.period_selector,
pdt_child.period_selector_hide_all_opt,
pdt_child.downloads
from (SELECT rownum id,
pdt_conf,
pdt_childs
FROM TABLE(p_table)) list_conf,
table(list_conf.pdt_childs) pdt_child;
sql fiddle example: http://sqlfiddle.com/#!4/2eee6/1

COUNT function for files?

Is it possible to use COUNT in some way that will give me the number of tuples that are in a .sql file? I tried using it in a query with the file name like this:
SELECT COUNT(*) FROM #q65b;
It tells me that the table is invalid, which I understand because it isn't a table, q65b is a file with a query saved in it. I'm trying to compare the number of rows in q65b to a view that I have created. Is this possible or do I just have to run the query and check the number of rows at the bottom?
Thanks
You can do this in SQL*Plus. For example:
Create the text file, containing the query (note: no semicolon!):
select * from dual
Save it in a file, e.g. myqueryfile.txt, to the folder accessible from your SQL*Plus session.
You can now call this from within another SQL query - but make sure the # as at the start of a line, e.g.:
SQL> select * from (
2 #myqueryfile.txt
3 );
D
-
X
I don't personally use this feature much, however.
Here is one approach. It's a function which reads a file in a directory, wraps the contents in a select count(*) from ( .... ) construct and executes the resultant statement.
1 create or replace function get_cnt
2 ( p_file in varchar2 )
3 return number
4 as
5 n pls_integer;
6 stmt varchar2(32767);
7 f_line varchar2(255);
8 fh utl_file.file_type;
9 begin
10 stmt := 'select count(*) from (';
11 fh := utl_file.fopen('SQL_SCRIPTS', p_file, 'R');
12 loop
13 utl_file.get_line(fh, f_line );
14 if f_line is null then exit;
15 elsif f_line = '/' then exit;
16 else stmt := stmt ||chr(10)||f_line;
17 end if;
18 end loop;
19 stmt := stmt || ')';
20 execute immediate stmt into n;
21 return n;
22* end get_cnt;
SQL>
Here is the contents of a sql file:
select * from emp
/
~
~
~
"scripts/q_emp.sql" 3L, 21C
And here is how the script runs:
SQL> select get_cnt ('q_emp.sql') from dual
2 /
GET_CNT('Q_EMP.SQL')
--------------------
14
SQL>
So it works. Obviously what I have posted is just a proof of concept. You will need to include lots of error handling for the UTL_FILE aspects - it's a package which can throw lots of exceptions - and probably some safety checking of the script that gets passed.

Is it possible to perform a bitwise group function?

I have a field in a table which contains bitwise flags. Let's say for the sake of example there are three flags: 4 => read, 2 => write, 1 => execute and the table looks like this*:
user_id | file | permissions
-----------+--------+---------------
1 | a.txt | 6 ( <-- 6 = 4 + 2 = read + write)
1 | b.txt | 4 ( <-- 4 = 4 = read)
2 | a.txt | 4
2 | c.exe | 1 ( <-- 1 = execute)
I'm interested to find all users who have a particular flag set (eg: write) on ANY record. To do this in one query, I figured that if you OR'd all the user's permissions together you'd get a single value which is the "sum total" of their permissions:
user_id | all_perms
-----------+-------------
1 | 6 (<-- 6 | 4 = 6)
2 | 5 (<-- 4 | 1 = 5)
*My actual table isn't to do with files or file permissions, 'tis but an example
Is there a way I could perform this in one statement? The way I see it, it's very similar to a normal aggregate function with GROUP BY:
SELECT user_id, SUM(permissions) as all_perms
FROM permissions
GROUP BY user_id
...but obviously, some magical "bitwise-or" function instead of SUM. Anyone know of anything like that?
(And for bonus points, does it work in oracle?)
MySQL:
SELECT user_id, BIT_OR(permissions) as all_perms
FROM permissions
GROUP BY user_id
Ah, another one of those questions where I find the answer 5 minutes after asking... Accepted answer will go to the MySQL implementation though...
Here's how to do it with Oracle, as I discovered on Radino's blog
You create an object...
CREATE OR REPLACE TYPE bitor_impl AS OBJECT
(
bitor NUMBER,
STATIC FUNCTION ODCIAggregateInitialize(ctx IN OUT bitor_impl) RETURN NUMBER,
MEMBER FUNCTION ODCIAggregateIterate(SELF IN OUT bitor_impl,
VALUE IN NUMBER) RETURN NUMBER,
MEMBER FUNCTION ODCIAggregateMerge(SELF IN OUT bitor_impl,
ctx2 IN bitor_impl) RETURN NUMBER,
MEMBER FUNCTION ODCIAggregateTerminate(SELF IN OUT bitor_impl,
returnvalue OUT NUMBER,
flags IN NUMBER) RETURN NUMBER
)
/
CREATE OR REPLACE TYPE BODY bitor_impl IS
STATIC FUNCTION ODCIAggregateInitialize(ctx IN OUT bitor_impl) RETURN NUMBER IS
BEGIN
ctx := bitor_impl(0);
RETURN ODCIConst.Success;
END ODCIAggregateInitialize;
MEMBER FUNCTION ODCIAggregateIterate(SELF IN OUT bitor_impl,
VALUE IN NUMBER) RETURN NUMBER IS
BEGIN
SELF.bitor := SELF.bitor + VALUE - bitand(SELF.bitor, VALUE);
RETURN ODCIConst.Success;
END ODCIAggregateIterate;
MEMBER FUNCTION ODCIAggregateMerge(SELF IN OUT bitor_impl,
ctx2 IN bitor_impl) RETURN NUMBER IS
BEGIN
SELF.bitor := SELF.bitor + ctx2.bitor - bitand(SELF.bitor, ctx2.bitor);
RETURN ODCIConst.Success;
END ODCIAggregateMerge;
MEMBER FUNCTION ODCIAggregateTerminate(SELF IN OUT bitor_impl,
returnvalue OUT NUMBER,
flags IN NUMBER) RETURN NUMBER IS
BEGIN
returnvalue := SELF.bitor;
RETURN ODCIConst.Success;
END ODCIAggregateTerminate;
END;
/
...and then define your own aggregate function
CREATE OR REPLACE FUNCTION bitoragg(x IN NUMBER) RETURN NUMBER
PARALLEL_ENABLE
AGGREGATE USING bitor_impl;
/
Usage:
SELECT user_id, bitoragg(permissions) FROM perms GROUP BY user_id
And you can do a bitwise or with...
FUNCTION BITOR(x IN NUMBER, y IN NUMBER)
RETURN NUMBER
AS
BEGIN
RETURN x + y - BITAND(x,y);
END;
You would need to know the possible permission components (1, 2 and 4) apriori (thus harder to maintain), but this is pretty simple and would work:
SELECT user_id,
MAX(BITAND(permissions, 1)) +
MAX(BITAND(permissions, 2)) +
MAX(BITAND(permissions, 4)) all_perms
FROM permissions
GROUP BY user_id
I'm interested to find all users who
have a particular flag set (eg: write)
on ANY record
What's wrong with simply
SELECT DISTINCT User_ID
FROM Permissions
WHERE permissions & 2 = 2