In the following minimal PL/SQL query example, I can't get the query to work, in order to filter records from a table based on a certain date, which is stored in a variable.
The goal obviously is to end up with different query results, by changing the date variable to some other moment, different from sysdate.
This query does succeed in printing the calculation date variable's content, but the select query afterwards fails with ORA-06550: line 6, column 3 (so on 'select *' below) : PLS-00428: an INTO clause is expected in this SELECT statement.
DECLARE
v_calculation_date date := sysdate;
BEGIN
dbms_output.put_line(v_calculation_date);
select *
from t
WHERE to_number(to_char(trunc(t.some_date), 'YYYYMM'))
BETWEEN to_number(to_char(add_months(v_calculation_date,-24), 'YYYYMM'))
AND to_number(to_char(add_months(v_calculation_date,-1), 'YYYYMM'));
END;
OK, with a SELECT INTO then... This again prints the calculation date variable's content just fine, but the select query afterwards again fails, with the same error:
DECLARE
v_calculation_date date;
BEGIN
select sysdate into v_calculation_date from dual; -- dual is a dummy table with only one row and one column, just what we need here.
dbms_output.put_line(v_calculation_date);
select *
from t
WHERE to_number(to_char(trunc(t.some_date), 'YYYYMM'))
BETWEEN to_number(to_char(add_months(v_calculation_date,-24), 'YYYYMM'))
AND to_number(to_char(add_months(v_calculation_date,-1), 'YYYYMM'));
END;
I already have a SELECT INTO, why is the normal SELECT, which follows afterwards, then refused?
For the sake of completeness: this is a minimal query example for easy reproduction of the problem. My actual query is more complex, I eventually want something like this to work:
DECLARE
v_calculation_date date;
BEGIN
DROP TABLE t1;
CREATE TABLE t1 as (
WITH intermediary1 AS (
-- The actual query from above, as a subquery
SELECT *
FROM t
WHERE to_number(to_char(trunc(t.some_date), 'YYYYMM'))
BETWEEN to_number(to_char(add_months(v_calculation_date,-24), 'YYYYMM'))
AND to_number(to_char(add_months(v_calculation_date,-1), 'YYYYMM'));
)
)
/* other drops, creates, inserts ... */
END
What is wrong in my query?
Execution info: the SQL dialect is PL/SQL, the IDE is PL/SQL Developer version 13.0, the database server runs Oracle Database 12c Enterprise Edition Release 12.2.
In PL/SQL, every SELECT (that isn't part of a cursor) must have an INTO clause. Therefore, it's not enough that one of your SELECTs has it.
As of your final code (the one that contains CREATE TABLE): that won't work. DDL can't be executed from PL/SQL, unless you use dynamic SQL (execute immediate). Therefore, all your "other drops, creates, ..." will also have to be dynamic.
Note that we usually do not create objects dynamically; create table at SQL level. Then, if you want to populate it with different data, do so (either from SQL or PL/SQL). First delete "old" data - you can use delete or truncate (but it is DDL so - as you already know by now - it requires execute immediate), and then re-populate the table.
Dynamic SQL is difficult to debug. Don't use it if you don't have to.
If you're concerned about your own data so that other users wouldn't interfere, consider creating a global temporary table (or private; depending on database version you use).
[EDIT, based on your comments]
I'm afraid you're misinterpreting reality. DDL can't be executed in PL/SQL unless it is dynamic SQL. Your "final" code is an anonymous PL/SQL block (I'm talking about the last code you posted, the one that begins with the DECLARE, contains DROP and CREATE table t1) so it can not execute those statements as you posted.
Here's a demo. I don't have your tables so I'll mimic what you're doing, using Scott's sample DEPT table instead on my 11gXE database.
Does the WITH factoring clause work? Yes.
SQL> with intermediary1 as (
2 -- The actual query from above, as a subquery
3 select *
4 from dept
5 where 1 = 1
6 )
7 select * from intermediary1;
DEPTNO DNAME LOC
---------- -------------- -------------
10 ACCOUNTING NEW YORK
20 RESEARCH DALLAS
30 SALES CHICAGO
40 OPERATIONS BOSTON
Does it work with the CREATE TABLE? Not on my 11gXE:
SQL> create table t1 as (
2 with intermediary1 as (
3 -- The actual query from above, as a subquery
4 select *
5 from dept
6 where 1 = 1
7 )
8 select * from intermediary1
9 );
)
*
ERROR at line 9:
ORA-32034: unsupported use of WITH clause
I tried it on apex.oracle.com which runs 18cEE - doesn't work either.
Maybe your database version supports it, can't tell as I don't have any higher database version to try it on.
But OK, doesn't matter, that's easily fixed by moving a CTE into a subquery:
SQL> create table t1 as
2 select *
3 from (-- The actual query from above, as a subquery
4 select *
5 from dept
6 where 1 = 1
7 );
Table created.
So, everything is OK with the query itself, it won't cause my PL/SQL block to fail because of it.
OK, so - let's try it, the way you say you use frequently.
SQL> declare
2 v_calculation_date date;
3 begin
4 drop table t1;
5
6 create table t1 as
7 select *
8 from (-- The actual query from above, as a subquery
9 select *
10 from dept
11 where 1 = 1
12 );
13 /* other drops, creates, inserts ... */
14 end;
15 /
drop table t1;
*
ERROR at line 4:
ORA-06550: line 4, column 3:
PLS-00103: Encountered the symbol "DROP" when expecting one of the following:
( begin case declare exit for goto if loop mod null pragma
raise return select update while with <an identifier>
<a double-quoted delimited-identifier> <a bind variable> <<
continue close current delete fetch lock insert open rollback
savepoint set sql execute commit forall merge pipe purge
SQL>
Whooops! There's an error on DROP. Let's remove it and leave CREATE TABLE:
SQL> declare
2 v_calculation_date date;
3 begin
4 -- drop table t1;
5
6 create table t1 as
7 select *
8 from (-- The actual query from above, as a subquery
9 select *
10 from dept
11 where 1 = 1
12 );
13 /* other drops, creates, inserts ... */
14 end;
15 /
create table t1 as
*
ERROR at line 6:
ORA-06550: line 6, column 3:
PLS-00103: Encountered the symbol "CREATE" when expecting one of the following:
( begin case declare exit for goto if loop mod null pragma
raise return select update while with <an identifier>
<a double-quoted delimited-identifier> <a bind variable> <<
continue close current delete fetch lock insert open rollback
savepoint set sql execute commit forall merge pipe purge
SQL>
Whoops #2! Error on CREATE TABLE. Looks like your way doesn't really work. Let's try it my way, using dynamic SQL:
SQL> declare
2 v_calculation_date date;
3 begin
4 execute immediate 'drop table t1';
5
6 execute immediate (
7 'create table t1 as
8 select *
9 from (-- The actual query from above, as a subquery
10 select *
11 from dept
12 where 1 = 1
13 )'
14 );
15 /* other drops, creates, inserts ... */
16 end;
17 /
PL/SQL procedure successfully completed.
SQL> select * from t1;
DEPTNO DNAME LOC
---------- -------------- -------------
10 ACCOUNTING NEW YORK
20 RESEARCH DALLAS
30 SALES CHICAGO
40 OPERATIONS BOSTON
SQL>
Now it works.
I'd be more than happy if you demonstrate the way you're doing it without dynamic SQL. Please, post your own SQL*Plus session (just like I did). Or, if you use some GUI, post a screenshot, no problem.
You do not need a PL/SQL block for your query and can simplify it to:
SELECT *
FROM t
WHERE ADD_MONTHS(TRUNC(SYSDATE, 'MM'), -24) <= some_date
AND some_date < TRUNC(SYSDATE, 'MM');
If you want it in a PL/SQL block then you want to use SELECT ... INTO ... if the query is going to return a single row:
DECLARE
v_calculation_date DATE := SYSDATE;
v_col1 T.COL1%TYPE;
v_col2 T.COL2%TYPE;
v_some_date T.SOME_DATE%TYPE;
BEGIN
SELECT col1, col2, some_date
INTO v_col1, v_col2, v_some_date
FROM t
WHERE ADD_MONTHS(TRUNC(v_calculation_date, 'MM'), -24) <= some_date
AND some_date < TRUNC(v_calculation_date, 'MM');
-- Do something with the variables.
DBMS_OUTPUT.PUT_LINE( v_col1 || ', ' || v_col2 || ', ' || v_some_date );
END;
/
If you can return multiple rows then use SELECT ... BULK COLLECT INTO ... or use a cursor:
DECLARE
v_calculation_date DATE := SYSDATE;
BEGIN
FOR cur IN (
SELECT *
FROM t
WHERE ADD_MONTHS(TRUNC(v_calculation_date, 'MM'), -24) <= some_date
AND some_date < TRUNC(v_calculation_date, 'MM')
)
LOOP
-- Do something with the cursor.
DBMS_OUTPUT.PUT_LINE( cur.col1 || ', ' || cur.col2 || ', ' || cur.some_date );
END LOOP;
END;
/
db<>fiddle here
Again, for your more complicated query, you do not need PL/SQL (and cannot have DDL statements in a PL/SQL context):
DROP TABLE t1;
CREATE TABLE t1 as
SELECT *
FROM t
WHERE ADD_MONTHS(TRUNC(SYSDATE, 'MM'), -24) <= some_date
AND some_date < TRUNC(SYSDATE, 'MM');
Or, if you want to use PL/SQL then do the DDL statements in the SQL context and then switch to PL/SQL for the DML statements:
DROP TABLE t1;
CREATE TABLE t1 AS
SELECT * FROM t WHERE 1 = 0;
-- or
-- TRUNCATE TABLE t1;
DECLARE
v_calculation_date DATE := SYSDATE;
BEGIN
INSERT INTO t1
SELECT *
FROM t
WHERE ADD_MONTHS(TRUNC(v_calculation_date, 'MM'), -24) <= some_date
AND some_date < TRUNC(v_calculation_date, 'MM');
END;
/
Is there any limit on the number of aggregate Functions on different columns in a query?
example
SELECT SUM(col1), AVG(col2), SUM(col3), ... FROM table;
DB- Oracle
and if yes, why?
There's none, as far as I can tell. Why do you think there is, or that there should be?
Oracle limits e.g. number of columns in a table to 1000 (as of 19c version), but - this is a SELECT column list, no matter you use aggregates on different columns.
I did a test (and so can you) on Scott's sample schema:
create table test (col clob);
declare
l_str varchar2(32000) := 'select ';
l_col varchar2(20);
begin
for i in 1 .. 1010 loop
l_col := case when substr(i, -1) = 1 then 'sum(sal)'
when substr(i, -1) = 3 then 'avg(empno)'
when substr(i, -1) = 5 then 'max(job)'
else 'min(deptno)'
end;
l_str := l_str || l_col ||',';
end loop;
l_str := rtrim(l_str, ',') || ' from emp';
insert into test values (l_str);
end;
/
select * From test;
Result is such a looooong string (1010 columns in total):
select sum(sal),min(deptno),avg(empno),min(deptno),max(job), ... from emp
which returns a result. For demonstration purposes, I shortened the query as it won't work in SQL*Plus because of "SP2-0027: Input is too long (> 2499 characters) - line ignored". It runs OK with 1010 columns in e.g. TOAD:
SQL> select sum(sal),min(deptno),avg(empno),min(deptno),max(job),min(deptno) from emp;
SUM(SAL) MIN(DEPTNO) AVG(EMPNO) MIN(DEPTNO) MAX(JOB) MIN(DEPTNO)
---------- ----------- ---------- ----------- --------- -----------
29025 10 7726,57143 10 SALESMAN 10
SQL>
So, no problem.
suppose table_1 consists of one column(table_no) and select * from table_1 gives you 5 values of table_no. now i want to store first two values of table_no into two different variables. How am i supposed to do that?
Try using MIN and MAX functions, if you need only 2 values. Like this:
DECLARE
var1 NUMBER;
var2 NUMBER;
BEGIN
SELECT MIN(table_no), MAX(table_no) INTO var1, var2 FROM
(SELECT table_no FROM (SELECT table_no FROM table_1 ORDER BY table_no )
WHERE rownum <= 2);
END;
SQL> create table t as select trunc(dbms_random.value(1,20)) x from dual connect by level <= 5;
Table created.
SQL> select * from t;
X
----------
10
13
8
10
16
SQL> select
2 max(decode(rn,1,x)),
3 max(decode(rn,2,x))
4 from
5 ( select row_number() over ( order by x ) rn, x from t ) ;
MAX(DECODE(RN,1,X)) MAX(DECODE(RN,2,X))
------------------- -------------------
8 10
Is this the best way to handle this insert, such that columns test1 and test2 are always equal?
CREATE SEQUENCE seq_temp
START WITH 1
INCREMENT BY 1
CACHE 50
NOCYCLE;
CREATE TABLE test_tbl (
test1 NUMBER,
test2 NUMBER
);
INSERT INTO test_tbl
SELECT
seq_temp.nextval,
seq_temp.currval
FROM dual;
It makes no difference - you must use nextval for at least one column, but for the other column nextval and currval will be the same value.
In other words, all the following statements will have the same effect:
SELECT seq_temp.nextval, seq_temp.currval FROM dual;
SELECT seq_temp.nextval, seq_temp.nextval FROM dual;
SELECT seq_temp.currval, seq_temp.nextval FROM dual;
I am using plsql in a oracle database 9i
I have a stored procedure,
With an in parameter of "table of number" that is called numbers.
I now want to select all rows from a table where a column named: ID is equal to a number inside "numbers"
Just like I can do select * from table name where Id in (!,!,!,...)
Thanks for the help.
Update :
Just to clear up,
I have a user defined type named numbers,
Number is defined: table of number.
So in the procedure decleration I have
"P_array in numbers"
I need to select * from a table where Id is found in p_array
like this?
SQL> create type numbers as table of number;
2 /
Type created.
SQL> create table foo (id number) ;
Table created.
SQL> insert into foo select rownum from dual connect by level <= 10;
10 rows created.
SQL> select * from foo;
ID
----------
1
2
3
4
5
6
7
8
9
10
10 rows selected.
SQL> create procedure testnum(p_num in numbers)
2 is
3 begin
4 for r_row in (select id
5 from foo f
6 where f.id in (select /*+ cardinality(t, 10) */ column_value
7 from table(p_num) t))
8 loop
9 dbms_output.put_line(r_row.id);
10 end loop;
11 end;
12 /
Procedure created.
SQL> set serverout on
SQL> exec testnum(numbers(2, 6, 9));
2
6
9
the cardinality hint is used to tell oracle roughly how many elements are in your table. without it, Oracle will assume ~8k rows which may be too high and cause unwanted full scans in the plans.
you can do a direct join too if you prefer.
for r_row in (select /*+ cardinality(t, 10) */ f.id
from foo f
inner join table(p_num) t
on t.column_value = f.id)
try the following :-
select * from tableName where id in (select c.column_value from table(cast(p_array as numbers)) c);
where numbers is table of number
SELECT * FROM A_TABLE WHERE ID IN (SELECT SAVED_NUMBERs FROM TABLE_NUMBER);
Do you mean this?