SELECT from a bulk collection - oracle

Is it possible to select from a bulk collection?
Something along these lines:
DECLARE
CURSOR customer_cur IS
SELECT CustomerId,
CustomerName
FROM Customers
WHERE CustomerAreaCode = '576';
TYPE customer_table IS TABLE OF customer_cur%ROWTYPE;
my_customers customer_table;
BEGIN
OPEN customer_cur;
FETCH customer_cur
BULK COLLECT INTO my_customers;
-- This is what I would like to do
SELECT CustomerName
FROM my_customers
WHERE CustomerId IN (1, 2, 3);
END;
I don't seem to be able to select from the my_customers table.

Yes, you can. Declare yourself schema-level types as follows:
create or replace rec_customer_cur
as
object (
customerid integer, -- change to the actual type of customers.customerid
customername varchar2(100) -- change to the actual type of customers.customername
);
/
create or replace type customer_table
as
table of rec_customer_cur;
/
Then, in your PLSQL code, you can declare
CURSOR customer_cur IS
SELECT new rec_customer_cur(CustomerId, CustomerName)
FROM Customers
WHERE CustomerAreaCode = '576';
... and then use ...
SELECT CustomerName
INTO whatever
FROM table(my_customers)
WHERE CustomerId IN (1, 2, 3);
This is because schema-level types can be used in SQL context.

If you want to also display the dataset returned by the select, then just use a REF CURSOR as an OUT parameter.
The SELECT ...FROM TABLE is a SQL statement, which needs a STATIC TABLE NAME, as a database object. It throws an error since the collection name is not actually a database table as an object.
To return the dataset, use SYS_REFCURSOR as OUT parameter.
open cur as select....

Related

Inserting values into newly created table from a pre-existing table using a cursor and for loop - Snowflake SQL (classic web interface)

I'm trying to insert values into a new table in the classic Snowflake SQL web interface using data from a table that was already created, a cursor, and a for loop. My goal is to insert new information and information from the original table into the new table, but when I try and run my code, there is an error where I am referring to the column of my original table. (See code below)
-- Creation and inserting values into table invoice_original
create temporary table invoice_original (id integer, price number(12,2));
insert into invoice_original (id, price) values
(1, 11.11),
(2, 22.22);
-- Creates final empty table invoice_final
create temporary table invoice_final (
study_number varchar,
price varchar,
price_type varchar);
execute immediate $$
declare
c1 cursor for select price from invoice_original;
begin
for record in c1 do
insert into invoice_final(study_number, price, price_type)
values('1', record.price, 'Dollars');
end for;
end;
$$;
My end goal is to have the resulting table invoice_final with 3 columns - study_number, price, and price_type where the price value comes from the invoice_original table. The error I'm currently getting is:
Uncaught exception of type 'STATEMENT_ERROR' on line 6 at position 8 : SQL compilation error: error line 2 at position 20 invalid identifier 'RECORD.PRICE'.
Does anyone know why the record.price is not capturing the price value from the invoice_original table?
there are a number of type of dynamic SQL that do not handle the cursor name, and thus give this error if you push it into a single name temp value it will work:
for record in c1 do
let temp_price number := record.price;
insert into invoice_final(study_number, price, price_type)
values('1', temp_price, 'Dollars');
end for;
this sql has not been run, and could be the wrong format, but it is the base issue.
Also this really looks like an INSERT would work, but I also assume this is the nature of simplify the question down.
See the following for details on working with variables:
https://docs.snowflake.com/en/developer-guide/snowflake-scripting/variables.html#working-with-variables
The revised code below functions as desired:
-- Creation and inserting values into table invoice_original
create
or replace temporary table invoice_original (id integer, price number(12, 2));
insert into
invoice_original (id, price)
values
(1, 11.11),
(2, 22.22);
-- Creates final empty table invoice_final
create
or replace temporary table invoice_final (
study_number varchar,
price number(12, 2),
price_type varchar
);
execute immediate $$
declare
new_price number(12,2);
c1 cursor for select price from invoice_original;
begin
for record in c1 do
new_price := record.price;
insert into invoice_final(study_number, price, price_type) values('1',:new_price, 'Dollars');
end for;
end;
$$;
Note that I changed the target table definition for price to NUMBER (12,2) instead of VARCHAR, and assigned the record.price to a local variable that was passed to the insert statement as :new_price.
That all said ... I would strongly recommend against this approach for loading tables for performance reasons. You can replace all of this with an INSERT .. AS ... SELECT.
Always opt for set based processing over cursor / loop / row based processing with Snowflake.
https://docs.snowflake.com/en/sql-reference/sql/insert.html

