Oracle dynamic table creation on runtime - sql

I make to try a search engine.Scenario like that; I have a table that it contains text context and I'm fetching some records what suitable according to my query then I want to transfer this founded text of id on a table that created dynamicly on runtime. My sql code as follow but this error
"expression is of wrong type"
SQL
declare
v_table dbms_sql.number_table;
begin
select a_id bulk collect into v_table from (select a_id from b where length(b_data) > 4);
select * from a where a_id in v_table;
end;

DBMS_SQL.NUMBER_TABLE is an associative array:
Unlike a database table, an associative array:
Does not need disk space or network operations
Cannot be manipulated with DML statements
You can select into an associative array, but you cannot use it as a table in a select.
You could to the select into with a nested table but you still couldn't use that in a select if the type is declared within your block because it would be a PL/SQL type that isn't valid within an SQL statement.
You would need a nested table type defined in SQL (i.e. not in PL/SQL) to achieve this. There is a built-in type to make it easier, but you can define your own:
declare
v_table sys.odcinumberlist;
v_table2 sys.odcinumberlist;
begin
select a_id bulk collect into v_table
from (select a_id from b where length(b_data) > 4);
select a.a_id bulk collect into v_table2
from table(v_table) vt
join a on a.a_id = vt.column_value;
end;
/
anonymous block completed
The second select you showed is incomplete so I've made one up. Your first doesn't need the nested select, that can be simplified to:
select a_id bulk collect into v_table
from b
where length(b_data) > 4;
Hopefully you're dong something with the collection before your second select, otherwise it's a bit pointless; you could just join a to b in the second select to get the same result:
select a.<columns> ...
from a
join b on b.a_id = a.a_id
where length(b.b_date) > 4;

Related

How to bulk collect in Oracle into a table of custom type

I have this:
TYPE record_flags IS RECORD (
is_delete map_calculation.is_delete%TYPE,
is_editable map_calculation.is_editable%TYPE
);
TYPE table_flags IS TABLE OF record_flags INDEX BY PLS_INTEGER;
BEGIN
SELECT IS_DELETE, IS_EDITABLE BULK COLLECT INTO table_flags.is_delete, table_flags.is_editable
FROM (table and where clause...)
The query returns 2 rows exactly always. One row has 2 fields. I need to save them all into variable(s). But Oracle is swearing at me for this syntax (underlines just before word bulk). Hope it is clear what i want to achieve. what is the correct syntax here?
declare table_flags like below and try
table_flags1 table_flags;
SELECT IS_DELETE, IS_EDITABLE BULK COLLECT INTO table_flags1
FROM your_table

If the first field doesn't exists in a table then look at a different field in the same table

