INSERT INTO TAB SELECT * FROM TABLE TAB#db2 - Too many values - sql

I'm trying to move rows between two tables which have many columns.
The table columns are identical other than the destination table (tab#db2) has a few more columns which causes a simple INSERT to fail.
I'd like to use a simple PL/SQL statement to build a list of the columns in tab#db2 dynamically instead of typing out the names of col1, col2, etc in the INSERT and SELECT clause. Example
declare a variable as var_col_list
set col_list = output of select * from tab (omitting rows)
INSERT INTO TAB *var_col_list* SELECT *var_cols_list* FROM TABLE TAB#db2
I've researched using %rowtype but cannot find a suitable example that would take less time than simply writing out the names of the columns!
Any advice is greatly appreciated

If you use e.g. TOAD, you can right-click the table and let it Generate statement - in your case, that would be INSERT. You'd slightly modify it (remove columns you don't need) and that's all.
Otherwise, this is how you might do it semi-manually.
This is my source table:
SQL> SELECT * FROM dept;
DEPTNO DNAME LOC
---------- -------------------- --------------------
10 ACCOUNTING NEW YORK
20 RESEARCH DALLAS
30 SALES CHICAGO
40 OPERATIONS BOSTON
Target table doesn't contain all columns:
SQL> CREATE TABLE target
2 (
3 deptno NUMBER,
4 dname VARCHAR2 (20)
5 );
Table created.
Code which loops through all TARGET table columns (i.e. a table which has less columns) and composes the INSERT INTO statement:
SQL> DECLARE
2 l_str VARCHAR2 (1000);
3 BEGIN
4 FOR cur_r IN (SELECT column_name
5 FROM user_tab_columns
6 WHERE table_name = 'TARGET')
7 LOOP
8 l_str := l_str || ', ' || cur_r.column_name;
9 END LOOP;
10
11 l_str :=
12 'insert into target select ' || LTRIM (l_str, ', ') || ' from dept';
13 DBMS_OUTPUT.put_line (l_str);
14
15 EXECUTE IMMEDIATE l_str;
16 END;
17 /
insert into target select DEPTNO, DNAME from dept --> this is the L_STR contents
PL/SQL procedure successfully completed.
SQL> SELECT * FROM target;
DEPTNO DNAME
---------- --------------------
10 ACCOUNTING
20 RESEARCH
30 SALES
40 OPERATIONS
Seems to be OK.

Using the solution provided by Littefoot, I made some minor tweaks to fit my requirement perfectly:
SQL> create table taba (col1 number,col2 number);
SQL> insert into taba values (1,2);
SQL> select * from taba;
COL1 COL2
---------- ----------
1 2
SQL> create table tabb (col1 number,col2 number, col3 number);
SQL> DECLARE
l_str VARCHAR2 (32767);
BEGIN
FOR cur_r IN (SELECT column_name
FROM user_tab_columns
WHERE table_name = 'TABA'
order by column_id asc)
LOOP
l_str := l_str || ', ' || cur_r.column_name;
END LOOP;
l_str :=
'insert into tabb (' || LTRIM (l_str, ', ') || ') ' ||' select ' || LTRIM (l_str, ', ') || ' from taba';
DBMS_OUTPUT.put_line (l_str);
EXECUTE IMMEDIATE l_str;
END;
/
Output of l_str (SQL INSERT):
insert into tabb (COL1, COL2) select COL1, COL2 from taba
Result:
SQL> select * from tabb;
COL1 COL2 COL3
---------- ---------- ----------
1 2

Related

How can I Create a generic procedure for inserting data from a table?

