Create table from many tables with specific data - sql

We have oracle11g.
I need to write stored procedure (or may be another solution like creating view) that will truncute old table and insert new updated data.
We have many tables in database with name %table_name%+number_prefix.
For example: Country_1, Country_2.
Here is data from this tables:
id code country
103858 834 TZ
103878 834 UA
103859 800 UG
103860 894 ZM
103861 716 ZW
103862 24 AO
Also we have table Country_all, with specific information from all this tables.
If users add new table from web gui new Country_%prefix% table, i have to update Country_all table. This table should group by code and put all country in one row (TZ, UA, UG ...):
table code countries
91 2005202000 ,AD,AL,AQ,AS,AT,
91 2005400000 ,AD,AL,AQ,AS,AT,
91 2005995000 ,AD,AL,AQ,AS,AT,
91 2005997000 ,AD,AL,AQ,AS,AT,
91 2006003100 ,AD,AL,AQ,AS,AT,
Here 91 means that this data from table Country_91
I use this to get prefix from table:
EXECUTE IMMEDIATE 'truncate table COUNTRY_ALL';
FOR r IN (SELECT regexp_replace(table_name,'(.*)_', '\1') ADD_NUM
FROM all_tables
WHERE upper(table_name)
LIKE ('COUNTRY_%'))
But when i try to use r.ADD_NUM i have trouble.
INSERT INTO COUNTRY_ALL
SELECT r.ADD_NUM, code, listagg(code, ',') WITHIN GROUP (ORDER BY code) AS GI_COUNTRIES
FROM 'COUNTRY_' || r.ADD_NUM
GROUP BY code;
I also try to declare some variable and assign COUNTRY_ || r.ADD_NUM to the variable, but it also don't want to query from variable.
UPDATE
Finally i did it yesterday!
I did it in two ways. The first one is exactly what i was asking:
BEGIN
EXECUTE IMMEDIATE 'truncate table Countries_ALL';
FOR r IN (SELECT regexp_replace(table_name,'_(.*)', '\1') ADD_NUM
FROM all_tables
WHERE upper(table_name)
LIKE ('COUNTRY_%')
AND owner='owner_name')
loop
EXECUTE IMMEDIATE 'INSERT COUNTRIES_ALL
SELECT '|| r.ADD_NUM ||' as ADD_NUM, code, listagg(countries, '','') WITHIN GROUP (ORDER BY countries) AS COUNTRIES
FROM COUNTRY_' || r.ADD_NUM || '
GROUP by code;
END loop;
END;
The second one is better. I create 2 temporary tables: CURRENT and FOR_UPDATING.
To fill CURRENT:
INSERT INTO CURRENT
SELECT DISTINCT ADD_NUM FROM COUNTRIES_ALL;
And procedure that will update only not existing table, unlike previous one:
DECLARE
BEGIN
execute IMMEDIATE 'TRUNCATE table for_updating';
INSERT INTO for_updating
SELECT regexp_replace(table_name,'_(.*)', '\5 ') as ADD_NUM
FROM all_tables
WHERE owner='owner_name'
AND table_name LIKE 'COUNTRY_%';
for num in (
SELECT b.add_num FROM CURRENT A
RIGHT JOIN for_updating b
ON A.add_num=b.add_num
WHERE A.add_num IS NULL)
loop
EXECUTE IMMEDIATE 'INSERT INTO COUNTRIES_ALL
SELECT '||num.add_num||' as ADD_NUM, code, listagg(countries, '','') WITHIN GROUP (ORDER BY countries) AS GI_COUNTRIES
FROM COUNTRY_' || num.ADD_NUM || '
GROUP by code';
end loop;
END;
In this procedure i compare what tables i already have in COUNTRIES_ALL and what i have in all_tables. Then all new tables will be inserted.
Thanks to Przemyslaw Kruglej for his patience!

You can't run a query with dynamic name of the table.
You have to either generate a query for each of the tables, use dbms_sql package or EXECUTE IMMEDIATE.
Do you know the number of the table you are supposed to work on? If so, you can use EXECUTE IMMEDIATE:
EXECUTE IMMEDIATE
'INSERT INTO COUNTRY_ALL
SELECT r.ADD_NUM, code, listagg(code, '','') WITHIN GROUP (ORDER BY code) AS GI_COUNTRIES
FROM COUNTRY_' || tab_number ||
' GROUP BY code';