execute SQL query stored in a table

I have a table having one of the columns that stores SQL query returning ids or it stores comma separated ids.
create table to store query or ids(separated by ,)
create table test1
(
name varchar(20) primary key,
stmt_or_value varchar(500),
type varchar(50)
);
insert into test1 (name, stmt_or_value, type)
values ('first', 'select id from data where id = 1;','SQL_QUERY')
insert into test1 (name, stmt_or_value, type)
values ('second', '1,2,3,4','VALUE')
data table is as follows
create table data
(
id number,
subject varchar(500)
);
insert into data (id, subject) values (1, 'test subject1');
insert into data (id, subject) values (2, 'test subject2');
insert into data (id, subject) values (3, 'test subject2');
I am not able to formulate query that will return values after either executing stored sql or parsing stored ids based on the value of name.
select id, subject
from data
where id in( EXECUTE IMMEDIATE stmt_or_value
where type='SQL_QUERY'
and name = 'first') or
( parse and return ids
from stmt_or_value
where type='VALUE'
and name = 'second')
Could you please help me in this.
Parsing comma separated value is done, I basically need help in below first part of the query:
( EXECUTE IMMEDIATE stmt_or_value
where type='SQL_QUERY'
and name = 'first')
This seems a very peculiar requirement, and one which will be difficult to solve in a robust fashion. STMT_OR_VALUE is the embodiment of the One Column Two Usages anti-pattern. Furthermore, resolving STMT_OR_VALUE requires flow control logic and the use of dynamic SQL. Consequently it cannot be a pure SQL solution: you need to use PL/SQL to assemble and execute the dynamic query.
Here is a proof of concept for a solution. I have opted for a function which you can call from SQL. It depends on one assumption: every query string you insert into TEST1.STMT_OR_VALUE has a projection of a single numeric column and every value string is a CSV of numeric data only. With this proviso it is simple to construct a function which either executes a dynamic query or tokenizes the string into a series of numbers; both of which are bulk collected into a nested table:
create or replace function get_ids (p_name in test1.name%type)
return sys.odcinumberlist
is
l_rec test1%rowtype;
return_value sys.odcinumberlist;
begin
select * into l_rec
from test1
where name = p_name;
if l_rec.type = 'SQL_QUERY' then
-- execute a query
execute immediate l_rec.stmt_or_value
bulk collect into return_value;
else
-- tokenize a string
select xmltab.tkn
bulk collect into return_value
from ( select l_rec.stmt_or_value from dual) t
, xmltable( 'for $text in ora:tokenize($in, ",") return $text'
passing stmt_or_value as "in"
columns tkn number path '.'
) xmltab;
end if;
return return_value;
end;
/
Note there is more than one way of executing a dynamic SQL statement and a multiplicity of ways to tokenize a CSV into a series of numbers. My decisions are arbitrary: feel free to substitute your preferred methods here.
This function can be invoked with a table() call:
select *
from data
where id in ( select * from table(get_ids('first'))) -- execute query
or id in ( select * from table(get_ids('second'))) -- get string of values
/
The big benefit of this approach is it encapsulates the logic around the evaluation of STMT_OR_VALUE and hides use of Dynamic SQL. Consequently it is easy to employ it in any SQL statement whilst retaining readability, or to add further mechanisms for generating a set of IDs.
However, this solution is brittle. It will only work if the values in the test1 table obey the rules. That is, not only must they be convertible to a stream of single numbers but the SQL statements must be valid and executable by EXECUTE IMMEDIATE. For instance, the trailing semi-colon in the question's sample data is invalid and would cause EXECUTE IMMEDIATE to hurl. Dynamic SQL is hard not least because it converts compilation errors into runtime errors.
Following is the set up data used for this example:
create table test1
(
test_id number primary key,
stmt_or_value varchar(500),
test_type varchar(50)
);
insert into test1 (test_id, stmt_or_value, test_type)
values (1, 'select id from data where id = 1','SQL_QUERY');
insert into test1 (test_id, stmt_or_value, test_type)
values (2, '1,2,3,4','VALUE');
insert into test1 (test_id, stmt_or_value, test_type)
values (3, 'select id from data where id = 5','SQL_QUERY');
insert into test1 (test_id, stmt_or_value, test_type)
values (4, '3,4,5,6','VALUE');
select * from test1;
TEST_ID STMT_OR_VALUE TEST_TYPE
1 select id from data where id = 1 SQL_QUERY
2 1,2,3,4 VALUE
3 select id from data where id = 5 SQL_QUERY
4 3,4,5,6 VALUE
create table data
(
id number,
subject varchar(500)
);
insert into data (id, subject) values (1, 'test subject1');
insert into data (id, subject) values (2, 'test subject2');
insert into data (id, subject) values (3, 'test subject3');
insert into data (id, subject) values (4, 'test subject4');
insert into data (id, subject) values (5, 'test subject5');
select * from data;
ID SUBJECT
1 test subject1
2 test subject2
3 test subject3
4 test subject4
5 test subject5
Below is the solution:
declare
sql_stmt clob; --to store the dynamic sql
type o_rec_typ is record(id data.id%type, subject data.subject%type);
type o_tab_typ is table of o_rec_typ;
o_tab o_tab_typ; --to store the output records
begin
--The below SELECT query generates the required dynamic SQL
with stmts as (
select (listagg(stmt_or_value, ' union all ') within group(order by stmt_or_value))||' union all ' s
from test1 t
where test_type = 'SQL_QUERY')
select
q'{select id, subject
from data
where id in (}'||
nullif(s,' union all ')||q'{
select distinct to_number(regexp_substr(s, '[^,]+', 1, l)) id
from (
select level l,
s
from (select listagg(stmt_or_value,',') within group(order by stmt_or_value) s
from test1
where test_type = 'VALUE') inp
connect by level <= length (regexp_replace(s, '[^,]+')) + 1))}' stmt into sql_stmt
from stmts; -- Create the dynamic SQL and store it into the clob variable
--execute the statement, fetch and display the output
execute immediate sql_stmt bulk collect into o_tab;
for i in o_tab.first..o_tab.last
loop
dbms_output.put_line('id: '||o_tab(i).id||' subject: '||o_tab(i).subject);
end loop;
end;
Output:
id: 1 subject: test subject1
id: 2 subject: test subject2
id: 3 subject: test subject3
id: 4 subject: test subject4
id: 5 subject: test subject5
Learnings:
Avoid using key words for table and column names.
Design application tables effectively to serve current and reasonable future requirements.
The above SQL will work. Still it is wise to consider reviewing the table design because, the complexity of the code will keep increasing with changes in requirements in future.
Learned how to convert comma separated values into records. "https://asktom.oracle.com/pls/apex/f?p=100:11:::NO::P11_QUESTION_ID:9538583800346706523"
declare
my_sql varchar2(1000);
v_num number;
v_num1 number;
begin
select stmt_or_value into my_sql from test1 where ttype='SQL_QUERY';
execute immediate my_sql into v_num;
select id into v_num1 from data where id=v_num;
dbms_output.put_line(v_num1);
end;
Answer for part 1.Please check.

