DECLARE
dynaCol varchar2(200);
varCol varchar2(100);
BEGIN
dbms_output.put_line(dynaCol);
select name into varCol from GSSP_ETL.DEMO_TABLE_CHECK where ID='1'|| (SELECT q'$ and optional='N'$' NAME FROM DUAL) ;
dbms_output.put_line(varCol);
END;
Though I have a row for id=1 and optional=N I still get no data found error.
SELECT q'$ and optional='N'$' NAME FROM DUAL gives me and optional='N', so there is nothing wrong to use like this.
Please help if anyone know the reason or better way to append the and in where clause.
Your original query is looking for the row with ID = "1 and optional = 'N'" which causes the problem.
You should use dynamic SQL if you want to have variable WHERE clause:
SQL> create table t (id, name, optional)
2 as
3 select 1, 'XXX','N' from dual
4 /
SQL> select * from t;
ID NAM O
---------- --- -
1 XXX N
SQL> set serveroutput on
SQL> declare
2 dynaCol varchar2(200) := q'[ and optional='N']';
3 varCol varchar2(100);
4 p_id int := 1;
5 begin
6
7 execute immediate
8 ' select name from t where id = :x '||dynaCol
9 into varCol using p_id;
10 dbms_output.put_line(varCol);
11 end;
12 /
XXX
Sorry i couldn't comment due to my low reputation..
No,its not possible to do with out immediate i think so,the best way is what Dmitry did.
Its possible to send whole select statement dynamically but a part with out execute immediate..
...You got close.
Since you're using PL/SQL, why not consider creating a persistent object such as a PL/SQL Stored Procedure instead? There will be less code on the JBOSS side and the database schema layout will be further separated from the code you intended on accessing the database.
The Demonstration Table
This is an interpretation from pieces of your original post. This is the table I used to test out my recommendations.
CREATE TABLE "DEMO_TABLE_CHECK"
( "ID" NUMBER(10,0) NOT NULL ENABLE,
"NAME" VARCHAR2(40) NOT NULL ENABLE,
"OPTIONAL" VARCHAR2(5) NOT NULL ENABLE,
CONSTRAINT "DEMO_TABLE_CHECK_PK" PRIMARY KEY ("ID") ENABLE
)
/
My test data:
The Procedure Source Code
CREATE or REPLACE PROCEDURE proc_example(p_id IN number,
p_opt IN varchar2 default null)
IS
Result varchar2(100);
BEGIN
SELECT name
INTO Result
FROM GSSP_ETL.DEMO_TABLE_CHECK
WHERE id = p_id
AND optional = nvl(p_opt, optional);
dbms_output.put_line(Result);
END proc_example;
Notice that this example solution actually does away with the dynamic SQL calls proposed in previous solutions. The realm of PL/SQL procedural code is likely to accommodate for pushing around and concatenating strings of SQL commands.
An Example Call and the Output
begin
proc_example(p_id=> 1, p_opt=>'N');
end;
-- output:
ALPHA
begin
proc_example(p_id=> 1, p_opt=> null);
proc_example(p_id=> 1);
end;
-- output:
ALPHA
ALPHA
All this PL/SQL code, which was originally in an a block passed from the part of your program that accessed the database will now reside on the database.
Discussion of the PL/SQL Procedure Design
The optional part of the query, represented by p_opt, has a default designation. This means if there is no value for that parameter, then the procedure will ignore it and assume it is equal to the defined default value. No errors will be thrown.
AND optional = nvl(p_opt, optional)
This line is a replacement for the add-on SQL string. the input parameter p_opt, whether it was supplied (such as = 'N') or skipped ( implied to = null ), the SQL script includes or excludes the effect of this operator based on the "switching" parameter supplied.
Closing Comments:
If you want to see a better differentiation of results based on that last, dynamic SQL command, you might want to try scenarios where the first criteria is actually ambiguous, such as multiple instances if the ID column. (If ID = 1 was true for more than one record...) But of that set of results, have the second criteria identify something unique when in combination with the first.
Related
I have an Excel table with two columns of data. Column A are codes, column B are the corresponding country names. I turned it into an associative array, ueln_country.
Now the task is to update HORSE table's column COUNTRY_OF_RESIDENCE. Horses have a column UELN where first three letters correspond to the codes in the Excel table.
I have to check if the code exists in the Excel table. If it does, I have to update HORSE.country_of_residence with a CLASSIFICATOR.code where CLASSIFICATOR.name = **the corresponding country in column B** andCLASSIFICATOR.dom_code = 'ISOCODE'`.
First try gets the error
PLS-00201: identifier 'UELN' must be declared
As I understood, it's because I can only use declared variables in PL/SQL statement.
declare
type TBL_UELN_COUNTRY is table of varchar2(50) index by varchar2 (3);
test TBL_UELN_COUNTRY;
ueln_country TBL_UELN_COUNTRY;
begin
ueln_country('008') := 'ALBAANIA';
ueln_country('010') := 'ANTARKTIS';
ueln_country('011') := 'ANTARKTIS';
....
update HORSE
set COUNTRY_OF_RESIDENCE=
when (...dummy_case...) then
(select code from meta.classifcator
where dom_code = 'ISOCODE'
and name = ueln_country(substr(UELN, 1, 3)))
where UELN is not null;
end;
/
Second try.
So because of the first error I tried to somehow declare the variables.
I knew it wouldn't work (ORA-01422: exact fetch returns more than requested number of rows) but made it to show where my idea is going:
declare
type TBL_UELN_COUNTRY is table of varchar2(50) index by varchar2 (3);
test TBL_UELN_COUNTRY;
ueln_country TBL_UELN_COUNTRY;
v_ueln horse.UELN%TYPE;
begin
select UELN into v_ueln from HORSE;
ueln_country('008') := 'ALBAANIA';
ueln_country('010') := 'ANTARKTIS';
ueln_country('011') := 'ANTARKTIS';
....
update HORSE
set COUNTRY_OF_RESIDENCE=
when (...dummy_case...) then
(select code from meta.classifcator
where dom_code = 'ISOCODE'
and name = ueln_country(substr(v_ueln, 1, 3)))
where UELN is not null;
end;
/
So I want pick a value from associative array where the key = substr(specific_horse.UELN, 1, 3).
Searched through Google and Stack for hours and didn't find an answer.
The ugly and very slow working solution was just where I didn't make the associate array and made 400+ cases for every Excel table row in the form like when -key- then select code from meta.classificator where dom_code = 'ISOKOOD' and name = -value-
associative array can not be used in SQL.
If you use expression like array(index) in SQL then, in fact, PL/SQL engine gets value by index and then result is bound into SQL engine before execution of the SQL statement.
More specifically
declare
type TBL_UELN_COUNTRY is table of varchar2(50) index by varchar2 (3);
test TBL_UELN_COUNTRY;
dummy varchar2(30);
begin
test('GBP') := 'UK';
test('USD') := 'USA';
select /*+ qwerty */ test('GBP')
into dummy
from dual;
end;
/
If we check binds for a cursor we see that actual bind value has a type VARCHAR(128) - :B1. test('GBP') in PL/SQL code is passed as bind variable B1.
SQL> column sql_text format a50
SQL> select sbc.datatype_string, sql_text
2 from v$sql s join v$sql_bind_capture sbc
3 on s.sql_id = sbc.sql_id
4 where lower(sql_text) not like '%v$sql%'
5 and lower(sql_fulltext) like 'select %qwerty%';
DATATYPE_STRING SQL_TEXT
--------------- --------------------------------------------------
VARCHAR2(128) SELECT /*+ qwerty */ :B1 FROM DUAL
SQL engine knows nothing about associative array and apparently it cannot pass and index value to array and get an element of the array back.
If you still want to use associative array to look-up some values you can declare package variable and a getter function (you may also want to implement the logic to handle a case when there is no element in array for a given index - otherwise you'll get run-time exception in such case).
create or replace package pkg as
function GetCountry(idx in varchar2) return varchar2;
end pkg;
/
sho err
create or replace package body pkg as
type TBL_UELN_COUNTRY is table of varchar2(50) index by varchar2 (3);
test pkg.TBL_UELN_COUNTRY;
function GetCountry(idx in varchar2) return varchar2 as
begin return test(idx); end;
-- initializing
begin
test('GBP') := 'UK';
test('USD') := 'USA';
end pkg;
/
sho err
And finally
SQL> set serveroutput on
SQL> declare
2 dummy varchar2(30);
3 begin
4 with t(idx) as (select 'GBP' from dual)
5 select pkg.GetCountry(t.idx)
6 into dummy
7 from t;
8 dbms_output.put_line(dummy);
9 end;
10 /
UK
PL/SQL procedure successfully completed.
Tried the following code:
--------------- Setup ------------
drop table suk_rc_t1;
create table suk_rc_t1 (x number, y number);
insert into suk_rc_t1(x) values(1);
commit;
create or replace function suk_instn_id_wrap(
call_id pls_integer )
return pls_integer as
begin
dbms_output.put_line('suk_instn_id_wrap ' || call_id);
--return suk_instn_id;
return 123;
end;
/
--------------- How many RUNs of suk_instn_id_wrap in 2 queries below ? ------------
select 3 from suk_rc_t1
where (coalesce (y, suk_instn_id_wrap(1)) = suk_instn_id_wrap(2)
or suk_instn_id_wrap(3) is null);
begin
dbms_output.put_line('Done');
end;
/
with suk_rc_t1 as (select 1 x, null y from dual)
select 3 from suk_rc_t1
where (coalesce (y, suk_instn_id_wrap(1)) = suk_instn_id_wrap(2)
or suk_instn_id_wrap(3) is null);
begin
dbms_output.put_line('Done');
end;
/
I was expecting to get the same output. Instead I got:
suk_instn_id_wrap 3
suk_instn_id_wrap 1
suk_instn_id_wrap 2
Done
suk_instn_id_wrap 1
suk_instn_id_wrap 2
Done
Does anybody have an explanation for this behaviour?
It will be an optimizer thing, and although I can't say for certain I suspect the reasoning might be as follows:
In the first case, Oracle needs to read the database to access column y, which it would prefer not to do if it doesn't have to, so it prefers to evaluate suk_instn_id_wrap(3) first and avoid reading the database. Of course, it turns out to be false and so has to evaulate the first expression anyway. Bad luck.
In the second case, Oracle knows that y is null, so in that case neither side of the OR condition costs any more in terms of database access. In this case, perhaps it defaults to the original order of the expressions. You might think that the second condition would be better since there is only one function call, but perhaps that is not considered.
I'm trying to create my first oracle procedure. The select will return multiple records; I need to be able to place each record in the variables and use the record in later actions in the procedure. Any help please?
key number;
keyCount number;
rub varchar2(50);
srub varchar2(100);
type varchar2(200);
date varchar2(14);
note varchar2(500);
BEGIN
SELECT KEY,COUNT(KEY),RUB,
SRUB,TYPE ,DATE,NOTE FROM Student
WHERE S_KEY = {key};
END;
In PL/SQL we need to select results into matching variables. One way is separate variables for each column (as shown). The alternative is to use a row variable which matches the project of the query; find out more.
You've got an aggregating function - COUNT() so you need a GROUP BY clause which defines the non-aggregating columns. You say you have more than one record so you need to populate a collection not scalar variables. Find out more.
Your procedure should look something like this
create or replace procedure my_first_proc
( p_key in student.s_key%type )
as
type my_rec is record (
key number ,
keyCount number ,
rub varchar2(50); ,
srub varchar2(100) ,
type varchar2(200) ,
date varchar2(14),
note varchar2(500)
);
type my_rec_coll is table of my_rec;
l_student_recs my_rec_coll;
BEGIN
SELECT KEY,COUNT(KEY),RUB,SRUB,TYPE ,DATE,NOTE
bulk collect into l_student_recs
FROM Student
WHERE S_KEY = p_key
group by KEY,RUB,SRUB,TYPE ,DATE,NOTE
;
for idx in l_student_recs.first() .. l_student_recs.last()
loop
-- do some processing here
dbms_output.put_line('RUB = '||l_student_recs(idx).rub);
end loop;
EXCEPTION
when no_data_found then
raise_application_error(-01403, 'no student records for key='||p_key);
END;
Get into good habits:
use sensible variable names
distinguish parameter names from local variables
handle predictable exceptions
I have a procedure I am running from SQL developer. It pumps out about 50 columns. Currently I am working on a bug which is updating one of these columns. It is possible to just show column X from the result?
I am running it as
VARIABLE cursorout REFCURSOR;
EXEC MY_PROC('-1', '-1', '-1', 225835, :cursorout);
PRINT cursorout;
Ideally I want to print out the 20th column so would like to do something like
PRINT cursorout[20];
Thanks
It is possible to just show column X from the result?
Not without additional coding, no.
As #OldProgrammer said in the comment to your question you can use dbms_sql package to describe columns and pick one you like.
But, if, as you said, you know column names, the probably easiest way to display contents of that column would be using XML functions, xmlsequence() and extract() in particular.
Unfortunately we cannot pass SQL*PLUS bind variable as a parameter to the xmlsequence() function, so you might consider to wrap your procedure in a function, which returns refcursor:
Test table:
create table t1(col, col2) as
select level
, level
from dual
connect by level <= 5;
SQL> select * from t1;
COL COL2
---------- ----------
1 1
2 2
3 3
4 4
5 5
Here is a simple procedure, which opens a refcursor for us:
create or replace procedure p1(
p_cursor out sys_refcursor
) is
begin
open p_cursor for
select * from t1;
end;
/
Procedure created
Here is the function-wrapper for the p1 procedure, which simply executes the procedure and returns refcursor:
create or replace function p1_wrapper
return sys_refcursor is
l_res sys_refcursor;
begin
p1(l_res);
return l_res;
end;
/
Function created
The query. Extract path is ROW/COL2/text(), where COL2 is the name of a column we want to print.
select t.extract('ROW/COL2/text()').getstringval() as res
from table(xmlsequence(p1_wrapper)) t ;
Result:
RES
--------
1
2
3
4
5
5 rows selected.
In my opinion,you can define a cursor in procedure MY_PROC,and put which column is updated in the cursor(for example 20) and then return then cursor.Or you just create a table to record every execute result of your procedure.
Can I change the value of a variable by using a select into with the variable's original value as part of the where clause in the select statement?
EI would the following code work as expected:
declare
v_id number;
v_table number; --set elsewhere in code to either 1 or 2
begin
select id into v_id from table_1 where name = 'John Smith';
if(v_table = 2) then
select id into v_id from table_2 where fk_id = v_id;
end if;
end;
Should work. Have you tried it? Any issues?
After parsing your select statements should have bind variables where your v_id is. The substitution is made when the statement is actually executed.
Edit:
Unless you're sticking constants into your queries, Oracle will always parse them into statements with bind variables - it enables the DBMS to reuse the same basic query with multiple values without reparsing the statement - a huge performance gain. The whole idea of a bind variable is runtime substitution of values into a parsed query. Think of it this way: in order to process a query, all of the values need to be known. You send them to the engine, Oracle does it's work, and returns a result. It's a serial process with no way for the output value to step on the input one.