I am trying to get old value from variable in PL/SQL for loop.
For instance:
I have l_sequnce variable with increment of 10.
for x in (select name
from test_names
order by position)
loop
-- print l_sequnce variable
-- when name is already was in loop then write same value from l_sequence as for first one
end loop;
I hope I described the problem well :)
Use ORDER BY in the SELECT statement and then you only need to check if the value has changed between the previous and current values:
DECLARE
v_name TEST_NAMES.NAME%TYPE := NULL;
l_sequence PLS_INTEGER := 0;
BEGIN
FOR x IN (select name from test_names ORDER BY name)
LOOP
IF x.name <> v_name
OR (x.name IS NULL AND v_name IS NOT NULL)
OR (x.name IS NOT NULL AND v_name IS NULL)
OR l_sequence = 0
THEN
l_sequence := l_sequence + 10;
v_name := x.name;
END IF;
DBMS_OUTPUT.PUT_LINE(l_sequence || ': ' || x.name);
END LOOP;
END;
/
Or, don't use PL/SQL and do it all in an SQL statement using the DENSE_RANK analytic function:
SELECT name,
10 * DENSE_RANK() OVER (ORDER BY name) AS seq_value
FROM test_names
ORDER BY name; -- or whatever other column you want to order by.
If you want to do it entirely in PL/SQL and cannot use ORDER BY then use an associative array:
DECLARE
TYPE t_name_seq_map IS TABLE OF PLS_INTEGER INDEX BY VARCHAR2(30);
v_names t_name_seq_map;
l_sequence PLS_INTEGER := 0;
BEGIN
FOR x IN (SELECT name FROM test_names ORDER BY position)
LOOP
IF NOT v_names.EXISTS(x.name) THEN
l_sequence := l_sequence + 10;
v_names(x.name) := l_sequence;
END IF;
DBMS_OUTPUT.PUT_LINE(v_names(x.name) || ': ' || x.name);
END LOOP;
END;
/
or:
BEGIN
FOR x IN (
SELECT name,
DENSE_RANK() OVER (ORDER BY name) AS l_sequence
FROM test_names
ORDER BY position
)
LOOP
DBMS_OUTPUT.PUT_LINE(x.l_sequence || ': ' || x.name);
END LOOP;
END;
/
Related
I wanted to select 10 highly paid employees from the "employees" table, but the function returnes only 1 row. How to get multiple rows in this case? My subquery for selecting employees works well, but when I call function it returns 1 row.
This is my code:
CREATE OR REPLACE FUNCTION f_sal
RETURN Varchar2
IS cursor c_emp is (select first_name, last_name from (select first_name, last_name, row_number()
over(order by salary desc) as ranking from employees) where ranking <= 10);
v_first employees.first_name%type;
v_last employees.last_name%type;
begin
open c_emp;
fetch c_emp into v_first, v_last;
close c_emp;
return v_first || ' ' || v_last;
EXCEPTION
WHEN NO_DATA_FOUND
THEN
dbms_output.put_line('Error');
when others then dbms_output.put_line('Other Error');
END;
select f_sal from dual;
Option 1: Use a collection
CREATE FUNCTION f_sal
RETURN SYS.ODCIVARCHAR2LIST
IS
v_names SYS.ODCIVARCHAR2LIST;
BEGIN
SELECT first_name || ' ' || last_name
BULK COLLECT INTO v_names
FROM employees
ORDER BY salary DESC
FETCH FIRST 10 ROWS ONLY;
return v_names;
END;
/
Then:
SELECT * FROM TABLE(f_sal);
Option 2: Use a pipelined function and iterate over a cursor into a collection
CREATE OR REPLACE FUNCTION f_sal
RETURN SYS.ODCIVARCHAR2LIST PIPELINED
IS
BEGIN
FOR n IN (
SELECT first_name || ' ' || last_name AS name
FROM employees
ORDER BY salary DESC
FETCH FIRST 10 ROWS ONLY
)
LOOP
PIPE ROW (n.name);
END LOOP;
END;
/
Then:
SELECT * FROM TABLE(f_sal);
Option 3: Return a cursor
CREATE FUNCTION f_sal
RETURN SYS_REFCURSOR
IS
v_names SYS_REFCURSOR;
BEGIN
OPEN v_names FOR
SELECT first_name || ' ' || last_name AS name
FROM employees
ORDER BY salary DESC
FETCH FIRST 10 ROWS ONLY;
return v_names;
END;
/
Then:
DECLARE
v_names SYS_REFCURSOR := f_sal();
v_name VARCHAR2(100);
BEGIN
LOOP
FETCH v_names INTO v_name;
EXIT WHEN v_names%NOTFOUND;
DBMS_OUTPUT.PUT_LINE( v_name );
END LOOP;
END;
/
db<>fiddle here
I am trying hard dynamic PL/SQL thing here.
I don't manage to fetch a column dynamic Query.
I am iterating on the name of the column to concatenate a full query in order to be executed on another table.
sql_req := 'select ';
for c in (SELECT name_col from TAB_LISTCOL)
loop
sql_req := sql_req || 'sum(' || c.name_col || '),';
end loop;
sql_req := sql_req || ' from ANOTHER_TAB ';
And when i try to execute it with EXECUTE IMMEDIATE or cursors or INTO/BULK COLLECT thing or just to fetch, i don't manage to iterate on the result.
I tried a lot.
Can you help me plz ? Or maybe it is not possible ?
ps : i know the coma is wrong but my code is more complexe than this : i didn't want to put more things
If you only want to get string columns, you can use listagg
select listagg(name_col, ',') WITHIN GROUP (ORDER BY null) from TAB_LISTCOL
Please see if this helps
In the absence of actual table structure and requirement, I'm creating dummy tables and query to illustrate an example:
SQL> create table another_tab
as
select 10 dummy_value1, 100 dummy_value2, 1000 dummy_value3 from dual union all
select 11 dummy_value1, 101 dummy_value2, 1001 dummy_value3 from dual union all
select 12 dummy_value1, 102 dummy_value2, 1003 dummy_value3 from dual
;
Table created.
SQL> create table tab_listcol
as select column_name from dba_tab_cols where table_name = 'ANOTHER_TAB'
;
Table created.
To reduce complexity in the final block, I'm defining a function to generate the dynamic sql query. This is based on your example and will need changes according to your actual requirement.
SQL> create or replace function gen_col_based_query
return varchar2
as
l_query varchar2(4000);
begin
l_query := 'select ';
for cols in ( select column_name cname from tab_listcol )
loop
l_query := l_query || 'sum(' || cols.cname || '), ' ;
end loop;
l_query := rtrim(l_query,', ') || ' from another_tab';
return l_query;
end;
/
Function created.
Sample output from the function will be as follows
SQL> select gen_col_based_query as query from dual;
QUERY
--------------------------------------------------------------------------------
select sum(DUMMY_VALUE1), sum(DUMMY_VALUE2), sum(DUMMY_VALUE3) from another_tab
Below is a sample block for executing a dynamic cursor using DBMS_SQL. For your ease of understanding, I've added comments wherever possible. More info here.
SQL> set serveroutput on size unlimited
SQL> declare
sql_stmt clob;
src_cur sys_refcursor;
curid number;
desctab dbms_sql.desc_tab; -- collection type
colcnt number;
namevar varchar2 (50);
numvar number;
datevar date;
l_header varchar2 (4000);
l_out_rows varchar2 (4000);
begin
/* Generate dynamic sql from the function defined earlier */
select gen_col_based_query into sql_stmt from dual;
/* Open cursor variable for this dynamic sql */
open src_cur for sql_stmt;
/* To fetch the data, however, you cannot use the cursor variable, since the number of elements fetched is unknown at complile time.
Therefore you use DBMS_SQL.TO_CURSOR_NUMBER to convert a REF CURSOR variable to a SQL cursor number which you can then pass to DBMS_SQL subprograms
*/
curid := dbms_sql.to_cursor_number (src_cur);
/* Use DBMS_SQL.DESCRIBE_COLUMNS to describe columns of your dynamic cursor, returning information about each column in an associative array of records viz., desctab. The no. of columns is returned in colcnt variable.
*/
dbms_sql.describe_columns (curid, colcnt, desctab);
/* Define columns at runtime based on the data type (number, date or varchar2 - you may add to the list)
*/
for indx in 1 .. colcnt
loop
if desctab (indx).col_type = 2 -- number data type
then
dbms_sql.define_column (curid, indx, numvar);
elsif desctab (indx).col_type = 12 -- date data type
then
dbms_sql.define_column (curid, indx, datevar);
else -- assuming string
dbms_sql.define_column (curid, indx, namevar, 100);
end if;
end loop;
/* Print header row */
for i in 1 .. desctab.count loop
l_header := l_header || ' | ' || rpad(desctab(i).col_name,20);
end loop;
l_header := l_header || ' | ' ;
dbms_output.put_line(l_header);
/* Loop to retrieve each row of data identified by the dynamic cursor and print output rows
*/
while dbms_sql.fetch_rows (curid) > 0
loop
for indx in 1 .. colcnt
loop
if (desctab (indx).col_type = 2) -- number data type
then
dbms_sql.column_value (curid, indx, numvar);
l_out_rows := l_out_rows || ' | ' || rpad(numvar,20);
elsif (desctab (indx).col_type = 12) -- date data type
then
dbms_sql.column_value (curid, indx, datevar);
l_out_rows := l_out_rows || ' | ' || rpad(datevar,20);
elsif (desctab (indx).col_type = 1) -- varchar2 data type
then
dbms_sql.column_value (curid, indx, namevar);
l_out_rows := l_out_rows || ' | ' || rpad(namevar,20);
end if;
end loop;
l_out_rows := l_out_rows || ' | ' ;
dbms_output.put_line(l_out_rows);
end loop;
dbms_sql.close_cursor (curid);
end;
/
PL/SQL procedure successfully completed.
Output
| SUM(DUMMY_VALUE1) | SUM(DUMMY_VALUE2) | SUM(DUMMY_VALUE3) |
| 33 | 303 | 3004 |
You have to use EXECUTE IMMEDIATE with BULK COLLECT
Below is an example of the same. For more information refer this link
DECLARE
TYPE name_salary_rt IS RECORD (
name VARCHAR2 (1000),
salary NUMBER
);
TYPE name_salary_aat IS TABLE OF name_salary_rt
INDEX BY PLS_INTEGER;
l_employees name_salary_aat;
BEGIN
EXECUTE IMMEDIATE
q'[select first_name || ' ' || last_name, salary
from hr.employees
order by salary desc]'
BULK COLLECT INTO l_employees;
FOR indx IN 1 .. l_employees.COUNT
LOOP
DBMS_OUTPUT.put_line (l_employees (indx).name);
END LOOP;
END;
If I understand correctly, you want to create a query and execute it and return the result to another function or some calling app. As the resulting query's columns are note before-known, I'd return a ref cursor in this case:
create function get_sums return sys_refcur as
declare
my_cursor sys_refcursor;
v_query varchar2(32757);
begin
select
'select ' ||
listagg('sum(' || name_col || ')', ', ') within group (order by name_col) ||
' from another_tab'
into v_query
from tab_listcol;
open my_cursor for v_query;
return v_query;
end get_sums;
I try to search a number from multiple columns (datatype number), but get ORA-01722: invalid number error.
My Query:
SELECT *
FROM CAMPAIGN
WHERE 1481125 IN (select column_name
from all_tab_columns
where table_name = 'CAMPAIGN'
AND data_type = 'NUMBER');
What is wrong with it?
You are comparing your number 1481125 with the names of the each column, not the values of each column in your table.
To go from a column's name (from dba_tab_columns) to the values in that column, you need to use some form of dynamic SQL. Here's a relatively simple example:
DECLARE
-- Since I don't have your CAMPAIGN table or data, I'm using DBA_OBJECTS in it's place.
l_table_name VARCHAR2 (30) := 'DBA_OBJECTS';
l_search_number NUMBER := 20; -- 1481125 in your example
l_record dba_objects%ROWTYPE;
l_sql VARCHAR2 (32000);
l_column_number NUMBER := 0;
l_cur SYS_REFCURSOR;
BEGIN
-- First: build dynamic SQL statement of the form:
-- SELECT * FROM table_name WHERE
-- ( ( col_name_a = 20 ) OR ( col_name_b = 20 ) OR ... )
l_sql := 'SELECT * FROM dba_objects WHERE ( ';
FOR r_number_column IN (SELECT column_name
FROM dba_tab_columns
WHERE table_name = l_table_name
AND data_type = 'NUMBER'
ORDER BY column_id) LOOP
IF l_column_number > 0 THEN
l_sql := l_sql || ' OR ';
END IF;
l_column_number := l_column_number + 1;
l_sql := l_sql || '(' || r_number_column.column_name || ' = ' || l_search_number || ')';
END LOOP;
IF l_column_number = 0 THEN
-- No number columns in table, so there should be no matches
l_sql := l_sql || ' 1=0';
END IF;
l_sql := l_sql || ')';
DBMS_OUTPUT.put_line (l_sql);
OPEN l_cur FOR l_sql;
LOOP
FETCH l_cur INTO l_record;
EXIT WHEN l_cur%NOTFOUND;
DBMS_OUTPUT.put_line ('Object Name ' || l_record.object_name || ' has search number ' || l_search_number);
END LOOP;
END;
Your query is:
SELECT * FROM CAMPAIGN WHERE 1481125 IN
(select column_name from all_tab_columns where table_name = 'CAMPAIGN' AND data_type='NUMBER')
Breaking that down we have:
SELECT * FROM CAMPAIGN WHERE 1481125 IN (<a set of numbers>)
and the subquery:
select column_name from all_tab_columns
where table_name = 'CAMPAIGN'
AND data_type='NUMBER'
That subquery is going to return a list of column names e.g.
CAMPAIGN_COUNT
CAMPAIGN_ID
CAMPAIGN_NUMBER_OF_SOMETHINGS
Your query is thus equivalent to:
SELECT * FROM CAMPAIGN WHERE 1481125 IN
('CAMPAIGN_COUNT', 'CAMPAIGN_ID', 'CAMPAIGN_NUMBER_OF_SOMETHINGS')
You can see why you would get the ORA-01722 error there?
You would need to write dynamic SQL to achieve your aim.
with oracle xmltable
SELECT u.*
FROM table1
, XMLTable('/abc/def[contract = $count]'
PASSING xmlcol, 1 as "count"
COLUMNS contract integer path 'contract',
oper VARCHAR2(20) PATH 'oper' ) u
This is normally what we do.
Now I need to have "COLUMNS" in above query selected from another tables column for Xpath
something like
{
SELECT u.*
FROM table1
, XMLTable('/abc/def[contract = $count]'
PASSING xmlcol, 1 as "count"
COLUMNS (select xpath from xpath_metadeta )) u
}
Please let me know if this is possible and how?
One option that comes to my mind is dynamic sql and ref cursor.
Something like this:
DECLARE
columnParameters SYS.ODCIVARCHAR2LIST :=
SYS.ODCIVARCHAR2LIST(
'TITLE VARCHAR2(1000) PATH ''title''',
'SUMMARY CLOB PATH ''summary''',
'UPDATED VARCHAR2(20) PATH ''updated''',
'PUBLISHED VARCHAR2(20) PATH ''published''',
'LINK VARCHAR2(1000) PATH ''link/#href'''
);
ref_cursor SYS_REFCURSOR;
cursor_id NUMBER;
table_description DBMS_SQL.DESC_TAB;
column_count NUMBER;
string_value VARCHAR2(4000);
clob_value CLOB;
FUNCTION DYNAMIC_XMLTABLE(xml_columns SYS.ODCIVARCHAR2LIST) RETURN SYS_REFCURSOR
IS
result SYS_REFCURSOR;
statementText VARCHAR2(32000) := Q'|SELECT * FROM
XMLTABLE(
XMLNAMESPACES (DEFAULT 'http://www.w3.org/2005/Atom'),
'for $entry in /feed/entry return $entry'
PASSING
HTTPURITYPE('http://stackoverflow.com/feeds/tag?tagnames=oracle&sort=newest').getxml()
COLUMNS
{column_definition}
)|';
BEGIN
SELECT REPLACE(statementText, '{column_definition}', LISTAGG(COLUMN_VALUE, ', ') WITHIN GROUP (ORDER BY ROWNUM)) INTO statementText FROM TABLE(xml_columns);
DBMS_OUTPUT.PUT_LINE('Statement: ' || CHR(10) || statementText);
OPEN result FOR statementText;
RETURN result;
END;
BEGIN
DBMS_OUTPUT.ENABLE(NULL);
ref_cursor := dynamic_xmltable(columnParameters);
cursor_id := DBMS_SQL.TO_CURSOR_NUMBER(ref_cursor);
DBMS_SQL.DESCRIBE_COLUMNS(cursor_id, column_count, table_description);
FOR i IN 1..column_count LOOP
IF table_description(i).col_type = 1 THEN
DBMS_SQL.DEFINE_COLUMN(cursor_id, i, string_value, 4000);
ELSIF table_description(i).col_type = 112 THEN
DBMS_SQL.DEFINE_COLUMN(cursor_id, i, clob_value);
END IF;
END LOOP;
WHILE DBMS_SQL.FETCH_ROWS(cursor_id) > 0 LOOP
FOR i IN 1..column_count LOOP
DBMS_OUTPUT.PUT_LINE(table_description(i).col_name || ': datatype=' || table_description(i).col_type);
IF (table_description(i).col_type = 1) THEN
BEGIN
DBMS_SQL.COLUMN_VALUE(cursor_id, i, string_value);
DBMS_OUTPUT.PUT_LINE('Value: ' || string_value);
END;
ELSIF (table_description(i).col_type = 112) THEN
BEGIN
DBMS_SQL.COLUMN_VALUE(cursor_id, i, clob_value);
DBMS_OUTPUT.PUT_LINE('Value: ' || clob_value);
END;
-- add other data types
END IF;
END LOOP;
END LOOP;
DBMS_SQL.CLOSE_CURSOR(cursor_id);
END;
I depends how the cursor is consumed. It's much simple if by an application, a bit more difficult if using PL/SQL.
I give the SQL few inputs and I need to get all the ID's and their count that doesn't satisfy the required criteria.
I would like to know if there are there any alternatives to using cursor.
DECLARE
v_count INTEGER;
v_output VARCHAR2 (1000);
pc table1%ROWTYPE;
unmarked_ids EXCEPTION;
dynamic_sql VARCHAR (5000);
cur SYS_REFCURSOR;
id pp.id%TYPE;
pos INTEGER;
BEGIN
v_count := 0;
SELECT *
INTO pc
FROM table1
WHERE id = '&ID';
DBMS_OUTPUT.ENABLE;
dynamic_sql :=
'SELECT ID from pp
WHERE ( TO_CHAR(cdate, ''yyyy/mm/dd'') =
TO_CHAR (:a, ''yyyy/mm/dd''))
AND aid IN (SELECT aid FROM ppd WHERE TO_CHAR(cdate, ''yyyy/mm/dd'') =
TO_CHAR (:b, ''yyyy/mm/dd'')
AND cid = :c )
AND cid <> :d';
OPEN cur FOR dynamic_sql USING pc.cdate, pc.cdate, pc.id, pc.id;
LOOP
FETCH cur INTO id;
EXIT WHEN cur%NOTFOUND;
v_count := v_count + 1;
DBMS_OUTPUT.PUT_LINE (' Id:' || id);
END LOOP;
CLOSE cur;
IF (v_count > 0)
THEN
DBMS_OUTPUT.PUT_LINE ( 'Count: ' || v_count || ' SQL: ' || dynamic_sql);
RAISE unmarked_ids;
END IF;
DBMS_OUTPUT.PUT_LINE('SQL ended successfully');
EXCEPTION
WHEN unmarked_ids
THEN
DBMS_OUTPUT.put_line (
'Found ID's that not marked with the current id.');
WHEN NO_DATA_FOUND
THEN
DBMS_OUTPUT.put_line (
'No data found in table1 with the current id ' || '&ID');
END;
There are bind variables in the query. One of them is date, there are three more.
The count and ID's are required to be shown which will later be reported.
You could store the rowid in a temporary table along with an index value (0...n) and then use a while loop to go through the index values and join to the real table using the rowid.