A more efficient way to do a select insert that involves over 300 columns? - sql

I am trying to find a more efficient way to write PL/SQL Query to
to select insert from a table with 300+ columns, to the back up version of that table (same column names + 2 extra columns).
I could simply type out all the column names in the script (example below), but with that many names, it will bother me... :(
INSERT INTO
TABLE_TEMP
(column1, column2, column3, etc)
(SELECT column1, column2, column3, etc FROM TABLE WHERE id = USER_ID);
Thanks in advance

Specify literals/null for those two extra columns.
INSERT INTO
TABLE_TEMP
SELECT t1.*, null, null FROM TABLE t1 WHERE id = USER_ID

You can pretty easily build a column list for any given table:
select table_catalog
,table_schema
,table_name
,string_agg(column_name, ', ' order by ordinal_position)
from information_schema.columns
where table_catalog = 'catalog_name'
and table_schema = 'schema_name'
and table_name = 'table_name'
group by table_catalog
,table_schema
,table_name
That should get you nearly where you need to be.

The question tag says plsql, which is Oracle or one of its variants. Here is an example of doing it in Oracle:
drop table brianl.deleteme1;
drop table brianl.deleteme2;
CREATE TABLE brianl.deleteme1
(
a INTEGER
, b INTEGER
, c INTEGER
, efg INTEGER
);
CREATE TABLE brianl.deleteme2
(
b INTEGER
, c INTEGER
, d INTEGER
, efg INTEGER
);
DECLARE
l_ownerfrom VARCHAR2 (30) := 'BRIANL';
l_tablefrom VARCHAR2 (30) := 'DELETEME1';
l_ownerto VARCHAR2 (30) := 'BRIANL';
l_tableto VARCHAR2 (30) := 'DELETEME2';
l_comma VARCHAR2 (1) := NULL;
BEGIN
DBMS_OUTPUT.put_line ('insert into ' || l_ownerto || '.' || l_tableto || '(');
FOR eachrec IN ( SELECT f.column_name
FROM all_tab_cols f INNER JOIN all_tab_cols t ON (f.column_name = t.column_name)
WHERE f.owner = l_ownerfrom
AND f.table_name = l_tablefrom
AND t.owner = l_ownerto
AND t.table_name = l_tableto
ORDER BY f.column_name)
LOOP
DBMS_OUTPUT.put_line (l_comma || eachrec.column_name);
l_comma := ',';
END LOOP;
DBMS_OUTPUT.put_line (') select ');
l_comma := NULL;
FOR eachrec IN ( SELECT f.column_name
FROM all_tab_cols f INNER JOIN all_tab_cols t ON (f.column_name = t.column_name)
WHERE f.owner = l_ownerfrom
AND f.table_name = l_tablefrom
AND t.owner = l_ownerto
AND t.table_name = l_tableto
ORDER BY f.column_name)
LOOP
DBMS_OUTPUT.put_line (l_comma || eachrec.column_name);
l_comma := ',';
END LOOP;
DBMS_OUTPUT.put_line (' from ' || l_ownerfrom || '.' || l_tablefrom || ';');
END;
This results in this output:
insert into BRIANL.DELETEME2(
B
,C
,EFG
) select
B
,C
,EFG
from BRIANL.DELETEME1;
Nicely formatted:
INSERT INTO brianl.deleteme2 (b, c, efg)
SELECT b, c, efg
FROM brianl.deleteme1;

Related

How to get last column in SQL?

For example I have a table with data:
Column1 Column2 Column3 ... Column1xx
1 a 10 x
2 b 20 y
3 c 30 z
What SQL query would get me only the last column?
Column1xx
x
y
z
Note: Column Name and Index of the last column is unknown
Fill your_table_schema and your_table_name. Originated from the post https://stackoverflow.com/a/38993544/18396231
SELECT COLUMN_NAME, ORDINAL_POSITION
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = '{your_table_schema}'
AND TABLE_NAME ='{your_table_name}'
ORDER BY ORDINAL_POSITION DESC
LIMIT 1;
create so test data:
CREATE TABLE test.test.testo(aa number, bb number, cc number);
INSERT INTO test.test.testo values (1,10,100),(2,20,200),(3,30,300);
use tasozgurcem11 SQL to show we can get the column name:
SELECT COLUMN_NAME
FROM test.information_schema.COLUMNS
WHERE TABLE_SCHEMA = 'TEST'
AND TABLE_NAME ='TESTO'
ORDER BY ORDINAL_POSITION DESC
LIMIT 1;
and the put that into Snowflake Scripting to having it run dynamically:
DECLARE
query STRING;
col_name STRING;
res RESULTSET;
BEGIN
LET _database := 'TEST';
LET _table := 'TESTO';
LET _schema := 'TEST';
SELECT COLUMN_NAME INTO :col_name
FROM test.information_schema.COLUMNS
WHERE TABLE_SCHEMA = :_schema
AND TABLE_NAME = :_table
ORDER BY ORDINAL_POSITION DESC
LIMIT 1;
QUERY := 'SELECT '|| :col_name ||' FROM '|| _database ||'.'|| _schema ||'.'|| _table ||';';
res := (EXECUTE IMMEDIATE :QUERY);
return table(res);
END;
gives:
CC
100
200
300
this may help you where a function returns the last column name.
create or replace TABLE TEST_TABLE (
ID NUMBER(38,0),
C1 NUMBER(38,0),
C2 VARCHAR(16777216) MASKING POLICY TEST_HK.PUBLIC.MASK_DATA,
C3 DATE
);
CREATE OR REPLACE function LST_COLUMN(tab_nam varchar)
RETURNS VARCHAR
AS 'select column_name from information_schema.columns where table_name = tab_nam and ORDINAL_POSITION
= (SELECT max(ORDINAL_POSITION) from information_schema.columns where table_name = tab_nam)';
select LST_COLUMN('TEST_TABLE') from test_table;

Oracle insert data from another table without column names

I have two tables. They differ only columns order.
First
Table1
(
name,
surname,
age
)
Second
Table2
(
age
surname,
name
)
I want insert data to Table2 from Table1.
If tables column order is the same I can use
insert into Table2
select * from Table1
I know that I can solve this problem with
insert into table2
select age,surname,name from table1
But I don't use it because there are many column in my real table.
is there good idea for it?
The only way is with some dynamic SQL, by relying on column names; for example, say you have the tables
CREATE TABLE Table1
(
name VARCHAR2(100),
surname VARCHAR2(100),
age NUMBER
);
CREATE TABLE Table2
(
name VARCHAR2(100),
age NUMBER,
oneMoreColumn NUMBER,
surname VARCHAR2(100)
);
you can do:
declare
vSQL varchar2(1000);
vCols varchar2(1000);
begin
select listagg(tc1.column_name, ', ') within group (order by tc1.column_name)
into vCols
from user_tab_columns tc1
inner join user_tab_columns tc2
on(tc1.column_name = tc2.column_name)
where tc1.table_name = 'TABLE1'
and tc2.table_name = 'TABLE2';
--
vSQL := 'insert into table2( ' || vCols || ') select ' || vCols || ' from table1';
--
dbms_output.put_line(vSQL);
--
execute immediate vSQL;
end;
this will build and execute the statement:
insert into table2( AGE, NAME, SURNAME) select AGE, NAME, SURNAME from table1
you can do like this
create table EX_EMPLOYEE
(
NAME VARCHAR2(100),
PATH VARCHAR2(1000)
)
SET serveroutput ON size 2000
/
declare T_COL varchar2(50);
CURSOR c1 IS SELECT column_name name FROM user_tab_cols where table_name='EX_EMPLOYEE';
BEGIN
FOR rec IN c1 LOOP
if T_COL is null then
T_COL := T_COL || rec.name;
else
T_COL := T_COL ||' ,' || rec.name;
end if;
END LOOP;
dbms_output.put_line('select '|| T_COL ||' FROM EX_EMPLOYEE');
END;
/
select NAME ,PATH FROM EX_EMPLOYEE
PL/SQL procedure successfully completed
you could do something like this:
create table new_table as
select * from old_table

CREATE AS SELECT * but with one column obtained from another table

I need to 'recreate' over 50 tables (in Oracle) with CREATE AS SELECT statements. However, all this tables will have one column modified using data from another table. Is there a way to achieve this without declaring each column in the SELECT statement?
Something like:
CREATE TABLE table_name_copy AS SELECT *, (SELECT col_name FROM other_table WHERE other_table.col_id = table_name.col_id) AS col_name FROM table_name`
Basically on all tables I have a column which needs to be replaced with the data in the other_table column.
Generate the SQL string as such:
SELECT 'CREATE TABLE table_name_copy AS SELECT '
|| LISTAGG (column_name, ', ') WITHIN GROUP (ORDER BY column_name)
|| ', (SELECT col_name FROM other_table
WHERE other_table.col_id = table_name.col_id) AS col_name'
|| ' FROM table_name'
FROM all_tab_cols
WHERE owner = 'OWNER'
AND table_name = 'TABLE_NAME'
AND column_name != 'COL_NAME'
If you want to run the above statement, you could use EXECUTE IMMEDIATE:
DECLARE
v_sql VARCHAR2(10000);
BEGIN
SELECT 'CREATE TABLE table_name_copy AS SELECT '
|| LISTAGG (column_name, ', ') WITHIN GROUP (ORDER BY column_name)
|| ', (SELECT col_name FROM other_table
WHERE other_table.col_id = table_name.col_id) AS col_name'
|| ' FROM table_name'
INTO v_sql
FROM all_tab_cols
WHERE owner = 'OWNER'
AND table_name = 'TABLE_NAME'
AND column_name != 'COL_NAME';
EXECUTE IMMEDIATE v_sql;
END;
/
If col_id column is fixed for both of the joined tables,
you may use user_tab_columns and user_tables dictionary views through the schema to produce new tables named as "table_name_copy" by using the following mechanism :
declare
v_ddl varchar2(4000);
v_cln varchar2(400);
begin
for c in ( select *
from user_tables t
where t.table_name in
( select c.table_name
from user_tab_columns c
where c.column_name = 'COL_ID' )
order by t.table_name )
loop
v_ddl := 'create table '||c.table_name||'_copy as
select ';
for d in ( select listagg('t1.'||column_name, ',') within group ( order by column_name ) cln
from user_tab_columns
where table_name = c.table_name
and column_name != 'COL_ID' )
loop
v_cln := v_cln||d.cln;
end loop;
v_ddl := v_ddl||v_cln;
v_ddl := v_ddl||', t2.col_id t2_id
from '||c.table_name||' t1
left outer join other_table t2 on ( t1.col_id = t2.col_id )';
execute immediate v_ddl;
v_ddl := null;
v_cln := null;
end loop;
end;
Maybe you can use a simple join and an asterisk to return all columns from the first table, like that:
CREATE TABLE table_name_copy AS
SELECT * FROM (
SELECT tab1.*, tab2.column_name
FROM table_name tab1 LEFT JOIN other_table tab2 ON tab1.col_id = tab2.col_id
);
I would try this (but I don't have Oracle SQL to test on so please leave me the benefit of the doubt)
CREATE TABLE table_name_copy AS
SELECT * FROM (
SELECT *, (SELECT col_name FROM other_table WHERE other_table.col_id = table_name.col_id) as col_name
FROM table_name`
)
edit:
then run
ALTER TABLE table_name_copy DROP COLUMN <old column>
to remove the column you don't need any more

