I want to select multiple rows and also update all selected rows. so this goal i wrote this query. but when execute it throw exception.
I wrote this query in a producer like bellow.
PROCEDURE get_rows(
a_cursor OUT SYS_REFCURSOR,
a_id IN VARCHAR,
a_count IN NUMBER);
exception detail:
java.sql.SQLException: ORA-01002: fetch out of sequence
a_cursor is SYS_REFCURSOR
OPEN a_cursor FOR
SELECT mytable.VID
FROM mytable
WHERE ROWNUM <= COUNT FOR UPDATE;
loop
FETCH a_cursor INTO a_id;
if a_cursor %notfound then
cnumber := 9999;
else
UPDATE mytable SET
...
WHERE VID = a_vid;
COMMIT;
end if;
end loop;
A sys_refcursor cannot be used in update statement. You can use an explicit cursor as shown below. Use this way:
DECLARE
cursor a_cursor is
SELECT mytable.VID
FROM mytable
WHERE ROWNUM <= COUNT FOR UPDATE;
a_id number;
begin
OPEN a_cursor;
loop
FETCH a_cursor INTO a_id;
exit when a_cursor%notfound;
UPDATE mytable SET
...
WHERE VID = a_vid;
end loop;
COMMIT;
close a_cursor;
end;
Edit:
create or replace PROCEDURE get_rows(
a_cursor OUT SYS_REFCURSOR,
a_id IN VARCHAR,
a_count IN NUMBER)
IS
cursor a_cur is
SELECT mytable.VID
FROM mytable
WHERE ROWNUM <= a_COUNT ;
a_id NUMBER;
cnumber number;
BEGIN
OPEN a_cur;
LOOP
FETCH a_cur INTO a_id;
IF a_cur%notfound THEN
cnumber := 9999;
End if;
exit when a_cursor%notfound;
UPDATE mytable SET
...
WHERE VID = a_vid;
END loop;
COMMIT;
CLOSE a_cur;
Open a_cursor for select * from mytable;
end ;
If you are not doing any other processing in the loop, you can use a MERGE statement instead of a cursor:
SQL Fiddle
Oracle 11g R2 Schema Setup:
CREATE TABLE mytable ( vid, columna ) AS
SELECT 1, 'a' FROM DUAL UNION ALL
SELECT 2, 'a' FROM DUAL UNION ALL
SELECT 3, 'a' FROM DUAL UNION ALL
SELECT 4, 'a' FROM DUAL UNION ALL
SELECT 5, 'a' FROM DUAL UNION ALL
SELECT 6, 'a' FROM DUAL;
Query 1:
MERGE INTO mytable dst
USING (
SELECT VID /* or ROWID AS rid */
FROM mytable
WHERE ROWNUM <= 3
) src
ON ( src.VID = dst.VID /* or src.RID = dst.ROWID */ )
WHEN MATCHED THEN
UPDATE SET
columna = 'b'
Query 2:
SELECT * FROM mytable
Results:
| VID | COLUMNA |
|-----|---------|
| 1 | b |
| 2 | b |
| 3 | b |
| 4 | a |
| 5 | a |
| 6 | a |
Related
I will try to explain this the best that I can, I have an array of products ['VALUE1','VALUE2']
my table has this data as an example
lets say the values are
product_id
order_qty
VALUE1
5
VALUE2
3
How can I build a select statement to check the table if product_id equals VALUE1 and VALUE2 but if the array contains VALUE3 it returns false else it's true I know using a function would be better.
This is how I understood it.
SQL> set serveroutput on;
Sample table:
SQL> select * from product;
PRODUC ORDER_QTY
------ ----------
VALUE1 5
VALUE2 3
PL/SQL code; it intersects values from the table with contents of collection. If that result is equal to number of values in collection, result is TRUE:
SQL> declare
2 l_arr sys.odcivarchar2list := sys.odcivarchar2list();
3 l_cnt number;
4 begin
5 l_arr.extend;
6 l_arr(1) := 'VALUE1';
7 l_arr.extend;
8 l_arr(2) := 'VALUE2';
9
10 select count(*)
11 into l_cnt
12 from (select product_id from product
13 intersect
14 select column_value
15 from table(l_arr)
16 );
17 dbms_output.put_line(l_cnt);
18 if l_cnt = l_arr.count then
19 dbms_output.put_line('Result is TRUE');
20 else
21 dbms_output.put_line('Result is FALSE');
22 end if;
23 end;
24 /
2
Result is TRUE
PL/SQL procedure successfully completed.
What if we add VALUE3 to collection? Result is then FALSE.
SQL> declare
2 l_arr sys.odcivarchar2list := sys.odcivarchar2list();
3 l_cnt number;
4 begin
5 l_arr.extend;
6 l_arr(1) := 'VALUE1';
7 l_arr.extend;
8 l_arr(2) := 'VALUE2';
9 l_arr.extend;
10 l_arr(3) := 'VALUE3';
11
12 select count(*)
13 into l_cnt
14 from (select product_id from product
15 intersect
16 select column_value
17 from table(l_arr)
18 );
19 if l_cnt = l_arr.count then
20 dbms_output.put_line('Result is TRUE');
21 else
22 dbms_output.put_line('Result is FALSE');
23 end if;
24 end;
25 /
Result is FALSE
PL/SQL procedure successfully completed.
SQL>
SELECT
CASE WHEN
COUNT(CASE WHEN PRODUCT_ID IN ('VALUE1','VALUE2') THEN 1 END) = COUNT(*)
THEN 'TRUE' ELSE 'FALSE'
END CHK
FROM T;
or you can just compare collections (NESTED TABLE):
create table t_product as
select 'VALUE1' product_id from dual union all
select 'VALUE2' product_id from dual union all
select 'VALUE3' product_id from dual;
declare
type string_table is table of t_product.product_id%type;
arr_input string_table:=string_table('VALUE1','VALUE2');
tab_data string_table;
begin
select product_id bulk collect into tab_data from t_product;
if tab_data = arr_input
then
dbms_output.put_line('yes');
else
dbms_output.put_line('no');
end if;
end;
/
Or:
select
case
when sys.ku$_vcnt('VALUE1','VALUE2') = X
then 'true'
else 'false'
end chk
from (select cast(collect(cast(product_id as varchar2(4000))) as sys.ku$_vcnt) x from t_product);
NB: it's better to create and use own type instead of sys.ku$_vcnt, for example:
create or replace type varchar2_table as table of varchar2(4000);
I have tables t1 and t2 and both of them do have columns created_on containing the timestamp when each record was created. Usual thing.
Now I'd like to create the view which would show my tables and the timestamp of last created record (MAX(created_on)) in corresponding table.
The result should look like:
table | last_record
======+============
t1 | 10.05.2019
t2 | 12.11.2020
For example I can retrieve the list of my tables with:
SELECT * FROM USER_TABLES WHERE table_name LIKE 'T%'
I'd like to get the timestamp of last record for each of these tables.
How to create this view?
It might depend on tables' description; I presume they are somehow related to each other.
Anyway: here's how I understood the question. Read comments within code.
SQL> with
2 -- sample data
3 t1 (id, name, created_on) as
4 (select 1, 'Little', date '2021-12-14' from dual union all --> max for Little
5 select 2, 'Foot' , date '2021-12-13' from dual union all --> max for Foot
6 select 2, 'Foot' , date '2021-12-10' from dual
7 ),
8 t2 (id, name, created_on) as
9 (select 2, 'Foot' , date '2021-12-09' from dual union all
10 select 3, 'SBrbot', date '2021-12-14' from dual --> max for SBrbot
11 )
12 -- query you'd use for a view
13 select id, name, max(created_on) max_created_on
14 from
15 -- union them, so that it is easier to find max date
16 (select id, name, created_on from t1
17 union all
18 select id, name, created_on from t2
19 )
20 group by id, name;
ID NAME MAX_CREATE
---------- ------ ----------
1 Little 14.12.2021
2 Foot 13.12.2021
3 SBrbot 14.12.2021
SQL>
After you fixed the question, that's even easier; view query begins at line #12:
SQL> with
2 -- sample data
3 t1 (id, name, created_on) as
4 (select 1, 'Little', date '2021-12-14' from dual union all
5 select 2, 'Foot' , date '2021-12-13' from dual union all
6 select 2, 'Foot' , date '2021-12-10' from dual
7 ),
8 t2 (id, name, created_on) as
9 (select 2, 'Foot' , date '2021-12-09' from dual union all
10 select 3, 'SBrbot', date '2021-12-14' from dual
11 )
12 select 't1' source_table, max(created_on) max_created_on from t1
13 union
14 select 't2' source_table, max(created_on) max_created_on from t2;
SO MAX_CREATE
-- ----------
t1 14.12.2021
t2 14.12.2021
SQL>
If it has to be dynamic, one option is to create a function that returns ref cursor:
SQL> create or replace function f_max
2 return sys_refcursor
3 is
4 l_str varchar2(4000);
5 rc sys_refcursor;
6 begin
7 for cur_r in (select distinct c.table_name
8 from user_tab_columns c
9 where c.column_name = 'CREATED_ON'
10 order by c.table_name
11 )
12 loop
13 l_str := l_str ||' union all select ' || chr(39) || cur_r.table_name || chr(39) ||
14 ' table_name, max(created_on) last_updated from ' || cur_r.table_name;
15 end loop;
16
17 l_str := ltrim(l_str, ' union all ');
18
19 open rc for l_str;
20 return rc;
21 end;
22 /
Function created.
Testing:
SQL> select f_max from dual;
F_MAX
--------------------
CURSOR STATEMENT : 1
CURSOR STATEMENT : 1
TA LAST_UPDAT
-- ----------
T1 14.12.2021
T2 14.12.2021
SQL>
I have 30+ tables and wanted avoid hard coding SELECT statements for each of these tables and UNION them all. I expected some solution where I would insert tablenames in kinda array and create JOIN to show result with all last records. I know problem is here that tablename is variable!
You cannot do this in SQL as a VIEW is set at compile time and the tables must be known; the best you can do is to dynamically create an SQL statement in PL/SQL and then use EXECUTE IMMEDIATE and then re-run it if you want to recreate the view:
DECLARE
v_tables SYS.ODCIVARCHAR2LIST := SYS.ODCIVARCHAR2LIST(
'TABLE1', 'TABLE2', 'table3', 'TABLE5'
);
v_sql CLOB := 'CREATE OR REPLACE VIEW last_dates (table_name, last_date) AS ';
BEGIN
FOR i in 1 .. v_tables.COUNT LOOP
IF i > 1 THEN
v_sql := v_sql || ' UNION ALL ';
END IF;
v_sql := v_sql || 'SELECT '
|| DBMS_ASSERT.ENQUOTE_LITERAL(v_tables(i))
|| ', MAX(created_on) FROM '
|| DBMS_ASSERT.ENQUOTE_NAME(v_tables(i), FALSE);
END LOOP;
EXECUTE IMMEDIATE v_sql;
END;
/
Then for the sample tables:
CREATE TABLE table1 (created_on) AS
SELECT SYSDATE - LEVEL FROM DUAL CONNECT BY LEVEL <= 3;
CREATE TABLE table2 (created_on) AS
SELECT SYSDATE - LEVEL FROM DUAL CONNECT BY LEVEL <= 3;
CREATE TABLE "table3" (created_on) AS
SELECT SYSDATE - LEVEL FROM DUAL CONNECT BY LEVEL <= 3;
CREATE TABLE table5 (created_on) AS
SELECT SYSDATE FROM DUAL;
After running the PL/SQL block, then:
SELECT * FROM last_dates;
Outputs:
TABLE_NAME
LAST_DATE
TABLE1
2021-12-13 13:21:58
TABLE2
2021-12-13 13:21:59
table3
2021-12-13 13:21:59
TABLE5
2021-12-14 13:21:59
db<>fiddle here
I don't know what is best solution for my problem. I need function with one parameter which resault is
VAL
-----
1
2
3
In function I need put union all to get all value.
Select column_1 as VAL from my_table where id = P_FUNCTION_PARAMETER --return 1
union all
Select column_2 as VAL from my_table where id = P_FUNCTION_PARAMETER --return 2
union all
Select column_3 as VAL from my_table where id = P_FUNCTION_PARAMETER; --return 3
What is the best solution for this?
"Best" just depends. Return a ref cursor or a collection, whichever you prefer.
For example:
SQL> create or replace function f_test_rc
2 return sys_refcursor
3 is
4 rc sys_refcursor;
5 begin
6 open rc for
7 select 1 from dual union all
8 select 2 from dual union all
9 select 3 from dual;
10
11 return rc;
12 end;
13 /
Function created.
SQL> select f_test_rc from dual;
F_TEST_RC
--------------------
CURSOR STATEMENT : 1
CURSOR STATEMENT : 1
1
----------
1
2
3
SQL> create or replace function f_test_coll
2 return sys.odcinumberlist
3 as
4 l_coll sys.odcinumberlist;
5 begin
6 select * bulk collect into l_coll
7 from (select 1 from dual union all
8 select 2 from dual union all
9 select 3 from dual
10 );
11
12 return l_coll;
13 end;
14 /
Function created.
SQL> select * from table(f_test_coll);
COLUMN_VALUE
------------
1
2
3
SQL>
First let's set up a small table for testing:
create table my_table
( id number primary key
, column_1 number
, column_2 number
, column_3 number
);
insert into my_table
select 1008, 3, -8, 0.2 from dual union all
select 1002, 6, null, -1.2 from dual
;
commit;
The function can look like this. Note that I don't use union all - that will require reading the table three times, when only one time is enough.
create or replace function my_function (p_function_parameter number)
return sys.odcinumberlist
as
arr sys.odcinumberlist;
begin
select case ord when 1 then column_1
when 2 then column_2
when 3 then column_3 end
bulk collect into arr
from my_table cross join
(select level as ord from dual connect by level <= 3)
where id = p_function_parameter
order by ord
;
return arr;
end;
/
The function could be used, for example, like this: (in older versions you may need to wrap the function call within the table operator)
select * from my_function(1002);
COLUMN_VALUE
------------
6
-1.2
suppose we have the following masterdata:
order_type: [classic, transport]
order_status: [new, active, delivered, finished]
and we have the order tables:
+----------+------------+--------------+
| order_id | order_type | order_status |
+==========+============+==============+
| 1 | classic | new |
+----------+------------+--------------+
| 2 | classic | active |
+----------+------------+--------------+
| 3 | transport | active |
+----------+------------+--------------+
| 4 | transport | delivered |
+----------+------------+--------------+
| 5 | classic | finished |
+----------+------------+--------------+
| 6 | classic | active |
+----------+------------+--------------+
| 7 | transport | delivered |
+----------+------------+--------------+
| 8 | classic | finished |
+----------+------------+--------------+
what is requested is to have one single query that could return 2 different sets of data, according to the value of a given variable v_order_status:
v_order_status = 'del' then only transport orders is retrieved:
+----------+------------+--------------+
| order_id | order_type | order_status |
+==========+============+==============+
| 3 | transport | active |
+----------+------------+--------------+
| 4 | transport | delivered |
+----------+------------+--------------+
| 7 | transport | delivered |
+----------+------------+--------------+
v_order_status =any other value or null then we retrieve the whole table.
the desired SQL statement is to be used within a stored procedure, something like:
procedure process_orders (v_order_status in varchar2(3) default null)
as
begin
for ord in (select order_id, order_type, order_status
from orders
where [here comes the tricky part])
loop
do_something (ord.order_id);
...
end loop;
end process_orders ;
any ideas how to set the where clause in the SQL statement?
Use boolean logic:
select order_id, order_type, order_status
from orders
where
(v_order_status = 'del' and order_type = 'transport')
or v_order_status <> 'del'
or v_order_status is null
WHERE (v_order_status='del' AND order_type= 'transport')
OR (v_order_status <> 'del' OR v_order_status is null)
I suppose that the stored procedure language is PL/SQL.
do not define v_order as default null, if necessary default 'active'
simply use the variable in the where condition
procedure process_orders(v_order_status in varchar2(3) )
as
begin
for ord in (select order_id, order_type, order_status
from orders
where order_status = v_order_status
loop
do_something (ord.order_id);
...
end loop;
end process_orders ;
Since you are in a procedure, you should use that to your advantage to make the select statement as simple as possible. Instead of creating a query with a complex where clause (which may cause performance issues), you should do whatever logic you need in your PL/SQL code, then execute a simple query.
In the example below, I created a local variable that has its value set based on the conditions you described, then uses that variable as the only condition in the where clause. I added the NVL (order_type, 'no_order_type') logic just in case there were any orders without an order type, but if that is impossible than you can remove that NVL from the where clause.
Example
DECLARE
PROCEDURE process_orders (p_order_status IN VARCHAR2 DEFAULT NULL)
AS
l_order_type VARCHAR2 (20)
:= CASE p_order_status WHEN 'del' THEN 'transport' ELSE NULL END;
BEGIN
FOR ord
IN (SELECT order_id, order_type, order_status
FROM (SELECT 1 AS order_id, 'classic' AS order_type, 'new' AS order_status
FROM DUAL
UNION ALL
SELECT 2, 'classic', 'active' FROM DUAL
UNION ALL
SELECT 3, 'transport', 'active' FROM DUAL
UNION ALL
SELECT 4, 'transport', 'delivered' FROM DUAL
UNION ALL
SELECT 5, 'classic', 'finished' FROM DUAL
UNION ALL
SELECT 6, 'classic', 'active' FROM DUAL
UNION ALL
SELECT 7, 'transport', 'delivered' FROM DUAL
UNION ALL
SELECT 8, 'classic', 'finished' FROM DUAL) orders
WHERE NVL (order_type, 'no_order_type') =
NVL (l_order_type, NVL (order_type, 'no_order_type')))
LOOP
--do_something (ord.order_id);
DBMS_OUTPUT.put_line ('Order ID: ' || ord.order_id);
END LOOP;
END process_orders;
BEGIN
DBMS_OUTPUT.put_line ('Status: something');
process_orders ('something');
DBMS_OUTPUT.put_line ('Status: del');
process_orders ('del');
DBMS_OUTPUT.put_line ('Status: null');
process_orders (NULL);
END;
Output
Status: something
Order ID: 1
Order ID: 2
Order ID: 3
Order ID: 4
Order ID: 5
Order ID: 6
Order ID: 7
Order ID: 8
Status: del
Order ID: 3
Order ID: 4
Order ID: 7
Status: null
Order ID: 1
Order ID: 2
Order ID: 3
Order ID: 4
Order ID: 5
Order ID: 6
Order ID: 7
Order ID: 8
As an alternative to all the solutions provided, another option is to use dynamic sql and change the for loop construction for a bulk collect . This is what is called Dynamic SQL for Multirow Queries.
Something like that should work in your case.
TEST CASE ELEMENTS
SQL> create table orders ( order_id number, order_type varchar2(20) , order_status varchar2(20) ) ;
Table created.
SQL> insert into orders
2 with t ( order_id , order_type, order_status )
3 as
4 (
5 select 1, 'classic' , 'del' from dual union all
6 select 2, 'classic' , 'del' from dual union all
7 select 3, 'transport' , 'act' from dual union all
8 select 4, 'transport' , 'act' from dual union all
9 select 5, 'transport' , 'fin' from dual union all
10 select 6, 'classic' , 'fin' from dual union all
11 select 7, 'classic' , 'fin' from dual union all
12 select 8, 'transport' , 'act' from dual union all
13 select 9, 'transport' , 'act' from dual
14 )
15 select * from t ;
9 rows created.
SQL>
SQL> commit;
Commit complete.
SQL>
SQL> create or replace procedure process_orders ( v_order_status in varchar2 default null )
2 is
3 TYPE order_rt IS RECORD (
4 order_id number,
5 order_type varchar2(20),
6 order_status varchar2(20)
7 );
8
9 TYPE order_rt_aat IS TABLE OF order_rt INDEX BY PLS_INTEGER;
10
11 l_orders order_rt_aat;
12 v_sql clob;
13 BEGIN
14
15 v_sql := q'[ select order_id, order_type, order_status from orders ]' ;
16 if v_order_status is not null
17 then
18 v_sql := v_sql || ' where order_status = '''||v_order_status||''' ';
19 end if;
20
21 execute immediate v_sql bulk collect into l_orders;
22
23 FOR indx IN 1 .. l_orders.COUNT
24 LOOP
25 DBMS_OUTPUT.put_line (l_orders (indx).order_id);
26 END LOOP;
27 END;
28 /
Procedure created.
SQL>
VERIFICATION
Run without input parameter
SQL> set serveroutput on
SQL> exec process_orders ;
1
2
3
4
5
6
7
8
9
PL/SQL procedure successfully completed.
Run with a specific input parameter
SQL> exec process_orders ( 'del' ) ;
1
2
PL/SQL procedure successfully completed.
With agreement to other answers which uses where clause which are completely fine,
As this particular requirement says v_order_status = 'del' then only transport orders is retrieved and v_order_status =any other value or null then we retrieve the whole table, for this purpose we can also use decode as one line statement,
The Oracle docs note the following about the decode SQL function:
"DECODE compares a column or expression to search values, returning a result when there is a match. DECODE is similar to IF_THEN-ELSE logic."
select *
from
(
select 1 order_id,'classic' order_type,'new' order_status from dual
union all
select 2,'classic','active' from dual
union all
select 3,'transport','active' from dual
union all
select 4,'transport','delivered' from dual
)
where order_type = decode(&v_order_status,'del','transport',order_type);
Is there any way in oracle pl/sql to have a new table created which has column name coming from other table column data value.
For Example:
TableA
column1
-----------
A
B
C
D
out of this a new table comes out as
TABLE2
A B C D
- - - -
Where A,B,C,D act as column name for Table2
Thanks in advance.
Yes, use PL/SQL to build a dynamic query and then use EXECUTE IMMEDIATE:
Oracle Setup:
CREATE TABLE TableA ( column1 ) AS
SELECT 'A' FROM DUAL UNION ALL
SELECT 'B' FROM DUAL UNION ALL
SELECT 'C' FROM DUAL UNION ALL
SELECT 'D' FROM DUAL;
PL/SQL:
DECLARE
p_sql VARCHAR2(4000);
BEGIN
SELECT 'CREATE TABLE TableB ('
|| LISTAGG(
column1 || ' NUMBER',
','
) WITHIN GROUP ( ORDER BY ROWNUM )
|| ')'
INTO p_sql
FROM TableA;
EXECUTE IMMEDIATE p_sql;
END;
/
Output:
SELECT * FROM TableB;
A | B | C | D
-: | -: | -: | -:
db<>fiddle here