I have a table:
create table D_DEFAULT
(
table_name VARCHAR2(200),
column_name VARCHAR2(200),
column_value VARCHAR2(200)
);
TABLE_NAME
COLUMN_NAME
COLUMN_VALUE
GCG_RTM
ID
-1
GCG_RTM
FILIAL_CODE
US
GCG_RTM
SK_ID
-1
GCG_RTM
SUBS_ID
-1
In my project i have to take data from columns COLUMN_NAME and COLUMN_VALUE
and insert it into another table.
Something like this:
select LISTAGG(COLUMN_NAME, ','), LISTAGG(COLUMN_VALUE, ',')
into v_columns, v_values
from D_DEFAULT
where TABLE_NAME = p_table || '_RTM';
v_sql := 'insert into table ' || p_table || ' values (' || v_values|| ') ';
It is dynamic SQL you need (as you more or less figured out yourself).
Sample data and table:
SQL> select * From d_default;
TABLE_NAME COLUMN_NAME COLUMN_VALUE
--------------- --------------- ---------------
GCG_RTM ID -1
GCG_RTM FILIAL_CODE US
SQL> desc gcg_rtm
Name Null? Type
----------------------------------------- -------- ----------------------------
ID NUMBER
FILIAL_CODE VARCHAR2(5)
Code:
SQL> declare
2 l_tab varchar2(30);
3 l_cols varchar2(200);
4 l_vals varchar2(200);
5 l_str varchar2(2000);
6 begin
7 select table_name,
8 listagg(column_name, ',') within group (order by rowid),
9 chr(39) || listagg(column_value, chr(39) ||','|| chr(39)) within group (order by rowid) ||chr(39)
10 into l_tab, l_cols, l_vals
11 from d_default
12 where table_name = 'GCG_RTM'
13 group by table_name;
14
15 l_str := 'insert into ' || l_tab || ' (' ||
16 l_cols ||') values (' || l_vals || ')';
17
18 execute immediate l_str;
19 end;
20 /
PL/SQL procedure successfully completed.
Result:
SQL> select * from gcg_rtm;
ID FILIA
---------- -----
-1 US
SQL>
Problems you can expect: different datatypes. It is easy to insert strings or numbers (and rely on Oracle's implicit datatype conversion), but - what about e.g. dates? How to apply TO_DATE function? Which format model will you use? Maybe your D_DEFAULT table lacks in some more info - at least datatype and format_model.

How to read the same column from every table in a database?

I have a huge database with 400+ tables. Each table has the same column id for the Primary key and "timestamp_modify" in which the last change of the table is done.
So what I want are 2 things:
Now I want a list of all changes by ID and table name like:
Table | id | timestamp_modiy
Kid | 1 | 24.10.2021 00:01
Parent | 1000 | 24.10.2021 00:02
The only, very bad way I could come up with, is that I make a view in which I include every damn table by hand and read out the values...
Is there a better way?
How about a pipelined function?
Just setting datetime format (you don't have to do that):
SQL> alter session set nls_date_format = 'dd.mm.yyyy hh24:mi:ss';
Session altered.
Types:
SQL> create or replace type t_row as object
2 (table_name varchar2(30),
3 id number,
4 timestamp_modify date)
5 /
Type created.
SQL> create or replace type t_tab is table of t_row;
2 /
Type created.
Function: querying user_tab_columns, its cursor FOR loop fetches tables that contain both ID and TIMESTAMP_MODIFY columns, dynamically creates select statement to return the last (MAX function, to avoid too_many_rows) columns' values for the last TIMESTAMP_MODIFY value (returned by the subquery).
SQL> create or replace function f_test
2 return t_tab pipelined
3 as
4 l_str varchar2(500);
5 l_id number;
6 l_timestamp_modify date;
7 begin
8 for cur_r in (select table_name from user_tab_columns
9 where column_name = 'ID'
10 intersect
11 select table_name from user_tab_columns
12 where column_name = 'TIMESTAMP_MODIFY'
13 )
14 loop
15 l_str := 'select max(a.id) id, max(a.timestamp_modify) timestamp_modify ' ||
16 'from ' || cur_r.table_name || ' a ' ||
17 'where a.timestamp_modify = ' ||
18 ' (select max(b.timestamp_modify) ' ||
19 ' from ' || cur_r.table_name || ' b ' ||
20 ' where b.id = a.id)';
21 execute immediate l_str into l_id, l_timestamp_modify;
22 pipe row(t_row(cur_r.table_name, l_id, l_timestamp_modify));
23 end loop;
24 end;
25 /
Function created.
Testing:
SQL> select * from table(f_test);
TABLE_NAME ID TIMESTAMP_MODIFY
------------------------------ ---------- -------------------
TABA 1 24.10.2021 14:59:29
TAB_1 1 24.10.2021 15:03:16
TAB_2 25 24.10.2021 15:03:36
TEST 5 24.10.2021 15:04:24
SQL>
Yes, the only way is to union all all tables, like:
select id, timestamp_modify
from kid
union all
select id, timestamp_modify
from parent
union all
...
The performance will be awful, since all the tables will be scanned every time :(
I think that you might reconsider you db design...
You can build a procedure for this, but even so it will have some impact in performance. Although there is a loop, with SQL Dynamic, you might only need 400 iterations, and in each one you will insert all the ids of that table.
I am taking some assumptions
You want all the IDs and their corresponding timestamp_modify per table
I create a table to store the results. If you use it with the same name always it will recycle the object. If you not, you can keep a history
I am assuming that only one timestamp_modify row is present per ID
I filter only the tables of your schema that contain both columns.
The table contains also the table_name that you can identify where the record is coming from.
One example
create or replace procedure pr_build_output ( p_tmp_table in varchar2 default 'TMP_RESULT' )
is
vcounter pls_integer;
vsql clob;
vtimestamp date; -- or timestamp
begin
-- create table to store results
select count(*) into vcounter from all_tables where table_name = upper(p_tmp_table) and owner = 'MY_SCHEMA';
if vcounter = 1
then
execute immediate ' drop table '||p_tmp_table||' purge ' ;
end if;
vsql := ' create table '||p_tmp_table||'
( table_name varchar2(128) ,
id number,
timestamp_modify date -- or timestamp
) ';
execute immediate vsql ;
-- Populate rows
for h in
( select a.table_name from all_tables a
where a.owner = 'MY_SCHEMA'
and a.table_name in ( select distinct b.table_name from all_tab_columns b where b.owner = 'MY_SCHEMA'
and b.column_name = 'ID' and b.column_name = 'TIMESTAMP_MODIFY'
)
)
loop
vsql := ' insert into '||p_tmp_table||' ( table_name , id, timestamp_modify )
select '''||h.table_name||''' as table_name , id , timestamp_modify
from my_schema.'||h.table_name||'
' ;
execute immediate vsql ;
commit ;
end loop;
exception when others then raise;
end;
/

Convert string from a table to be used as a column for selection for another table

I have a table where I store records to be used as a column name for my queries where the record is an actual column on another table.
TBL_1
COL_1
==========
SAMPLE_COL
TBL_2
SAMPLE_COL_1 SAMPLE_COL2
============ ===========
ABC DEF
I'm having a problem using the record that I fetched to use as an actual column. I already tried a bunch of things like casting and using case (using case works but it's a bit of a brute force and I'm looking for a more elegant way of doing this).
This is a sample query that I have tried:
SELECT (SELECT column_1 FROM tbl_1)
FROM tbl_2
Expected output
SAMPLE_COL_1
============
ABC
Actual output
(SELECT column_1 FROM tbl_1)
============================
SAMPLE_COL_1
This is what I've tried that worked so far but a brute force technique
SELECT (
CASE
WHEN (SELECT column_1 FROM tbl_2) = 'SAMPLE_COL_1' THEN SAMPLE_COL_1
ELSE SAMPLE_COL_2
END
)
FROM tbl_2
Appreciate the help! Keep safe from COVID-19 everyone :)
It's not that easy as you'd want it to be - you'll have to use dynamic SQL. Here's an example, based on Scott's table(s).
Create a function that accepts table and column names and returns ref cursor.
SQL> create or replace function f_test
2 (par_table_name in varchar2, par_column_name in varchar2)
3 return sys_refcursor
4 is
5 rc sys_refcursor;
6 begin
7 open rc for 'select ' || dbms_assert.simple_sql_name(par_column_name) ||
8 ' from ' || dbms_assert.sql_object_name(par_table_name);
9 return rc;
10 end;
11 /
Function created.
Testing:
SQL> select f_test('dept', 'dname') from dual;
F_TEST('DEPT','DNAME
--------------------
CURSOR STATEMENT : 1
CURSOR STATEMENT : 1
DNAME
--------------
ACCOUNTING
RESEARCH
SALES
OPERATIONS
SQL> select f_test('dual', 'dummy') from dual;
F_TEST('DUAL','DUMMY
--------------------
CURSOR STATEMENT : 1
CURSOR STATEMENT : 1
D
-
X
SQL>
Another example, with column (and table) names stored in a table (something like you posted).
Table that contains those info and the function:
SQL> select * from tbl_1;
TNAM CNAME
---- -----
dept dname
dual dummy
SQL> create or replace function f_test
2 (par_table_name in varchar2)
3 return sys_refcursor
4 is
5 l_str varchar2(1000);
6 rc sys_refcursor;
7 begin
8 select 'select ' || dbms_assert.simple_sql_name(cname) ||
9 ' from ' || dbms_assert.sql_object_name(tname)
10 into l_str
11 from tbl_1
12 where tname = dbms_assert.sql_object_name(par_table_name);
13 open rc for l_str;
14 return rc;
15 end;
16 /
Function created.
Testing:
SQL> select f_test('dept') from dual;
F_TEST('DEPT')
--------------------
CURSOR STATEMENT : 1
CURSOR STATEMENT : 1
DNAME
--------------
ACCOUNTING
RESEARCH
SALES
OPERATIONS
SQL>

How to use 'sysdate' if its a string constant

I extract data from a table, the field is mostly null, but sometimes it's sysdate. However since its from a table, after getting the field its 'sysdate', between single quotes. How can I use it?
I have tried to_date, to_date(to_char()).
I need something that works within
select to_date('sysdate') from dual;
You can use a case expression:
select case
when the_column = 'sysdate' then sysdate
else to_date(the_column)
end as date_value
from the_table;
The only way I know is dynamic SQL. Here's an example:
SQL> create table test (id number, col varchar2(20));
Table created.
SQL> insert into test
2 select 1, '''sysdate''' from dual union all
3 select 2, null from dual;
2 rows created.
SQL> declare
2 l_res test%rowtype;
3 l_str varchar2(200);
4 begin
5 for cur_r in (select id, col from test) loop
6 l_str := 'select ' || cur_r.id ||', '||
7 nvl(replace(cur_r.col, chr(39), null), 'null') || ' from dual';
8 execute immediate l_str into l_res;
9 dbms_output.put_line(l_res.id ||': '|| l_res.col);
10 end loop;
11 end;
12 /
1: 24.06.2019 12:18:39
2:
PL/SQL procedure successfully completed.
SQL>

Need help on writing sql query with dynamic columns

I have to write a query which does below. I tried but couldn't write. Please help me.
I have table which returns below result set.
select *
from table1; --(rowid and ColumnName are columns of the table)
Output:
rowid ColumnName
------------------------------
1 Segment1
2 Segment2
I have another table which has below structure : (Segment1 and Segment2 are columns here)
select *
from table2;
Output:
appId Segment1 Segment2 Segment3
---------------------------------------------
a1 fld1 fld2 per
a2 cmp1 hcd4 klp
I need to write a query, which reads the "ColumnName" values from first table and retrieves column values in the second table.
That means, from the table1, I will know what are the available columns I the table2 and from table2, I will know what is the data stored against those columns.
Please let me know if I am not clear.
This query is in Oracle SQL
As mentioned in the comment you need a PLSQL block with dynamic sql. See below an example:
Tables:
create table table1 (row_id number,
ColumnName varchar2(100))
create table table2 (appId number,
Segment1 varchar2(100),
Segment2 varchar2(100),
Segment3 varchar2(100));
Insert all
into TABLE1 (ROW_ID, COLUMNNAME) Values (1, 'Segment1')
into TABLE1 (ROW_ID, COLUMNNAME) Values (2, 'Segment2')
into TABLE2 (APPID, SEGMENT1, SEGMENT2, SEGMENT3) Values (1, 'RRR', 'KKK', 'MMM')
into TABLE2 (APPID, SEGMENT1, SEGMENT2, SEGMENT3) Values (2, 'ZZZ', 'PPP', 'QQQ')
into TABLE2 (APPID, SEGMENT1, SEGMENT2, SEGMENT3) Values (3, 'LLL', 'NNN', 'DDD')
select * from dual;
Code:
DECLARE
var VARCHAR2 (1000);
v_sql VARCHAR2 (2000);
TYPE x_var IS TABLE OF VARCHAR2(1000);
z_var x_var;
num number:=0;
BEGIN
FOR rec IN ( SELECT DISTINCT columnname
FROM table1
ORDER BY 1)
LOOP
num := num +1;
if num = 1 then
var:= rec.columnname;
else
var := var || ' || '' , ''||' || rec.columnname;
end if;
END LOOP;
var := RTRIM (LTRIM (var, ','), ',');
v_sql := 'select '|| var ||' from table2';
EXECUTE IMMEDIATE v_sql BULK COLLECT INTO z_var;
FOR i IN 1 .. z_var.COUNT
LOOP
DBMS_OUTPUT.put_line (z_var(i));
END LOOP;
END;
Output:
SQL> /
RRR , KKK
ZZZ , PPP
LLL , NNN
Dynamic columns in a SQL statement are almost always a bad idea. There's usually a way to avoid these kind of problems and build a simpler solution.
But if this is one of those rare times when you really need to run dynamic SQL in SQL then you'll need to install and run something like my open source project Method4.
For example:
create table table1 as
select 1 id, 'Segment1' columnName from dual union all
select 2 id, 'Segment2' columnName from dual;
create table table2 as
select 'a1' appId, 'fld1' Segment1, 'fld2' Segment2, 'per' Segment3 from dual union all
select 'a2' appId, 'cmp1' Segment1, 'hcd4' Segment2, 'klp' Segment3 from dual;
select * from table(method4.dynamic_query(
q'[
select
'select appID, '
||listagg(columnName, ',') within group (order by id)
||' from table2'
sql_statement
from table1
]'
));
APPID SEGMENT1 SEGMENT2
----- -------- --------
a1 fld1 fld2
a2 cmp1 hcd4
There are a lot of downsides to running this way. The code is complicated, slow, and has some odd behavior. For an explanation of how this works, see this article
by Adrian Billington.
Will the below PL SQL block help your requirement.
BEGIN
FOR iter IN (
SELECT column_name
FROM all_tab_columns
WHERE upper(table_name) = 'table1'
AND UPPER(column_name) LIKE 'SEGMENT%'
)
LOOP
SELECT iter.column_name INTO temp_table FROM table1
dbms_output.put_line(temp_table.column_name);
END LOOP;
END;
/
Say you have tables like the following:
SQL> select * from someTable;
COLUMN1 COLUMN2 COLUMN3
---------- ---------- ----------
1 2 3
2 4 6
3 6 9
SQL> select * from tableOfColumns;
COLUMNN
-------
column1
column3
You may need something like the following:
SQL> declare
2 type tListOfResults is table of varchar2(1000);
3 vSQL varchar2(1000);
4 vResult tListOfResults ;
5 begin
6 select 'select ' || listagg (columnName, ' || '', '' || ') within group (order by columnName) || ' from someTable'
7 into vSQL
8 from tableOfColumns;
9 --
10 execute immediate vSQL bulk collect into vResult;
11 if vResult.count() > 0 then
12 for i in vResult.first .. vResult.last loop
13 dbms_output.put_line(vResult(i));
14 end loop;
15 end if;
16 end;
17 /
1, 3
2, 6
3, 9
PL/SQL procedure successfully completed.