Something happens that I dont understand.
here is a type declared:
type my_type is record (
amount number(18,2),
my_date date
)
my_variable my_type
...
for my_variable in (select sum(table_amount), table_date from table group by table_date)
loop
dbms_output.put_line(my_variable.my_date);
dbms_output.put_line(my_variable.amount)
end loop;
my_variable.my_date works good but my_variable.amount triggers a PLS00302 error that I dont understand.
table structure:
table_date date,
table_amount number(10,2)
The loop declares a local variable my_variable that shadows the previous declaration. You need to reference the column names and not the record's attributes:
DECLARE
type my_type is record (
amount number(18,2),
my_date date
);
my_variable my_type;
BEGIN
for my_variable in (select table_amount, table_date from table_name)
loop
dbms_output.put_line(my_variable.table_date);
dbms_output.put_line(my_variable.table_amount);
end loop;
END;
/
Which is the same as:
DECLARE
type my_type is record (
amount number(18,2),
my_date date
);
my_variable my_type;
BEGIN
for not_my_variable in (select table_amount, table_date from table_name)
loop
dbms_output.put_line(not_my_variable.table_date);
dbms_output.put_line(not_my_variable.table_amount);
end loop;
END;
/
(But is slightly clearer as you do not have the cursor loop variable shadowing the declared variable.)
db<>fiddle here
You seem to be confused about explicit record types and implicit cursor for-loop record types. The my_type and my_variable you are declaring are never used. The my_variable in the cursor loop is a completely independent construct, that you just happen to have given the same name to. From the documentation that is the:
Name for the loop index that the cursor FOR LOOP statement implicitly declares as a %ROWTYPE record variable of the type that cursor or select_statement returns.
record is local to the cursor FOR LOOP statement. Statements inside the loop can reference record and its fields. They can reference virtual columns only by aliases. Statements outside the loop cannot reference record. After the cursor FOR LOOP statement runs, record is undefined.
So in a construct like:
for my_variable in (select sum(table_amount), table_date from table group by table_date)
the my_variable has the row type of the cursor query projection, and fields corresponding to the column expressions or their aliases.
You need to alias the sum(), and then refer to that alias, something like:
for my_variable in (
select sum(table_amount) as amount, table_date
from my_table
group by table_date
)
loop
dbms_output.put_line(my_variable.table_date);
dbms_output.put_line(my_variable.amount);
end loop;
db<>fiddle with some names changed to be more consistent with what you are describing. Note that the record type and variable declaration are not needed.
Related
I just want to ask a simple question, I have a procedure in my package: procedure(p_x in default sysdate). However, when i try to use this p_x to do query in my procdure, i got nothing from my table. My assumption is p_x does not get value from sysdate and it is null. Why does this happen? How can I fix it?
Thanks in advance.
In Package:
Procedure M_m(p_x in default sysdate)
In Package Body:
Procedure M_m(p_x in default sysdate) as
cursor cur is select manager_id, manager_name,manger_department
from employ_table where trunc(EmploymentDate)=trunc(p_x);
r_cur cur%rowtype;
Begin
for r_cur in cur
loop
DBMS_OUTPUT.PUT_LINE(employ_table.manager_id || employ_table.manager_name
|| employ_table.manager_department);
end loop;
end;
I guess Wernfried is right, it depends on how exactly you call it. Just add a DBMS_OUTPUT.PUT_LINE(p_x) immediately before the FOR to verify the value of the variable:
CREATE TABLE employ_table AS SELECT * FROM scott.emp;
UPDATE employ_table SET hiredate=SYSDATE WHERE ename='SCOTT';
CREATE OR REPLACE PACKAGE P IS
PROCEDURE M_m(p_x IN DATE DEFAULT sysdate);
END;
/
CREATE OR REPLACE PACKAGE BODY p IS
PROCEDURE M_m(p_x IN DATE DEFAULT sysdate) IS
CURSOR cur IS
SELECT * FROM employ_table WHERE TRUNC(hiredate)=TRUNC(p_x);
BEGIN
DBMS_OUTPUT.PUT_LINE('p_x='||p_x);
FOR r_cur IN cur LOOP
DBMS_OUTPUT.PUT_LINE(r_cur.mgr);
END LOOP;
END M_m;
END P;
/
EXEC P.m_m(DATE '1980-12-17');
p_x=1980-12-17 00:00:00
7902
EXEC P.m_m();
p_x=2018-05-26 17:50:11
7566
EXEC P.m_m(NULL);
p_x=
Currently (as of 18c) the default or := clause in a PLSQL parameter specification behaves like a pre-12c default clause in a table column definition. That is, the default value is only applied when the corresponding column is not specified at all.
If you define a table column as
somedate date default sysdate
then it will only be assigned the default value if the insert statement does not mention column somedate at all. If the insert sets somedate to null then it will be null, regardless of any default. The same behaviour is true of PL/SQL parameters. An explicit null value passed in overrides any default that you set for a parameter.
You will need to define a local constant along the lines of
k_x constant date := coalesce(p_x,sysdate);
and then have the rest of the procedure refer to that instead of the parameter.
In 12c we have default on null for table columns, but in my opinion PL/SQL lags behind, as it has no equivalent syntax for parameter defaults. If you pass null to a PL/SQL parameter, it respects that null and not any default that you specified, as it has no on null syntax option.
If you would like Oracle to extend PL/SQL parameter defaults along the same lines as table columns then consider voting for my suggestion here:
https://community.oracle.com/ideas/18189
It is probably a lame error but I do not find it.
In a procedure I have this :
PROCEDURE myProcedure(returnCode OUT NUMBER) IS
CURSOR myCursor IS
SELECT column1, column2, column3, column4 FROM MyTable WHERE columX IS NULL AND columnY = 'PS';
TYPE myType IS RECORD (
name1 MyTable.Column1%TYPE,
name2 MyTable.Column2%TYPE,
name3 MyTable.Column3%TYPE,
name4 MyTable.Column4%TYPE
);
myVar myType;
myVar2 typeA
BEGIN
FOR myVar IN myCursor
LOOP
myVar2 := myVar.name2;
END LOOP;
END;
ERROR :
PLS-00302 component name2 must be declared
What is wrong?
Ty
The myVar cursor loop variable is not related to your myVar record-type variable; it's scope effectively overrides the type definition. You'd get the same error if you removed that type.
From the documentation for cursor for loop:
The cursor FOR LOOP statement implicitly declares its loop index as a record variable of the row type that a specified cursor returns.
and from a related section:
The cursor FOR LOOP statement implicitly declares its loop index as a %ROWTYPE record variable of the type that its cursor returns. This record is local to the loop and exists only during loop execution.
With this syntax, myVar is implicitly the same rowtype as the cursor itself, and has a column2 field rather than a name2 field; so this works:
BEGIN
FOR myVar IN myCursor
LOOP
myVar2 := myVar.column2;
END LOOP;
END;
and the both the myVar variable declaration and myType type declaration are redundant.
To use your record type instead you would need more explicit cursor handling:
BEGIN
OPEN myCursor;
LOOP
FETCH myCursor INTO myVar;
EXIT WHEN myCursor%NOTFOUND;
myVar2 := myVar.name2;
END LOOP;
CLOSE myCursor;
END;
Now myVar is still your myType record type variable - nothing is overriding that - so it has the field names you specified.
You could also explicitly define myVar as myCursor%rowType to avoid needing your own record type, but then it's just a longer way of writing the first loop, and you'd need to go back to referencing myVar.column2.
I'm trying to use an array of input values to my procedure in an IN Clause as part of the where clause of a cursor. I know that this has been asked before, but I haven't seen how to make my syntax compile correctly.
In the package specification, the type is
TYPE t_brth_dt IS TABLE OF sourceTable.stdt_brth_dt%TYPE INDEX BY PLS_INTEGER;
sourceTable.std_brth_dt is a date column in the table.
Simplified version of my cursor is in the package body is -
cursor DataCursor_Sort( p_brth_dt in t_brth_dt) is
SELECT *
FROM sourceTable
WHERE a.brth_dt IN (select column_value
from table(p_brth_dt))
When I try to compile this, I'm getting the following errors.
[1]:(Error): PLS-00382: expression is of wrong type
[2]:(Error): PL/SQL: ORA-22905: cannot access rows from a non-nested table item
I know this looks similar to other questions, but I don't understand what the syntax error is.
In order to use collection defined as a nested table or an associative array in the from clause of a query you either should, as #Alex Poole correctly pointed out, create a schema level (SQL) type or use one, that is available to you trough ODCIConst package - odcidatelist as you intend to use a list of dates. For example, your cursor definition might look like this:
cursor DataCursor_Sort(p_brth_dt in sys.odcidatelist) is
select *
from sourceTable
where a.brth_dt IN (select column_value
from table(p_brth_dt))
OR
cursor DataCursor_Sort(p_brth_dt in sys.odcidatelist) is
select s.*
from sourceTable s
join table(p_brth_dt) t
on (s.brth_dt = t.column_value)
Note: You should take into consideration the time part of a date when performing a date comparison. If you want to compare date part only it probably would be useful to get rid of time part by using trunc() function.
It is possible to use a PL/SQL-defined nested table type (as opposed to a SQL-defined nested table type) indirectly in an IN clause of a SELECT statement in a PL/SQL package. You must use a PIPELINED function as an intermediary. It felt kind of clever to write, but I don't believe in its fundamental usefulness.
CREATE OR REPLACE PACKAGE so18989249 IS
TYPE date_plsql_nested_table_type IS TABLE OF DATE;
dates date_plsql_nested_table_type;
FUNCTION dates_pipelined RETURN date_plsql_nested_table_type PIPELINED;
PROCEDURE use_plsql_nested_table_type;
END so18989249;
/
CREATE OR REPLACE PACKAGE BODY so18989249 IS
FUNCTION dates_pipelined RETURN date_plsql_nested_table_type
PIPELINED IS
BEGIN
IF (dates.count > 0)
THEN
FOR i IN dates.first .. dates.last
LOOP
IF (dates.exists(i))
THEN
PIPE ROW(dates(i));
END IF;
END LOOP;
END IF;
END;
PROCEDURE use_plsql_nested_table_type IS
BEGIN
dates := NEW date_plsql_nested_table_type();
-- tweak these values as you see fit to produce the dbms_output results you want
dates.extend(5);
dates(1) := DATE '2013-12-25';
dates(2) := DATE '2013-01-01';
dates(3) := DATE '2013-07-01';
dates(4) := DATE '2013-09-03';
dates(5) := DATE '2008-11-18';
FOR i IN (SELECT o.owner,
o.object_name,
o.object_type,
to_char(o.last_ddl_time, 'YYYY-MM-DD') AS last_ddl
FROM all_objects o
WHERE trunc(o.last_ddl_time) IN
(SELECT column_value FROM TABLE(dates_pipelined))
--uses pipeline function which uses pl/sql-defined nested table
)
LOOP
dbms_output.put_line('"' || i.owner || '"."' || i.object_name || '" ("' || i.object_type || ') on ' || i.last_ddl);
END LOOP;
END;
END so18989249;
/
begin so18989249.use_plsql_nested_table_type; end;
/
The type has to be created at SQL level, not in a package. An SQL query doesn't know how to use any types defined in PL/SQL. So you'd have to do:
CREATE OR REPLACE TYPE t_brth_dt IS TABLE OF date;
/
... and remove the type from your package specification. (Or give them different names, at least, and they won't be interchangeable in use). Because it's at SQL level, you also can't use sourceTable.stdt_brth_dt%TYPE in the declaration, unfortunately.
I am using SQL*Plus. When I am using the below query, it is giving error
Error report:
ORA-06550: line 4, column 1:
PLS-00428: an INTO clause is expected in this SELECT statement
Query
declare
id varchar2(80) :='test123';
begin
select test_quote,test_id from order_link where id = 'test123';
end;
Not sure why you're using a PL/SQL block for that. You aren't using the id you declare, and it would be better to give it a name different to the column name to avoid confusion.
You can declare a bind variable in SQL*Plus though, and select into that:
var l_test_quote varchar2(80); -- or whatever type/size you need
var l_test_id varchar2(80);
declare
l_id varchar2(80) :='test123';
begin
select test_quote, test_id
into :l_test_quote, :l_test_id
from order_link
where id = l_id;
end;
/
print l_test_quote
print l_test_id
Note the : before the references to the variables defined outside the block, indicating they are bind variables. l_id is declared inside the block so it does not have a preceding :.
In this case you could also define l_id outside the block, and avoid PL/SQL while still using a bind variable for that:
var l_id varchar2(80);
exec :l_id := 'test123';
select test_quote, test_id
from order_link
where id = :l_id;
Because the main query isn't PL/SQL any more (although the exec is; that's just a shorthand for a one-line anonymous block), you don't need to select ... into so you don't need to declare those variables.
try this:
declare
id varchar2(80) :='test123';
v_test_quote order_link.test_quote%type;
v_test_id order_link.test_id%type;
begin
select test_quote,test_id
into v_test_qoute, v_test_id
from order_link
where id = 'test123';
end;
In the SQL Server, I can define local variables like this.
declare #id number := 1000
select * from tbl_A where id = #id;
select * from tbl_B where id = #id;
It is very convenient.
I tried to do same thing in PL/SQL but it doesn't work.
DECLARE id number;
select 1000 into id from dual;
Do you know how to do something similar? The simplest method is my objective.
If you want to define a local variable in PL/SQL, you need a complete PL/SQL block
DECLARE
id NUMBER;
BEGIN
SELECT 1000
INTO id
FROM dual;
END;
or just
DECLARE
id NUMBER := 1000;
BEGIN
<<do something that uses the local variable>>
END;
If you want to declare a variable in SQL*Plus
SQL> variable id number
SQL> begin
select 1000 into :id from dual;
end;
/
SQL> print id
ID
----------
1000
SQL> SELECT * FROM tbl_a WHERE id = :id
An alternative to DECLARE Block is to use a WITH Clause:
WITH my_params AS (
SELECT 123 AS min_id FROM DUAL
)
SELECT *
FROM some_table
WHERE id > (SELECT min_id FROM my_params)
It is more portable as many vendors support the WITH clause and you can change seamless from parameter to dynamic value. For example:
WITH my_params AS (
SELECT min(id) AS min_id FROM some_id_table
)
SELECT *
FROM some_table
WHERE id > (SELECT min_id FROM my_params)
Solution for Oracle SQL
DEF x = foo
SELECT '&x' FROM dual;
The result will be : foo
NB: The variable will keep the value even after execution. To clear variable run UNDEFINE x.
General syntax to declare variable in PL/SQL is
var_nm datatype [NOT NULL := var_value ];
var_nn is the name of the variable.
datatype is a valid PL/SQL datatype.
NOT NULL is an optional specification on the variable which this variable cannot be assigned null value.
var_value or DEFAULT value is also an optional specification, where you can initialize a variable with some specific value.
Each variable declaration is a separate statement and must be terminated by a semicolon.
We can assign value to variables in one of the following two ways -
direct assignment (Eg. var_nm:= var_value;)
Using select from (Eg. SELECT col_nm INTO var_nm FROM tbl_nm [WHERE clause];)
In you case as Justin Cave has already mentioned it can be
DECLARE
id number;
BEGIN
SELECT 1000 into id from dual;
dbms_output.put_line('id : '|| id );
END;
/
OR
DECLARE
id number := 1000;
BEGIN
dbms_output.put_line('id : '|| id );
END;
/
NOTE: '/' i.e Back slash after END keyword indicates to execute the above PL/SQL Block.
(Just stumbled across this thread.) Beginning with SQL*Plus 12.2, you can declare and assign a value at the same time:
SQL> var id number = 1000
SQL> select * from tbl_A where id = :id;
Oracle calls it input binding.
If you must do it in PL/SQL, the answer was given by others.