Declaring and using variables in PL-SQL

I am new to PL-SQL. I do not understand why I am getting the error "PLS-00428: an INTO clause is expected in this SELECT statement"
What I'm trying to accomplish is to create a variable c_limit and load it's value. I then want to use that variable later to filter data.
Basically I am playing around in the demo db to see what I can/can't do with PL-SQL.
The code worked up to the point that I added "select * from demo_orders where CUSTOMER_ID = custID;"
declare
c_limit NUMBER(9,2);
custID INT;
BEGIN
custID := 6;
-- Save the credit limit
select credit_limit INTO c_limit
from demo_customers cust
where customer_id = custID;
select * from demo_orders where CUSTOMER_ID = custID;
dbms_output.Put_line(c_limit);
END;
If you are using a SQL SELECT statement within an anonymous block (in PL/SQL - between the BEGIN and the END keywords) you must select INTO something so that PL/SQL can utilize a variable to hold your result from the query. It is important to note here that if you are selecting multiple columns, (which you are by "SELECT *"), you must specify multiple variables or a record to insert the results of your query into.
for example:
SELECT 1
INTO v_dummy
FROM dual;
SELECT 1, 2
INTO v_dummy, v_dummy2
FROM dual;
It is also worth pointing out that if your SELECT * FROM.... will return multiple rows, PL/SQL will throw an error. You should only expect to retrieve 1 row of data from a SELECT INTO.
Looks like the error is from the second select query.
select * from demo_orders where CUSTOMER_ID = custID;
PL-SQL won't allow a standalone sql select query for info.
http://pls-00428.ora-code.com/
You need to do some operation with the second select query