Count the number of null values into an Oracle table?

I need to count the number of null values of all the columns in a table in Oracle.
For instance, I execute the following statements to create a table TEST and insert data.
CREATE TABLE TEST
( A VARCHAR2(20 BYTE),
B VARCHAR2(20 BYTE),
C VARCHAR2(20 BYTE)
);
Insert into TEST (A) values ('a');
Insert into TEST (B) values ('b');
Insert into TEST (C) values ('c');
Now, I write the following code to compute the number of null values in the table TEST:
declare
cnt number :=0;
temp number :=0;
begin
for r in ( select column_name, data_type
from user_tab_columns
where table_name = upper('test')
order by column_id )
loop
if r.data_type <> 'NOT NULL' then
select count(*) into temp FROM TEST where r.column_name IS NULL;
cnt := cnt + temp;
END IF;
end loop;
dbms_output.put_line('Total: '||cnt);
end;
/
It returns 0, when the expected value is 6.
Where is the error?
Thanks in advance.
Counting NULLs for each column
In order to count NULL values for all columns of a table T you could run
SELECT COUNT(*) - COUNT(col1) col1_nulls
, COUNT(*) - COUNT(col2) col2_nulls
,..
, COUNT(*) - COUNT(colN) colN_nulls
, COUNT(*) total_rows
FROM T
/
Where col1, col2, .., colN should be replaced with actual names of columns of T table.
Aggregate functions -like COUNT()- ignore NULL values, so COUNT(*) - COUNT(col) will give you how many nulls for each column.
Summarize all NULLs of a table
If you want to know how many fields are NULL, I mean every NULL of every record you can
WITH d as (
SELECT COUNT(*) - COUNT(col1) col1_nulls
, COUNT(*) - COUNT(col2) col2_nulls
,..
, COUNT(*) - COUNT(colN) colN_nulls
, COUNT(*) total_rows
FROM T
) SELECT col1_nulls + col1_nulls +..+ colN_null
FROM d
/
Summarize all NULLs of a table (using Oracle dictionary tables)
Following is an improvement in which you need to now nothing but table name and it is very easy to code a function based on it
DECLARE
T VARCHAR2(64) := '<YOUR TABLE NAME>';
expr VARCHAR2(32767);
q INTEGER;
BEGIN
SELECT 'SELECT /*+FULL(T) PARALLEL(T)*/' || COUNT(*) || ' * COUNT(*) OVER () - ' || LISTAGG('COUNT(' || COLUMN_NAME || ')', ' + ') WITHIN GROUP (ORDER BY COLUMN_ID) || ' FROM ' || T
INTO expr
FROM USER_TAB_COLUMNS
WHERE TABLE_NAME = T;
-- This line is for debugging purposes only
DBMS_OUTPUT.PUT_LINE(expr);
EXECUTE IMMEDIATE expr INTO q;
DBMS_OUTPUT.PUT_LINE(q);
END;
/
Due to calculation implies a full table scan, code produced in expr variable was optimized for parallel running.
User defined function null_fields
Function version, also includes an optional parameter to be able to run on other schemas.
CREATE OR REPLACE FUNCTION null_fields(table_name IN VARCHAR2, owner IN VARCHAR2 DEFAULT USER)
RETURN INTEGER IS
T VARCHAR2(64) := UPPER(table_name);
o VARCHAR2(64) := UPPER(owner);
expr VARCHAR2(32767);
q INTEGER;
BEGIN
SELECT 'SELECT /*+FULL(T) PARALLEL(T)*/' || COUNT(*) || ' * COUNT(*) OVER () - ' || listagg('COUNT(' || column_name || ')', ' + ') WITHIN GROUP (ORDER BY column_id) || ' FROM ' || o || '.' || T || ' t'
INTO expr
FROM all_tab_columns
WHERE table_name = T;
EXECUTE IMMEDIATE expr INTO q;
RETURN q;
END;
/
-- Usage 1
SELECT null_fields('<your table name>') FROM dual
/
-- Usage 2
SELECT null_fields('<your table name>', '<table owner>') FROM dual
/
Thank you #Lord Peter :
The below PL/SQL script works
declare
cnt number :=0;
temp number :=0;
begin
for r in ( select column_name, nullable
from user_tab_columns
where table_name = upper('test')
order by column_id )
loop
if r.nullable = 'Y' then
EXECUTE IMMEDIATE 'SELECT count(*) FROM test where '|| r.column_name ||' IS NULL' into temp ;
cnt := cnt + temp;
END IF;
end loop;
dbms_output.put_line('Total: '||cnt);
end;
/
The table name test may be replaced the name of table of your interest.
I hope this solution is useful!
The dynamic SQL you execute (this is the string used in EXECUTE IMMEDIATE) should be
select sum(
decode(a,null,1,0)
+decode(b,null,1,0)
+decode(c,null,1,0)
) nullcols
from test;
Where each summand corresponds to a NOT NULL column.
Here only one table scan is necessary to get the result.
Use the data dictionary to find the number of NULL values almost instantly:
select sum(num_nulls) sum_num_nulls
from all_tab_columns
where owner = user
and table_name = 'TEST';
SUM_NUM_NULLS
-------------
6
The values will only be correct if optimizer statistics were gathered recently and if they were gathered with the default value for the sample size.
Those may seem like large caveats but it's worth becoming familiar with your database's statistics gathering process anyway. If your database is not automatically gathering statistics or if your database is not using the default sample size those are likely huge problems you need to be aware of.
To manually gather stats for a specific table a statement like this will work:
begin
dbms_stats.gather_table_stats(user, 'TEST');
end;
/
select COUNT(1) TOTAL from table where COLUMN is NULL;

