PL/SQL create view base on dynamic table name - oracle

I'm trying to create a view that access all the tables starting with the same name, they have the exactly same structure, and in time there will be more.
Table Names:
TEMP_ENTITIES_1000
TEMP_ENTITIES_1001
TEMP_ENTITIES_1002
and in the future there will be
TEMP_ENTITIES_1003
TEMP_ENTITIES_1004
and so on...
What I need is to use list of tables from the following script and then use the result the list of object name result to access in a view.
select object_name
from user_objects
where object_type = 'TABLE'
and object_name like upper('temp_entities_%');
create view entities_join as
select * from (object_name)
Is it possible to achieve?

In modern versions of Oracle you may use SQL table macros without scheduled anything. It will build dynamic query on-the-fly and may be used as a plain view.
Below is the example:
SQL> insert all
2 when mod(rownum, 5) = 0 then into TEMP_ENTITIES_1000 values (l, dt, val)
3 when mod(rownum, 5) = 1 then into TEMP_ENTITIES_1001 values (l, dt, val)
4 when mod(rownum, 5) = 2 then into TEMP_ENTITIES_1002 values (l, dt, val)
5 when mod(rownum, 5) = 3 then into TEMP_ENTITIES_1003 values (l, dt, val)
6 when mod(rownum, 5) = 4 then into TEMP_ENTITIES_1004 values (l, dt, val)
7
8 select
9 level as l,
10 sysdate + level as dt,
11 level as val
12 from dual
13 connect by level < 10;
9 rows inserted.
SQL>
SQL> create or replace function f_temp_entities_union
2 /*Create a macro*/
3 return varchar2 SQL_MACRO
4 is
5 v_union varchar2(4000);
6 begin
7 select listagg('select ''' || table_name || ''' as src, a.* from ' || table_name || ' a ', chr(10) || 'union all' || chr(10))
8 into v_union
9 from all_tables
10 where table_name like 'TEMP_ENTITIES%'
11 and owner = sys_context('USERENV', 'CURRENT_SCHEMA')
12 ;
13
14 return v_union;
15 end;
16 /
Function F_TEMP_ENTITIES_UNION compiled
SQL>
SQL> select *
2 from f_temp_entities_union();
TEMP_ENTITIES_1000 5 20.09.21 5
TEMP_ENTITIES_1001 1 16.09.21 1
TEMP_ENTITIES_1001 6 21.09.21 6
TEMP_ENTITIES_1002 2 17.09.21 2
TEMP_ENTITIES_1002 7 22.09.21 7
TEMP_ENTITIES_1003 3 18.09.21 3
TEMP_ENTITIES_1003 8 23.09.21 8
TEMP_ENTITIES_1004 4 19.09.21 4
TEMP_ENTITIES_1004 9 24.09.21 9
9 rows selected.
SQL>
SQL> /*Add new table*/
SQL> create table TEMP_ENTITIES_1005 as select * from TEMP_ENTITIES_1001 where 1 = 0;
Table TEMP_ENTITIES_1005 created.
SQL> insert into TEMP_ENTITIES_1005
2 select 1000 + rownum, sysdate + rownum, rownum - 100 from TEMP_ENTITIES_1000;
1 row inserted.
SQL>
SQL> /*Check that new data is here*/
SQL> select *
2 from v_demo;
TEMP_ENTITIES_1000 5 20.09.21 5
TEMP_ENTITIES_1001 1 16.09.21 1
TEMP_ENTITIES_1001 6 21.09.21 6
TEMP_ENTITIES_1002 2 17.09.21 2
TEMP_ENTITIES_1002 7 22.09.21 7
TEMP_ENTITIES_1003 3 18.09.21 3
TEMP_ENTITIES_1003 8 23.09.21 8
TEMP_ENTITIES_1004 4 19.09.21 4
TEMP_ENTITIES_1004 9 24.09.21 9
TEMP_ENTITIES_1005 1001 16.09.21 -99
10 rows selected.

Is it possible? Yes, using dynamic SQL.
However, from what you described, it looks that data model you use is wrong. You should have only one table with identifier that makes the difference (those 1000, 1001, ... values).
Then you wouldn't need a view at all, and do everything with that single table. I suggest you consider doing so.
Here's an example of what you might do (the way you asked):
Several sample tables:
SQL> select * from temp_entities_1000;
ID NAME
---------- ------
1000 Little
SQL> select * from temp_entities_1001;
ID NAME
---------- ----
1001 Foot
SQL> select * from temp_entities_1002;
ID NAME
---------- -----
1002 Scott
Procedure:
SQL> DECLARE
2 l_str VARCHAR2 (2000);
3 BEGIN
4 FOR cur_r IN (SELECT object_name
5 FROM user_objects
6 WHERE object_type = 'TABLE'
7 AND object_name LIKE 'TEMP_ENTITIES%')
8 LOOP
9 l_str :=
10 l_str || ' select * from ' || cur_r.object_name || ' union all ';
11 END LOOP;
12
13 l_str :=
14 'create or replace view entities_join as '
15 || RTRIM (l_str, ' union all');
16
17 EXECUTE IMMEDIATE l_str;
18 END;
19 /
PL/SQL procedure successfully completed.
Result:
SQL> select * from entities_join;
ID NAME
---------- ------
1000 Little
1001 Foot
1002 Scott
SQL>
You'd have to run that procedure every time new table is created so that it is included into the view.

Related

How to take dbms output of an object in oracle 19c?

create or replace type pb_calculate_bill_ot as object ( bill id number(9),
version number(2),
rate number(5),
descp varchar2(25)
);
create or replace type pb_calculate_bill_ct is table of pb_calculate_bill_ot;
----- inside a package -----
procedure select_price ( pi_bill id in number,
pi_version in number,
po_data_ct out pb_calculate_bill_ct) IS
lt_calculate_bill_ct pb_calculate_bill_ct := pb_calculate_bill_ct();
-- procedure functionality--
end select_price;
--------- calling this proc inside same pkg ----------
pkg.select_price ( pi_bill_id => pi_bill,
pi_version => pi_version,
po_data_ct => lt_calculate_bill_ct);
how to take dbms_output of lt_calculate_bill_ct ???
Here's an example.
Sample table:
SQL> CREATE TABLE bill
2 AS
3 SELECT 1 bill_id, 20 version, 1234 rate, 'Little' descp FROM DUAL
4 UNION ALL
5 SELECT 2, 13, 434, 'Foot' FROM DUAL;
Table created.
Types you created:
SQL> CREATE OR REPLACE TYPE pb_calculate_bill_ot AS OBJECT
2 (
3 bill_id NUMBER (9),
4 version NUMBER (2),
5 rate NUMBER (5),
6 descp VARCHAR2 (25)
7 );
8 /
Type created.
SQL> CREATE OR REPLACE TYPE pb_calculate_bill_ct IS TABLE OF pb_calculate_bill_ot;
2 /
Type created.
Sample procedure:
SQL> CREATE OR REPLACE PROCEDURE select_price (
2 pi_bill_id IN NUMBER,
3 pi_version IN NUMBER,
4 po_data_ct OUT pb_calculate_bill_ct)
5 IS
6 lt_calculate_bill_ct pb_calculate_bill_ct := pb_calculate_bill_ct ();
7 BEGIN
8 SELECT pb_calculate_bill_ot (bill_id,
9 version,
10 rate,
11 descp)
12 BULK COLLECT INTO lt_calculate_bill_ct
13 FROM bill
14 WHERE bill_id = pi_bill_id
15 AND pi_version = pi_version;
16
17 po_data_ct := lt_calculate_bill_ct;
18 END select_price;
19 /
Procedure created.
Testing (this is what you asked for): declare a local variable which will hold result returned by the procedure; then, in a loop, do something with the result - I displayed it using dbms_output.put_line:
SQL> DECLARE
2 l_res pb_calculate_bill_ct;
3 BEGIN
4 select_price (1, 20, l_res);
5
6 FOR i IN 1 .. l_res.COUNT
7 LOOP
8 DBMS_OUTPUT.put_line (
9 'Bill ID = '
10 || l_res (i).bill_id
11 || ', version = '
12 || l_res (i).version
13 || ', rate = '
14 || l_res (i).rate
15 || ', description = '
16 || l_res (i).descp);
17 END LOOP;
18 END;
19 /
Bill ID = 1, version = 20, rate = 1234, description = Little
PL/SQL procedure successfully completed.
SQL>

how to write get 4 activities against profile_id= 1 by using select satement?

I want to write a select query to get 4 activities against profile id= 1.
Aha; screenshot reveals the secret - activity ID is passed as a comma-separated values string having 4 values (e.g. '1,6,7,8') which make those "4 activities"; each of them should be stored into its own row.
Sample table:
SQL> create table activity_profile_mapping
2 (profile_id number,
3 activity_id number,
4 created_by varchar2(10),
5 created timestamp
6 );
Table created.
Procedure: the trick is to split p_activity_id into rows.
SQL> create or replace procedure apm_add
2 (p_profile_id in activity_profile_mapping.profile_id%type,
3 p_activity_id in varchar2,
4 p_created_by in varchar2
5 )
6 as
7 /* P_ACTIVITY_ID is passed as comma-separated values string and
8 contains 4 values, e.g. '1,6,7,8'. They represent "4 activities
9 from the title
10 */
11 begin
12 insert into activity_profile_mapping
13 (profile_id,
14 activity_id,
15 created_by,
16 created
17 )
18 select p_profile_id,
19 regexp_substr(p_activity_id, '[^,]+', 1, level),
20 p_created_by,
21 systimestamp
22 from dual
23 connect by level <= regexp_count(p_activity_id, ',') + 1;
24 end apm_add;
25 /
Procedure created.
Testing:
SQL> begin
2 apm_add (p_profile_id => 100,
3 p_activity_id => '1,6,7,8',
4 p_created_by => 'Littlefoot');
5 end;
6 /
PL/SQL procedure successfully completed.
SQL> select * from activity_profile_mapping;
PROFILE_ID ACTIVITY_ID CREATED_BY CREATED
---------- ----------- ---------- ------------------------------
100 1 Littlefoot 07.12.21 19:51:52,480000
100 6 Littlefoot 07.12.21 19:51:52,480000
100 7 Littlefoot 07.12.21 19:51:52,480000
100 8 Littlefoot 07.12.21 19:51:52,480000
SQL>

How to insert records using loops

So this code is basically trying to add 5 cars into the location called 'garage' where the 'car_id' is + 1 so each 'car_id' should be different with the use of a package called 'Package_Store.cars'
For example, car0001 should have a 'car_id' of 1 and car0002 should have a 'car_id' of 2 etc.
I think the issue I am getting is with the 'car_id' as it is not incrementing for car0002 till car0005
This is the error message.
00000 - "unique constraint (%s.%s) violated"
*Cause: An UPDATE or INSERT statement attempted to insert a duplicate key.
The code
Your code always inserts the same CAR_ID, hence the error.
However, even if you fix it, it'll work in a single-user environment. If two (or more) users run the same code, you're about to hit the same error again because sooner or later they will generate the same MAX + 1 value. What to do? Switch to a sequence.
If this is just for your own amusement, then you might e.g.
SQL> DECLARE
2 car_id NUMBER;
3 car_str VARCHAR2 (10);
4 BEGIN
5 FOR i IN 1 .. 5
6 LOOP
7 SELECT MAX (id) + i
8 INTO car_id
9 FROM vehicles
10 WHERE id < 10;
11
12 car_str := 'car' || LPAD (i, 4, '0');
13
14 dbms_output.put_line('car_id = ' || car_id ||', car_str = ' || car_str);
15
16 Package_Store.cars (car_id, 'garage', car_str, 1, 4);
17 END LOOP;
18 END;
19 /
car_id = 4, car_str = car0001
car_id = 5, car_str = car0002
car_id = 6, car_str = car0003
car_id = 7, car_str = car0004
car_id = 8, car_str = car0005
PL/SQL procedure successfully completed.
SQL>
You may also use plain SQL INSERTfor this simple task
insert into garage
(car_id, location, col1, col2)
select
rownum car_id, 'car'|| to_char(rownum,'FM0999'),1,4
from dual
connect by level <= 5;
select * from garage
CAR_ID LOCATION COL1 COL2
---------- ---------- ---------- ----------
1 car0001 1 4
2 car0002 1 4
3 car0003 1 4
4 car0004 1 4
5 car0005 1 4
If the table is not empty, adjust the INSERTas follows to use new ids and car names
insert into garage
(car_id, location, col1, col2)
with ci as
(select nvl(max(car_id),0) car_id from garage)
select
car_id+rownum, 'car'|| to_char(car_id+rownum,'FM0999'),1,4
from ci
connect by level <= 5;

E-mail result of 'select' statement having multiple rows as result set in ORACLE

I am new to ORACLE PL/SQL world. I am trying to figure out a way to calculate something as below.
Let's say you have a MASTER_TABLE as below :
SELECT * FROM MASTER_TABLE;
+----------+----------+------------------+-----------------------+
| SCHEMA | TABLE_NM | REQUIRED_COLUMNS | TABLE_FILTER |
+----------+----------+------------------+-----------------------+
| USER_SCH | A | A1,A2,A3 | EXAM_DT > SYSDATE - 1 |
| USER_SCH | B | B1,B2 | TRUNC(SYSDATE) |
+----------+----------+------------------+-----------------------+
I would like to generate SELECT query from above table such as below:
SELECT 'SELECT SCHEMA || '.' || TABLE_NM ||' WHERE '|| TABLE_FILTER FROM MASTER_TABLE;
Obviously, the result of above query would generate multiple select statements.
Now, I want to execute all such SELECT statements and send out the resultset via e-mail.
The tricky part is, the columns mentioned in the MASTER_TABLE varies (i.e. For table 'A' there can be 3 REQUIRED_COLUMNS to be selected, For table 'B' there can be 2 REQUIRED_COLUMNS to be selected - As shown in the MASTER_TABLE)
I have the e-mail utility ready which basically takes an argument as your_message and sends it out via e-mail.
Here is what I have tried :
Created CURSOR to generate such select statements.
Tried inserting the resultset (LIST OF SELECT QUERIES) to another temp table by concatenating the columns.
(i.e.
SELECT
'SELECT '
|| replace(required_columns, ',', '||'',''||')
|| ' AS MSG_BDY'
|| ' FROM '
|| schema
|| '.'
|| table_nm
|| ' WHERE '
|| table_filter
as my_select_stmt
FROM
master_table;
I am stuck after this.
Can you please help me out ? or is there any approach to achieve this ?.
Note : Tables mentioned in MASTER_TABLE can have 1 or more rows.
I don't have your tables so I used Scott's.
Master table:
SQL> select * From master_table;
SCHEM TABL REQUIRED_COLUMN TABLE_FILTER
----- ---- --------------- ----------------------
scott emp ename, job, sal hiredate < sysdate - 1
scott dept dname, loc deptno = 20
SQL>
Procedure which simulates your mailing procedure; I'll just display those values.
SQL> CREATE OR REPLACE PROCEDURE p_mail (par_result IN SYS.odcivarchar2list)
2 AS
3 BEGIN
4 FOR i IN par_result.FIRST .. par_result.LAST
5 LOOP
6 DBMS_OUTPUT.put_line (par_result (i));
7 END LOOP;
8 END;
9 /
Procedure created.
SQL>
Procedure you actually need; as you composed the select statement(s), now you only have to run them. In order to do so, use dynamic SQL (e.g. execute immediate):
SQL> SET SERVEROUTPUT ON
SQL>
SQL> DECLARE
2 retval SYS.odcivarchar2list;
3 BEGIN
4 FOR cur_r
5 IN (SELECT 'SELECT '
6 || REPLACE (required_columns, ',', '||'',''||')
7 || ' AS MSG_BDY'
8 || ' FROM '
9 || schema
10 || '.'
11 || table_nm
12 || ' WHERE '
13 || table_filter
14 AS my_select_stmt
15 FROM master_table)
16 LOOP
17 EXECUTE IMMEDIATE cur_r.my_select_stmt BULK COLLECT INTO retval;
18
19 -- you'd call your mailing procedure here
20 p_mail (retval);
21 END LOOP;
22 END;
23 /
SMITH,CLERK,920
ALLEN,SALESMAN,1600
WARD,SALESMAN,1250
JONES,MANAGER,2975
MARTIN,SALESMAN,1250
BLAKE,MANAGER,2850
CLARK,MANAGER,2450
SCOTT,ANALYST,3000
KING,PRESIDENT,10000
TURNER,SALESMAN,1500
ADAMS,CLERK,1300
JAMES,CLERK,950
FORD,ANALYST,3000
MILLER,CLERK,1300
RESEARCH,DALLAS
PL/SQL procedure successfully completed.
SQL>
[EDIT: what if you wanted to display 'null' for missing values?]
Well, that's a new moment - probably not very simple. See if this helps.
In order to help myself, I modified master_table and added ID column to uniquely identify every row. It'll be used to split required columns' list to rows, apply NVL to them, apply CAST to columns (because NVL complains if datatypes don't match), aggregate them back using listagg. As this is quite a lot to do, I'm going to create a view and use it instead of the table itself.
SQL> CREATE OR REPLACE VIEW v_master_table
2 AS
3 SELECT id,
4 schema,
5 table_nm,
6 LISTAGG ('NVL(cast(' || col || ' as varchar2(20)), ''null'')', '||'',''||')
7 WITHIN GROUP (ORDER BY lvl)
8 required_columns,
9 table_filter
10 FROM (SELECT id,
11 schema,
12 table_nm,
13 table_filter,
14 COLUMN_VALUE lvl,
15 TRIM (REGEXP_SUBSTR (required_columns,
16 '[^,]+',
17 1,
18 COLUMN_VALUE))
19 col
20 FROM master_table
21 CROSS JOIN
22 TABLE (
23 CAST (
24 MULTISET (
25 SELECT LEVEL
26 FROM DUAL
27 CONNECT BY LEVEL <=
28 REGEXP_COUNT (required_columns,
29 ',')
30 + 1) AS SYS.odcinumberlist)))
31 GROUP BY id,
32 schema,
33 table_nm,
34 table_filter;
View created.
For example, it now looks like this:
SQL> select * from v_master_table where id = 2;
ID SCHEM TABL REQUIRED_COLUMNS TABLE_FILTER
--- ----- ---- ------------------------------------------------------------------------------------- ------------
2 scott dept NVL(cast(dname as varchar2(20)), 'null')||','||NVL(cast(loc as varchar2(20)), 'null') deptno = 20
SQL>
The mailing procedure remains the same, no change.
Anonymous PL/SQL block is slightly changed - I removed REPLACE you previously used as view does it now; also, source is the view, not the table.
SQL> alter session set nls_date_format = 'dd.mm.yyyy';
Session altered.
SQL> DECLARE
2 retval SYS.odcivarchar2list;
3 BEGIN
4 FOR cur_r
5 IN (SELECT 'SELECT '
6 || required_columns
7 || ' AS MSG_BDY'
8 || ' FROM '
9 || schema
10 || '.'
11 || table_nm
12 || ' WHERE '
13 || table_filter
14 AS my_select_stmt
15 FROM v_master_table)
16 LOOP
17 EXECUTE IMMEDIATE cur_r.my_select_stmt BULK COLLECT INTO retval;
18
19 -- you'd call your mailing procedure here
20 p_mail (retval);
21 END LOOP;
22 END;
23 /
CLARK,09.06.1981,null
KING,17.11.1981,null
MILLER,23.01.1982,null
RESEARCH,DALLAS
PL/SQL procedure successfully completed.
SQL>

Getting Ranges from a series of numbers from a table and storing all ranges in a string variable in PLSQL/Oracle Forms

I have a table containing a series of numbers 1,2,3,4,5,11,12,13,14,15,101,102,103,104,105,510,511,512,513,515,516,517.
I want an PL/SQL Function so that I can get the ranges and then store all all the ranges in a single string variable in the following format.
"1-5, 11-15, 101-105, 510-517".
I have get a code to generate the rages in SQL*Plus, but it did not work in PL/SQL and forms. The procedure is given below:
SQL> SET SERVEROUTPUT ON
SQL> DECLARE
2 v_list VARCHAR2(100);
3 BEGIN
4 SELECT listagg(RANGE, ',') WITHIN GROUP(
5 ORDER BY min_num)
6 INTO v_list
7 FROM
8 (SELECT MIN(num) min_num,
9 MIN(num)
10 ||'-'
11 || MAX(num) range
12 FROM
13 (SELECT num, num-Row_Number() over(order by num) AS rn FROM t
14 )
15 GROUP BY rn
16 );
17 dbms_output.put_line(v_list);
18 END;
19 /
1-3,5-7,10-12,20-20
As mentioned here, since the output is a list of strings, you could declare and store the output in a varchar2 variable.
You could create a procedure and put the entire logic in it.
For example,
Setup
SQL> CREATE TABLE t AS
2 SELECT *
3 FROM
4 ( WITH data(num) AS
5 ( SELECT 1 FROM dual
6 UNION
7 SELECT 2 FROM dual
8 UNION
9 SELECT 3 FROM dual
10 UNION
11 SELECT 5 FROM dual
12 UNION
13 SELECT 6 FROM dual
14 UNION
15 SELECT 7 FROM dual
16 UNION
17 SELECT 10 FROM dual
18 UNION
19 SELECT 11 FROM dual
20 UNION
21 SELECT 12 FROM dual
22 UNION
23 SELECT 20 FROM dual
24 )
25 SELECT * FROM DATA);
Table created.
Procedure
SQL> CREATE OR REPLACE
2 PROCEDURE p_get_list
3 AS
4 v_list VARCHAR2(100);
5 BEGIN
6 SELECT listagg(RANGE, ',') WITHIN GROUP(
7 ORDER BY min_num)
8 INTO v_list
9 FROM
10 (SELECT MIN(num) min_num,
11 MIN(num)
12 ||'-'
13 || MAX(num) range
14 FROM
15 (SELECT num, num-Row_Number() over(order by num) AS rn FROM t
16 )
17 GROUP BY rn
18 );
19 dbms_output.put_line(v_list);
20 END;
21 /
Procedure created.
Test case
SQL> SET SERVEROUTPUT ON
SQL> BEGIN
2 p_get_list;
3 END;
4 /
1-3,5-7,10-12,20-20
PL/SQL procedure successfully completed.
You could simply call the procedure in your Oracle Forms.

Resources