Related

LOOP through and select from multiple tables in SQL developer

I am trying to loop through 5 tables all with a similar naming format. I am able to return the tables names by using the query
select table_name from all tables
where table_name like '
order by table_name
My intention is to insert the data from all the 5 tables into one table where a particular date condition is met. I know I can do this via union all but this makes my script very long as I have to paste the same script for 5 tables, I was wondering if there is a way to do without using union all but rather using dynamic sql? I am new to dynamic sql.
It might look like this:
begin
for cur_r in (select table_name
from all_tables
where table_name like 'ABC%'
)
loop
execute immediate
'insert into target_table (id, name, address) ' ||
'select id, name, address from ' || cur_r.table_name ||
' where date_column = ' || trunc(sysdate);
end loop;
end;
/
loop through all_tables, fetching only those you're interested in
compose insert statement, presuming that all tables (from previous step) share the same set of common columns
include where clause, if you want

SQL Procedure to Return Counts of All Unique IDs in Some Tables within a Schema

I want to write a stored procedure that grabs all of the tables in a schema that have a column called "ID" , once I have those tables I want to output the counts of unique IDs within each table.
ex.
table1
table2
Output:
I have the latter portion implemented but the first part I'm having difficulties with. Here's my script so far:
create or replace PROCEDURE get_id_counts
IS
BEGIN
FOR table_n in (SELECT * FROM dba_tables) LOOP
EXECUTE IMMEDIATE 'SELECT ID, COUNT(*) FROM MySchema.' || table_n.table_name || ' GROUP BY ID';
END LOOP;
END;
/
execute get_id_counts;
Currently I receive the error : "%s: invalid identifier", I assume this is caused by the fact that not all of the tables in the schema have the column "ID"
Assuming that your database is Oracle
Use the dba_tab_columns to get only those which has ID column
So extend your query from
SELECT * FROM dba_tables
to
SELECT * FROM dba_tables where table_name in (select table_name from dba_tab_columns where column_name = 'ID');

How can I loop to get counts from multiple tables?

I am trying to derive a table with counts from multiple tables. The tables are not on my schema. The table names on the schema that I am interested in all start with 'STAF_' and end with '_TS'. The criteria i am looking for is where SEP = 'MO'. So for example, the query in its base form is:
select area, count(SEP) areacount
from mous.STAF_0001_TS
where SEP = 'MO'
group by area;
I have about 1000 tables that i'd like to do this for.
Ultimatly, I'd like the output to be a table on my schema that looks like the following:
area| areacount
0001| 3
0002| 7
0003| 438
Thank you.
As a first step I'd write an SQL query that generates an SQL query:
SELECT 'SELECT area, count(*) FROM '||c.table_name||'UNION ALL' as run_me
FROM all_tables c
WHERE c.table_name LIKE 'STAF\_%\_MS' escape '\'
Running this will produce an output that is another SQL query. Copy the result text out of your results grid and paste it back into your query pane. Delete the final UNION ALL and run it
Once you dig how to write an SQL query that generate an SQL query, you can look at turning it into a view, or creating a dynamic query in a string.
Gotta say, this is a horrible way to store data; you'd be better off using ONE table with an extra column containing whatever is in xxx of STAF_xxx_MS right now
In Oracle 12c, you can embed a FUNCTION that will query the number of rows in any given table. Then you can use that function in your main query. Here is an example:
WITH FUNCTION cnt ( p_owner VARCHAR2, p_table_name VARCHAR2 ) RETURN NUMBER IS
l_cnt NUMBER;
BEGIN
EXECUTE IMMEDIATE 'SELECT count(*) INTO :cnt FROM ' || p_owner || '.' || p_table_name INTO l_cnt;
RETURN l_cnt;
EXCEPTION WHEN OTHERS THEN
RETURN NULL; -- This will happen for entries in ALL_TABLES that are not directly accessible (e.g., IOT overflow tables)
END cnt;
SELECT t.owner, t.table_name, cnt(t.owner, t.table_name)
FROM all_tables t
where t.table_Name like 'STAF\_%\_MS' escape '\';