I need to change one table datatype same as other in netezza, by just passing table name?

I have one table named L0 which is created as:
create table L0 (
name varchar,
number varchar,
address varchar
);
The data type for all the columns present in L0 is varchar,
I have another table L1 which is created as:
create table L1 (
name varchar,
number int,
address char
);
I want to convert the data types of L0 table to be same as L1 table, by just passing the table name.
I want the final query look like this:
select cast(name as varchar), cast(number as int), cast(address as char) from L0
minus
select * from L1:
What is the way to do it?
If you have group_concat UDA installed in Netezza then you can get the desired output using columns metadata view
create table L0 (
name varchar(20),
number varchar(10),
address varchar(50)
);
create table L1 (
name varchar(50),
number int,
address char(20)
);
Query :
SELECT 'select ' || system..group_concat('cast (' || a.column_name || ' as ' || b.data_type || ')') || ' from ' || a.table_name || ' minus select ' || system..group_concat(b.column_name) || ' from ' || b.table_name || ' ;'
FROM columns a
,columns b
WHERE a.column_name = b.column_name
AND a.table_name = 'L0'
AND b.table_name = 'L1'
AND a.table_catalog = 'TEST' --- this is the current database name
GROUP BY a.table_name
,b.table_name;
Output:
SELECT cast(ADDRESS AS CHARACTER(20))
,cast(NAME AS CHARACTER VARYING(50))
,cast(NUMBER AS INTEGER)
FROM L0 minus
SELECT ADDRESS
,NAME
,NUMBER
FROM L1;
EDIT:
Alternate approach using stored procedure
CREATE OR REPLACE PROCEDURE GRP_CONCAT(varchar(50),varchar(50))
RETURNS VARCHAR(ANY)
EXECUTE AS OWNER
LANGUAGE NZPLSQL AS
BEGIN_PROC
DECLARE
TABLE_NAME1 ALIAS FOR $1;
TABLE_NAME2 ALIAS for $2;
return_text varchar(10000) := 'SELECT';
x record;
BEGIN
FOR x IN select a.column_name as colname,b.data_type datatype
FROM columns a
,columns b
WHERE a.column_name = b.column_name
AND a.table_name = TABLE_NAME1
AND b.table_name = TABLE_NAME2
order by b.ordinal_position
LOOP
return_text := return_text || ' CAST ('||x.colname ||' as '||x.datatype||') , ';
end loop;
return_text := trim(return_text,' , ')||' FROM '||TABLE_NAME1 || ' minus select * from '||TABLE_NAME2||';';
return return_text;
EXCEPTION WHEN OTHERS THEN
RAISE NOTICE 'ERROR: %', SQLERRM;
RETURN 1;
END;
END_PROC;
Output :
Call GRP_CONCAT('L0','L1') ;
SELECT CAST(NAME AS CHARACTER VARYING(50))
,CAST(NUMBER AS INTEGER)
,CAST(ADDRESS AS CHARACTER(20))
FROM L0 minus
SELECT *
FROM L1;