How can I replace CHAR with VARCHAR2 in all tables in a schema?
Note: I'm content with a query that returns the ALTER TABLE statements so I can save the script and run it again.
select 'ALTER TABLE "' || owner || '"."' || table_name
|| '" MODIFY ("' || column_name
|| '" VARCHAR2(' || data_length || '));'
from all_tab_columns tc
where data_type = 'CHAR'
and owner = :schemaname
and exists (
select 1
from all_tables t
where tc.owner = t.owner
and tc.table_name = t.table_name
);
Related
( I think it could be a simple question for the most users here ..)
Short description:
I need a way (maybe with PL/SQL which I don't know ..) to "select defined data from all tables which contain this type of data"
Long description (example):
I have a different number of different tables. An often changing part of them - I don't know the number and the names - contains the column "FID". Now I need two steps:
a) Select all tables which contain the column "FID". ( I know how to do this as single step)
b) Select from all found tables the value FID and show it.
For me the problem is the step from a) to b). With known tables I would use UNION, but with a dynamic result of tables I have no idea ..
You could use a variation on an XML magic trick, by using dbms_xmlgen to get all the values into XML documents based on a query against user_tab_columns:
select dbms_xmlgen.getxmltype(
'select "' || column_name || '" from "' || table_name || '"')
from user_tab_columns
where upper(column_name) = 'FID'
and data_type = 'NUMBER';
... where I'm assuming FID is expected to be a numeric ID, so limiting only to numeric columns (and also allowing for mixed case/quoted identifiers for table and columns names, just in case). That gives one row per table, with an XML document listing the FID values in that table.
Then from that XML you can extract the individual values, again as numbers:
with cte (xml) as (
select dbms_xmlgen.getxmltype(
'select "' || column_name || '" as fid from "' || table_name || '"')
from user_tab_columns
where upper(column_name) = 'FID'
and data_type = 'NUMBER'
)
select x.fid
from cte
cross apply xmltable(
'/ROWSET/ROW'
passing cte.xml
columns fid number path 'FID'
) x;
Or if you want to see the table/column each value came from, just include those in the CTE and select list:
with cte (table_name, column_name, xml) as (
select table_name, column_name, dbms_xmlgen.getxmltype(
'select "' || column_name || '" as fid from "' || table_name || '"')
from user_tab_columns
where upper(column_name) = 'FID'
and data_type = 'NUMBER'
)
select cte.table_name, cte.column_name, x.fid
from cte
cross apply xmltable(
'/ROWSET/ROW'
passing cte.xml
columns fid number path 'FID'
) x;
If you want to search other schemas, then use all_tab_columns instead, and optionally include each table's owner:
with cte (owner, table_name, column_name, xml) as (
select owner, table_name, column_name, dbms_xmlgen.getxmltype(
'select "' || column_name || '" as fid from "' || owner || '"."' || table_name || '"')
from all_tab_columns
where upper(column_name) = 'FID'
and data_type = 'NUMBER'
)
select cte.owner, cte.table_name, cte.column_name, x.fid
from cte
cross apply xmltable(
'/ROWSET/ROW'
passing cte.xml
columns fid number path 'FID'
) x;
db<>fiddle
The basis for this trick goes back to at least 2007 but may be even older, from before getxmltype() existed (it seems to have been added in 10g); I'd originally used xmltype(getxml()):
select xmltype(dbms_xmlgen.getxml(
'select "' || column_name || '" from "' || table_name || '"'))
from user_tab_columns
where upper(column_name) = 'FID'
and data_type = 'NUMBER';
which works most of the time, but if any of the tables are empty throws "ORA-06502: PL/SQL: numeric or value error".
If you want to use pl/sql I really love pipelined functions:
create type result_type as Object ( text varchar2(2000) );
create type result_type_table as table of result_type;
create or replace function select_all( p_column_name in varchar2 )
return result_type_table
deterministic
pipelined
as
v_table_name varchar2(40);
v_result result_type := result_type('');
v_table_name_cursor sys_refcursor;
v_inner_cursor sys_refcursor;
begin
open v_table_name_cursor
for 'select a.table_name
from user_tab_cols a
, user_tables b
where a.column_name = :1
and a.table_name = b.table_name'
using upper(p_column_name);
loop
fetch v_table_name_cursor into v_table_name;
exit when v_table_name_cursor%notfound;
open v_inner_cursor
for 'select '||p_column_name||' from '||v_table_name;
loop
fetch v_inner_cursor into v_result.text;
exit when v_inner_cursor%notfound;
pipe row (v_result );
end loop;
close v_inner_cursor;
end loop;
close v_table_name_cursor;
end;
/
Using this function is simple:
select * from table( select_all('your_column_name') );
db<>fiddle
The column HIGH_VALUE has data similar to below, of type LONG, and always has the same length:
TIMESTAMP' 2019-01-30 00:00:00'
How can I convert it to a DATE type without using a function?
My overall goal is to create a result set which can then be used as an inner query for other aggregations. For example, I would like to be able to sum the number of rows over a year according to the date produced by converting the HIGH_VALUE column to a date.
I have only read permissions on this database and therefore cannot create functions. I've seen other solutions on StackOverflow and other sites, but they all require creating a function.
ALL_TAB_PARTITIONS is a standard built-in Oracle table and therefore I'm not including the table structure. In case that's an issue, please let me know and I will create an example table.
An example query and the data one row that results from this query follows. Note that I cannot create tables on this database so I will also need an a method that works without creating a temporary table.
Insert into EXPORT_TABLE (TABLE_OWNER,TABLE_NAME,PARTITION_NAME,HIGH_VALUE,NUM_ROWS)
VALUES ('TO','TN','SYS_P201709','TIMESTAMP'' 2019-01-30 00:00:00''',5053133);
SELECT TABLE_OWNER, TABLE_NAME, PARTITION_NAME, HIGH_VALUE, NUM_ROWS
from ALL_TAB_PARTITIONS;
If you are using Oracle 12c you could still use function but defined inline:
WITH FUNCTION with_function(p_id IN NUMBER) RETURN NUMBER IS
BEGIN
-- logic here
RETURN p_id;
END;
SELECT with_function(id)
FROM your_table
Related: WITH Clause Enhancements in Oracle Database 12c Release 1 (12.1)
For the conversion of a LONG type ( HIGH_VALUE ) to TIMESTAMP,
One option is to use dynamic sql and perform your insert through an anonymous block. No Procedure or function is required.
DECLARE
tstamp TIMESTAMP;
BEGIN
FOR rec IN ( SELECT table_owner,table_name,partition_name,high_value,num_rows
FROM all_tab_partitions
WHERE ROWNUM < 5
) LOOP
EXECUTE IMMEDIATE 'BEGIN :dt := '
|| rec.high_value
|| '; END;'
USING OUT tstamp; --assign the long to an external timestamp variable
INSERT INTO export_table (
table_owner,table_name,partition_name,high_value,num_rows
) VALUES (rec.table_owner,
rec.table_name, rec.partition_name, tstamp, rec.num_rows
);
END LOOP;
END;
/
AS #APC commented, There's also a solution using Pure SQL, which
uses a slightly complex Xml expression.
Combining the pure SQL solution from APC's comment with the enhancements in Oracle 12 to allow functions to be declared in WITH clauses and Kaushik Nayak's method of using EXECUTE IMMEDIATE to convert the string value to a date then you can get this:
Oracle Setup - Test Table & Data:
CREATE TABLE EXPORT_TABLE (
TABLE_OWNER VARCHAR2(30),
TABLE_NAME VARCHAR2(30),
PARTITION_NAME VARCHAR2(30),
HIGH_VALUE LONG,
NUM_ROWS INTEGER
);
INSERT INTO EXPORT_TABLE VALUES ( 'TO', 'TN', 'PN', 'TIMESTAMP ''2019-06-26 12:34:56''', 12345 );
Query:
WITH FUNCTION clobToDate( value IN CLOB ) RETURN DATE
IS
ts DATE;
BEGIN
EXECUTE IMMEDIATE 'BEGIN :ts := ' || value || '; END;' USING OUT ts;
RETURN ts;
END;
SELECT TABLE_OWNER,
TABLE_NAME,
PARTITION_NAME,
clobToDate(
EXTRACTVALUE(
dbms_xmlgen.getxmltype(
'SELECT high_value'
|| ' FROM EXPORT_TABLE'
|| ' WHERE TABLE_OWNER = ''' || t.table_owner || ''''
|| ' AND TABLE_NAME = ''' || t.table_name || ''''
|| ' AND PARTITION_NAME = ''' || t.partition_name || ''''
),
'//text()'
)
) AS HIGH_VALUE,
NUM_ROWS
FROM EXPORT_TABLE t;
Output:
TABLE_OWNER | TABLE_NAME | PARTITION_NAME | HIGH_VALUE | NUM_ROWS
:---------- | :--------- | :------------- | :------------------ | -------:
TO | TN | PN | 2019-06-26 12:34:56 | 12345
db<>fiddle here
Update: If you want to aggregate some columns then:
WITH FUNCTION clobToDate( value IN CLOB ) RETURN DATE
IS
ts DATE;
BEGIN
EXECUTE IMMEDIATE 'BEGIN :ts := ' || value || '; END;' USING OUT ts;
RETURN ts;
END;
SELECT table_owner,
table_name,
MAX( high_value ) AS max_high_value,
SUM( num_rows ) AS total_rows
FROM (
SELECT TABLE_OWNER,
TABLE_NAME,
PARTITION_NAME,
clobToDate(
EXTRACTVALUE(
dbms_xmlgen.getxmltype(
'SELECT high_value'
|| ' FROM EXPORT_TABLE'
|| ' WHERE TABLE_OWNER = ''' || t.table_owner || ''''
|| ' AND TABLE_NAME = ''' || t.table_name || ''''
|| ' AND PARTITION_NAME = ''' || t.partition_name || ''''
),
'//text()'
)
) AS HIGH_VALUE,
NUM_ROWS
FROM EXPORT_TABLE t
)
GROUP BY table_owner, table_name;
db<>fiddle here
I have a oracle script to convert table structure from Oracle to Redshift kind of below,
WITH TABLE_SCRIPT AS (
SELECT
TABLE_NAME,
COLUMN_NAME,
CASE
WHEN DATA_TYPE= 'DATE' THEN 'DATE'
WHEN DATA_TYPE= 'DATETIME' THEN 'TIMESTAMP'
WHEN DATA_TYPE LIKE 'TIMESTAMP%' THEN 'TIMESTAMP'
WHEN DATA_TYPE= 'LONG' THEN 'TEXT'
WHEN DATA_TYPE= 'NCHAR' THEN 'NCHAR(' || DATA_LENGTH || ')'
WHEN DATA_TYPE= 'NVARCHAR' THEN 'NVARCHAR(' || DATA_LENGTH || ')'
FROM ALL_TAB_COLUMNS
WHERE
OWNER= 'SMART_DS'
AND TABLE_NAME = 'BATCH'
ORDER BY DATA_TYPE,COLUMN_ID
)
SELECT 'CREATE TABLE '||MAX(TABLE_NAME) ||' (' as text FROM TABLE_SCRIPT
UNION ALL
SELECT ' '||COLUMN_NAME||' '||REDSHIFT_COLUMN_DEFINITION || ', ' AS TEXT FROM TABLE_SCRIPT
UNION ALL
SELECT ' );' AS TEXT FROM dual
When I run this script it has to run perfectly. My problem is ' , ' should not come at the end of second row, how to change that?
CREATE TABLE VERSION
(
RELEASE_ID DOUBLE PRECISION NOT NULL ,
VERSION_ID DOUBLE PRECISION NOT NULL ,
)
N.B. you missed out the end of your case statement in your table_script subquery, so I had to guess what it should be.
You can do this by using listagg, which removes the need to have separate union all'd select statements:
WITH table_script AS
(SELECT owner,
table_name,
column_name,
data_type,
CASE
WHEN data_type = 'DATE' THEN 'DATE'
WHEN data_type = 'DATETIME' THEN 'TIMESTAMP'
WHEN data_type LIKE 'TIMESTAMP%' THEN 'TIMESTAMP'
WHEN data_type = 'LONG' THEN 'TEXT'
WHEN data_type = 'NCHAR' THEN 'NCHAR(' || data_length || ')'
WHEN data_type = 'NVARCHAR' THEN 'NVARCHAR(' || data_length || ')'
ELSE
data_type
END redshift_column_definition,
column_id
FROM all_tab_columns
WHERE owner = 'SMART_DS'
AND table_name = 'BATCH')
SELECT 'create table ' || owner || '.' || table_name || ' (' || chr(10) || listagg(column_name || ' ' || redshift_column_definition, ',' || chr(10)) within GROUP(ORDER BY column_id) || chr(10) || ' );' AS text
FROM table_script
GROUP BY owner,
table_name
ORDER BY owner,
table_name;
Example output:
create table SYS.ALL_IDENTIFIERS (
OWNER VARCHAR2,
NAME VARCHAR2,
SIGNATURE VARCHAR2,
TYPE VARCHAR2,
OBJECT_NAME VARCHAR2,
OBJECT_TYPE VARCHAR2,
USAGE VARCHAR2,
USAGE_ID NUMBER,
LINE NUMBER,
COL NUMBER,
USAGE_CONTEXT_ID NUMBER
);
You'll note that I have added owner into the mix; this means you can create all the create table scripts in one go.
If you won't to use ListAgg. I Can suggest you to this query below, I tried it and it worked fine:
WITH
TABLE_SCRIPT
AS
( SELECT TABLE_NAME,
COLUMN_NAME,
(CASE
WHEN DATA_TYPE = 'DATE'
THEN
'DATE'
WHEN DATA_TYPE = 'DATETIME'
THEN
'TIMESTAMP'
WHEN DATA_TYPE LIKE 'TIMESTAMP%'
THEN
'TIMESTAMP'
WHEN DATA_TYPE = 'LONG'
THEN
'TEXT'
WHEN DATA_TYPE = 'NCHAR'
THEN
'NCHAR(' || DATA_LENGTH || ')'
WHEN DATA_TYPE = 'NVARCHAR'
THEN
'NVARCHAR(' || DATA_LENGTH || ')'
ELSE
DATA_TYPE
END) REDSHIFT_COLUMN_DEFINITION
,
ROWNUM
RankOfCol
FROM ALL_TAB_COLUMNS
WHERE OWNER = 'SMART_DS' AND TABLE_NAME = 'BATCH'
ORDER BY rankofcol, DATA_TYPE, COLUMN_ID)
(SELECT 'CREATE TABLE ' || MAX (TABLE_NAME) || ' (' AS TEXT
FROM TABLE_SCRIPT
UNION ALL
SELECT ' ' || COLUMN_NAME ||' '||REDSHIFT_COLUMN_DEFINITION || ' ' AS TEXT FROM TABLE_SCRIPT WHERE rankofcol = 1
UNION ALL
SELECT ', ' || COLUMN_NAME || ' '||REDSHIFT_COLUMN_DEFINITION ||' ' AS TEXT FROM TABLE_SCRIPT WHERE rankofcol > 1
UNION ALL
SELECT ' );' AS TEXT FROM DUAL);
Hope this can help you.
I would like to create a function who uppercase all columns name, in a oracle db.
I don't use oracle everyday, so I need help.
I would like this, but for Oracle :
CREATE OR REPLACE FUNCTION uppercase_fields(schemaname text) RETURNS void AS $$
DECLARE
r RECORD;
full_table text;
geom_type_count integer;
BEGIN
FOR r IN
EXECUTE 'SELECT table_name, table_schema, column_name FROM information_schema.columns WHERE table_schema = $1 AND column_name <> upper(column_name)'
USING schemaname
LOOP
EXECUTE 'ALTER TABLE "' || r.table_schema || '"."' || r.table_name || '" RENAME "' || r.column_name || '" to "' || upper(r.column_name) || '"';
END LOOP;
END;
$$ LANGUAGE plpgsql;
Thanks
Oracle, as the default functionality, will covert all unquoted table/column identifiers to upper case - so you do not need to use the UPPER function; just leave the identifiers unquoted.
To find the data you require you want the ALL_TAB_COLUMNS or USER_TAB_COLUMNS tables from the data dictionary:
BEGIN
FOR r IN ( SELECT owner, table_name, column_name
FROM ALL_TAB_COLUMNS
WHERE owner IN ( 'your', 'list' , 'of', 'tablespaces' )
AND column_name <> UPPER( column_name )
)
LOOP
EXECUTE 'ALTER TABLE "' || r.owner || '"."' || r.table_name
|| '" RENAME COLUMN "' || r.column_name || '" TO ' || r.column_name;
END LOOP;
END;
/
If your column names are reserved words or otherwise cannot be in an unquoted identifier then you can use:
BEGIN
FOR r IN ( SELECT owner, table_name, column_name
FROM ALL_TAB_COLUMNS
WHERE owner IN ( 'your', 'list' , 'of', 'tablespaces' )
AND column_name <> UPPER( column_name )
)
LOOP
EXECUTE 'ALTER TABLE "' || r.owner || '"."' || r.table_name
|| '" RENAME COLUMN "' || r.column_name || '" TO "' || UPPER(r.column_name) || '"';
END LOOP;
END;
/
For change to uppercase all columns name in Oracle, use this solution:
DECLARE
TARGET_TABLE_NAME VARCHAR2(31) := 'ENTER_YOUR_TARGET_TABLE_NAME_HERE';
BEGIN
FOR I IN (
SELECT
COLUMN_NAME
FROM
ALL_TAB_COLUMNS
WHERE
TABLE_NAME = TARGET_TABLE_NAME
AND
COLUMN_NAME <> UPPER(COLUMN_NAME)
)
LOOP
EXECUTE IMMEDIATE 'ALTER TABLE ' ||
TARGET_TABLE_NAME ||
' RENAME COLUMN "' ||
I.COLUMN_NAME ||
'" TO ' ||
UPPER(I.COLUMN_NAME);
END LOOP;
END;
/
Just replace the name of your target table by ENTER_YOUR_TARGET_TABLE_NAME_HERE
I have productionDB and testDB.ProductionDB have synonym for tables.And now i want to create synonym for all tables for testDB.How i can create synonym all of them. I can get list synonym this query
select *
from all_synonyms s
join all_objects o
on s.table_owner = o.owner
and s.table_name = o.object_name
where s.table_owner = 'XYZ'
Try the following query:
SELECT CAST(dbms_metadata.get_ddl(object_type => 'SYNONYM',
name => a.synonym_name,
SCHEMA => a.owner) AS VARCHAR2(4000))
FROM ALL_SYNONYMS a;
Share and enjoy.
This is a starter script - it writes sql as output. Run it, READ the output first. Uncomment the last line when you think it is correct - it has to be run with privilege.
set pages 0
set feed off
set linesize 180
set trimspool on
spool syn.sql
select 'CREATE PUBLIC SYNONYM ' || SYNONYM_NAME | ' FOR ' ||
TABLE_OWNER || '.' || TABLE_NAME || ';'
from ALL_SYNONYMS
where DB_LINK is NULL;
select 'CREATE PUBLIC SYNONYM ' || SYNONYM_NAME | ' FOR ' ||
TABLE_OWNER || '.' || TABLE_NAME || '#' || DB_LINK || ';'
from ALL_SYNONYMS
where DB_LINK is NOT NULL;
spool off
-- uncomment after it has been tested
--#syn.sql
If testDB was created using a PROD export then why are the synonyms all missing? Something is very wrong here. OR I am missing something.