Collecting the last updates of multiple tables into a single table

I have a problem in that I want my output to be a single table (lets call it Output) with 2 columns: one for the "TableName" and one for the DateTime of the last update (using the scn_to_timestamp(max(ora_rowscn)) command).
I have 100 tables and I want to pull in the last update date/times for all these tables into the Output table.
So I can do this:
insert into Output(TableName)
select table_name
from all_tables;
which will put all the tables I have from my database into the TableName column. But I don't know how to loop through each entry and use the tablename as a variable and pass this into the scn_to_timestamp(ora_rowscn).
I thought I would try something like below:
for counter in Output(TableName) LOOP
insert into Output(UpdateDate)
select scn_to_timestamp(max(ora_rowscn))
from counter;
END LOOP;
Any suggestions?
Thank you
This query is a little bit clumsy as it uses xmlgen to execute dynamic sql in a query, but it might work for you.
select x.*
from all_tables t,
xmltable('/ROWSET/ROW' passing
dbms_xmlgen.getxmltype('select ''' || t.table_name ||
''' tab_name, max(ora_rowscn) as la from ' ||
t.table_name)
COLUMNS tab_name varchar2(30) PATH 'TAB_NAME',
max_scn number PATH 'LA') x
Here is a sqlfiddle demo
You can also use PLSQL and then use execute immediate

How to choose tables on select from all_tables?

I have the following table name template, there are a couple with the same name and a number at the end: fmj.backup_semaforo_geo_THENUMBER, for example:
select * from fmj.backup_semaforo_geo_06391442
select * from fmj.backup_semaforo_geo_06398164
...
Lets say I need to select a column from every table which succeeds with the 'fmj.backup_semaforo_geo_%' filter, I tried this:
SELECT calle --This column is from the backup_semaforo_geo_# tables
FROM (SELECT table_name
FROM all_tables
WHERE owner = 'FMJ' AND table_name LIKE 'BACKUP_SEMAFORO_GEO_%');
But I'm getting the all_tables tables name data:
TABLE_NAME
----------
BACKUP_SEMAFORO_GEO_06391442
BACKUP_SEMAFORO_GEO_06398164
...
How can I achieve that without getting the all_tables output?
Thanks.
Presumably your current query is getting ORA-00904: "CALLE": invalid identifier, because the subquery doesn't have a column called CALLE. You can't provide a table name to a query at runtime like that, unfortunately, and have to resort to dynamic SQL.
Something like this will loop through all the tables and for each one will get all the values of CALLE from each one, which you can then loop through. I've used DBMS_OUTPUT to display them, assuming you're doing this in SQL*Plus or something that can deal with that; but you may want to do something else with them.
set serveroutput on
declare
-- declare a local collection type we can use for bulk collect; use any table
-- that has the column, or if there isn't a stable one use the actual data
-- type, varchar2(30) or whatever is appropriate
type t_values is table of table.calle%type;
-- declare an instance of that type
l_values t_values;
-- declare a cursor to generate the dynamic SQL; where this is done is a
-- matter of taste (can use 'open x for select ...', then fetch, etc.)
-- If you run the query on its own you'll see the individual selects from
-- all the tables
cursor c1 is
select table_name,
'select calle from ' || owner ||'.'|| table_name as query
from all_tables
where owner = 'FMJ'
and table_name like 'BACKUP_SEMAFORO_GEO%'
order by table_name;
begin
-- loop around all the dynamic queries from the cursor
for r1 in c1 loop
-- for each one, execute it as dynamic SQL, with a bulk collect into
-- the collection type created above
execute immediate r1.query bulk collect into l_values;
-- loop around all the elements in the collection, and print each one
for i in 1..l_values.count loop
dbms_output.put_line(r1.table_name ||': ' || l_values(i));
end loop;
end loop;
end;
/
May be a dynamic SQL in a PLSQL program;
for a in (SELECT table_name
FROM all_tables
WHERE owner = 'FMJ' AND table_name LIKE 'BACKUP_SEMAFORO_GEO_%')
LOOP
sql_stmt := ' SELECT calle FROM' || a.table_name;
EXECUTE IMMEDIATE sql_stmt;
...
...
END LOOP;