creating mview containing pl/sql definitions in with clause

How to use pl sql definitions in with clause while creating mview?
An explanation with example would really help.
Also I have tried the following queries and I have faced some errors.
create table tab1
(
fname varchar2(15) ,
lname varchar2(15)
);
create or replace type user_data_type as object (val1 varchar2(20),val2 varchar2(20));
/
CREATE OR REPLACE Function get_fname_lname(n in varchar2)
RETURN user_data_type as
name user_data_type;
cursor buffer_cur is
select fname,lname from tab1 where fname=n;
BEGIN
name := new user_data_type(null,null);
OPEN buffer_cur;
fetch buffer_cur into name.val1,name.val2;
close buffer_cur;
return name;
End;
/
select * from tab1;
select get_fname_lname(fname).val1 fname,get_fname_lname(fname).val2 from tab1;
drop materialized view mv1;
create materialized view mv1
with names as
(select get_fname_lname(fname).val1 fname,get_fname_lname(fname).val2 lname from tab1)
select fname,lname from names;
select * from mv1;
I have seen this error:
Error while creating mview:
ORA-00905: missing keyword
Can some one point me out what might be the issue.
You can try something like this while creating view , since recursive views are not supported in oracle.
By using with clause you are already creating an inline view and for that view you are trying to create a materialised view which is not supported in oracle
create materialized view mv1
as
select fname,lname from
(select get_fname_lname(fname).val1 fname,get_fname_lname(fname).val2 lname
from tab1);

Insert content of table variable into table

I have the following table, two types based on it, and a function that reads from this table:
CREATE TABLE myTable (
ID RAW(16) NULL,
NAME NVARCHAR2(200) NULL,
ENTITYID RAW(16) NOT NULL
);
CREATE TYPE myRowType AS OBJECT (
NAME NVARCHAR2(200),
ENTITYID RAW(16)
);
CREATE TYPE myTableType IS TABLE OF myRowType;
CREATE FUNCTION myFunction(...) RETURN myTableType ...
As you can see, the type myRowType is similar to myTable, but not exactly.
My goal is to insert rows into myTable based on the results of myFunction.
The naive approach would be to just write:
INSERT INTO myTable(ID, NAME, ENTITYID)
SELECT sys_guid(), NAME, ENTITYID
FROM TABLE(myFunction(...));
But since myFunction reads from myTable, this leads to the following error:
ORA-04091: table myTable is mutating, trigger/function may not see it
So I have to split the myFunction call from the insert statement. I tried it like this:
DECLARE
tbl myTableType;
BEGIN
SELECT myRowType(x.NAME, x.ENTITYID)
BULK COLLECT INTO tbl
FROM TABLE(myFunction(...)) x;
INSERT INTO myTable
(ID, NAME, ENTITYID)
SELECT sys_guid(), x.NAME, x.ENTITYID
FROM tbl x;
END;
But here, Oracle doesn't seem to understand the FROM tbl clause. It shows the error
ORA-00942: table or view does not exist
How can I insert the rows in tbl into myTable?
Since you can't use a locally defined nested table as an argument for TABLE function, maybe you would consider using the FORALL bulk insert? I see you are using Oracle 11g, so you will be able to access fields of myRowType. You would then replace your INSERT from your PL/SQL block with this:
FORALL v_i IN tbl.FIRST..tbl.LAST
INSERT INTO myTable VALUES (sys_guid(), tbl(v_i).name, tbl(v_i).entityid);
I recommend this great article by Tim Hall: BULK COLLECT & FORALL

Resources