Is there a way to select a field from a table and if that field doesn't exist then select a different field from the same table? example:
SELECT MY_FIELD from MY_TABLE
error: "MY_FIELD": invalid identifier
is there any way to check if it exists and if it does then use that field for the query, if it doesn't exist then use example:
SELECT my_field2 from client.
My problem is
I am writing a report that will be used on two databases, but the field names on occasion can be named slightly different depending on the database.
What you really need to do is talk to your management / development leads about why the different databases are not harmonized. But, since this is a programming site, here is a programming answer using dynamic SQL.
As has been pointed out, you could create views in the different databases to provide yourself with a harmonized layer to query from. If you are unable to create views, you can do something like this:
create table test ( present_column NUMBER );
insert into test select rownum * 10 from dual connect by rownum <= 5;
declare
l_rc SYS_REFCURSOR;
begin
BEGIN
OPEN l_rc FOR 'SELECT missing_column FROM test';
EXCEPTION
WHEN others THEN
OPEN l_rc FOR 'SELECT present_column FROM test';
END;
-- This next only works in 12c and later
-- In earlier versions, you've got to process l_rc on your own.
DBMS_SQL.RETURN_RESULT(l_rc);
end;
This is inferior to the other solutions (either harmonizing the databases or creating views). For one thing, you get no compile time checking of your queries this way.
That won't compile, so - I'd say not. You might try with dynamic SQL which reads contents of the USER_TAB_COLUMNS and create SELECT statement on-the-fly.
Depending on reporting tool you use, that might (or might not) be possible. For example, Apex offers (as reports's source) a function that returns query, so you might use it there.
I'd suggest a simpler option - create views on both databases which have unified column names, so that your report always selects from the view and works all the time. For example:
-- database 1:
create view v_client as
select client_id id,
client_name name
from your_table;
-- database 2:
create view v_client as
select clid id,
clnam name
from your_table;
-- reporting tool:
select id, name
from v_client;
This can be done in a single SQL statement using DBMS_XMLGEN.GETXML, but it gets messy. It would probably be cleaner to use dynamic SQL or a view, but there are times when it's difficult to create supporting objects.
Sample table:
--Create either table.
create table my_table(my_field1 number);
insert into my_table values(1);
insert into my_table values(2);
create table my_table(my_field2 number);
insert into my_table values(1);
insert into my_table values(2);
Query:
--Get the results by converting XML into rows.
select my_field
from
(
--Convert to an XMLType.
select xmltype(clob_results) xml_results
from
(
--Conditionally select either MY_FIELD1 or MY_FIELD2, depending on which exists.
select dbms_xmlgen.GetXML('select my_field1 my_field from my_table') clob_results
from user_tab_columns
where table_name = 'MY_TABLE'
and column_name = 'MY_FIELD1'
--Stop transformations from running the XMLType conversion on nulls.
and rownum >= 1
union all
select dbms_xmlgen.GetXML('select my_field2 my_field from my_table') clob_results
from user_tab_columns
where table_name = 'MY_TABLE'
and column_name = 'MY_FIELD2'
--Stop transformations from running the XMLType conversion on nulls.
and rownum >= 1
)
--Only convert non-null values.
where clob_results is not null
)
cross join
xmltable
(
'/ROWSET/ROW'
passing xml_results
columns
my_field number path 'MY_FIELD'
);
Results:
MY_FIELD
--------
1
2
Here's a SQL Fiddle if you want to see it running.

How to store multiple rows in a variable in pl/sql function?

I'm writing a pl/sql function. I need to select multiple rows from select statement:
SELECT pel.ceid
FROM pa_exception_list pel
WHERE trunc(pel.creation_date) >= trunc(SYSDATE-7)
if i use:
SELECT pel.ceid
INTO v_ceid
it only stores one value, but i need to store all values that this select returns. Given that this is a function i can't just use simple select because i get error, "INTO - is expected."
You can use a record type to do that. The below example should work for you
DECLARE
TYPE v_array_type IS VARRAY (10) OF NUMBER;
var v_array_type;
BEGIN
SELECT x
BULK COLLECT INTO
var
FROM (
SELECT 1 x
FROM dual
UNION
SELECT 2 x
FROM dual
UNION
SELECT 3 x
FROM dual
);
FOR I IN 1..3 LOOP
dbms_output.put_line(var(I));
END LOOP;
END;
So in your case, it would be something like
select pel.ceid
BULK COLLECT INTO <variable which you create>
from pa_exception_list
where trunc(pel.creation_Date) >= trunc(sysdate-7);
If you really need to store multiple rows, check BULK COLLECT INTO statement and examples. But maybe FOR cursor LOOP and row-by-row processing would be better decision.
You may store all in a rowtype parameter and show whichever column you want to show( assuming ceid is your primary key column, col1 & 2 are some other columns of your table ) :
SQL> set serveroutput on;
SQL> declare
l_exp pa_exception_list%rowtype;
begin
for c in ( select *
from pa_exception_list pel
where trunc(pel.creation_date) >= trunc(SYSDATE-7)
) -- to select multiple rows
loop
select *
into l_exp
from pa_exception_list
where ceid = c.ceid; -- to render only one row( ceid is primary key )
dbms_output.put_line(l_exp.ceid||' - '||l_exp.col1||' - '||l_exp.col2); -- to show the results
end loop;
end;
/
SET SERVEROUTPUT ON
BEGIN
FOR rec IN (
--an implicit cursor is created here
SELECT pel.ceid AS ceid
FROM pa_exception_list pel
WHERE trunc(pel.creation_date) >= trunc(SYSDATE-7)
)
LOOP
dbms_output.put_line(rec.ceid);
END LOOP;
END;
/
Notes from here:
In this case, the cursor FOR LOOP declares, opens, fetches from, and
closes an implicit cursor. However, the implicit cursor is internal;
therefore, you cannot reference it.
Note that Oracle Database automatically optimizes a cursor FOR LOOP to
work similarly to a BULK COLLECT query. Although your code looks as if
it fetched one row at a time, Oracle Database fetches multiple rows at
a time and allows you to process each row individually.

How do I insert multiple rows into temporary tables?

I am using variable to store multiple rows and I want to insert it into temporary table.
This query returns multiple rows
BEGIN
SELECT id INTO
temp_var
FROM TABLE_1 a,
TABLE_2 b
where a.id =b.id;
EXCEPTION
WHEN NO_DATA_FOUND THEN
temp_var := NULL;
END;
I want to insert variable values into temporary table?
Assumption: when you say "temporary table" you mean the SQL Server usage, which is a PL/SQL collection in Oracle.
Here a variable temp_var is a nested table with a composite structure which matches the projection of table_1.
declare
type table1_nt is table of table_1%rowtype;
temp_var table1_nt;
begin
select *
bulk collection into temp_var
from table_1;
....
end;
This gives you the contents of table_1 in an array which you can work with in PL/SQL. Find out more.
Given your revised requirement, it's easy enough to work with a single attribute instead:
declare
type id_nt is table of varchar2(20); -- table_1.id%type
temp_var id_nt;
begin
select a.id
bulk collection into temp_var
from table_1 a,
join table_2 b
on a.id =b.id; ;
....
end;

Looping in PL/SQL

Basically I want to select data from a table based on id's of another table. So the algorithm goes:
foreach i in (select distinct id from table1)
{
select * from table2 where table2.id=i;
}
How to perform this function with an SQL query? I understand that we could use joins etc instead of looping, however, my requirement is such that I need to pass id's one by one in a for-loop only.
select * from table2 where table2.id IN (select distinct id from table1)
Use for instead of foreach.
Place PL/SQL into a begin .... end; block (but see the declare below)
The =i should read =i.id as i is the complete record from the select statement, but you're only interested in its id field.
In PL/SQL, a select statement must be fetched into a variable. Therefore, you have to declare an according variable: r table1%rowtype
The declaration of such variables is in the declare ... section of PL/SQL blocks.
The "algorithm" then becomes
declare
r table2%rowtype;
begin
for i in (select distinct id from table1) loop
select * into r from table2 where table2.id = i.id;
end loop;
end;