( 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
Related
I am having 20 tables ( Each table has a PK and data ), i want to find out what is the current MAX(PK) Value for each table.
I Want the result as follows :
TABLE_NAME MAX_VAL
-------------------- ----------
TABELE_A 114
TABELE_B 55
TABELE_C 14
TABELE_D 866
TABELE_3 4552
is there any way to accomplish this or else i have to write 20 times SELECT MAX(PK_COL) FROM TABLE ?
Assuming your currently connected schema is composed of those twenty tables, and each have identical primary key column name(pk_col), then consider the following code block containing an implicit cursor :
declare
v_max pls_integer;
begin
dbms_output.put_line('table_name max_val');
for c in ( select * from user_tables )
loop
execute immediate 'select max(pk_col) from '||c.table_name into v_max;
dbms_output.put_line(c.table_name||' '||v_max);
end loop;
end;
/
i have found another method which will bring TABLE_NAME,PK_COLUMN and MAX( PK_COLUMN ).
SELECT CASE
WHEN RN = 1 THEN
FORMATTED_QUERY_SET
ELSE
FORMATTED_QUERY_SET || ' UNION ALL '
END AS FORMATTED_QUERY_SET
FROM (SELECT ' SELECT NVL(MAX( ' || COL.COLUMN_NAME ||
' ),0) CURR_MAX_VAL, ''' || TAB.TABLE_NAME ||
''' TABLE_NAME,''' || COL.COLUMN_NAME ||
''' COLUMN_NAME FROM ' || TAB.TABLE_NAME AS FORMATTED_QUERY_SET,
TAB.TABLE_NAME,
ROW_NUMBER() OVER(ORDER BY TAB.TABLE_NAME DESC) AS RN
FROM USER_CONSTRAINTS TAB
JOIN USER_CONS_COLUMNS COL
ON TAB.TABLE_NAME = COL.TABLE_NAME
JOIN USER_TAB_COLUMNS COL2
ON COL.COLUMN_NAME = COL2.COLUMN_NAME
AND COL.TABLE_NAME = COL2.TABLE_NAME
WHERE TAB.CONSTRAINT_TYPE = 'P'
AND COL.CONSTRAINT_NAME LIKE '%_PK'
AND REGEXP_LIKE(COL2.DATA_TYPE, ('NUMB|INTE')))
ORDER BY TABLE_NAME;
Copy the output returned by the above query and execute.
Note : Remove the last ' UNION ALL ' operator from the query string.
Note : Please correct me if i am doing anything wrong .
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 wonder how read column names in oracle. Ok, I know that there is table named USER_TAB_COLUMNS which gives info about it, but if I have 2 or 3 level nested query and I don't know column names. Or I just have simple query with join statement and i want to get column names. How to do that? any idey?
select * from person a
join person_details b where a.person_id = b.person_id
thanks
I would go for:
select 'select ' || LISTAGG(column_name , ',') within group (order by column_id) || ' from T1'
from user_tab_columns
where table_name = 'T1';
to get a query from database. To get columns with types to fill map you can use just:
select column_name , data_type
from user_tab_columns
where table_name = 'T1';
I assume you are looking for this:
DECLARE
sqlStr VARCHAR2(1000);
cur INTEGER;
columnCount INTEGER;
describeColumns DBMS_SQL.DESC_TAB2;
BEGIN
sqlStr := 'SELECT a.*, b.*, SYSDATE as "Customized column name"
FROM person a JOIN person_details b
WHERE a.person_id = b.person_id';
cur := DBMS_SQL.OPEN_CURSOR;
DBMS_SQL.PARSE(cur, sqlStr, DBMS_SQL.NATIVE);
DBMS_SQL.DESCRIBE_COLUMNS2(cur, columnCount, describeColumns);
FOR i IN 1..columnCount LOOP
DBMS_OUTPUT.PUT_LINE ( describeColumns(i).COL_NAME );
END LOOP;
DBMS_SQL.CLOSE_CURSOR(cur);
END;
I would like to check all columns of all tables for entries which are not contained in the table of valid dates.
In other words the elements of all columns with type date have to one of the entries of the table column CALENDAR.BD.
My problem is that executing the function by select checkAllDateColumns() from DUAL results only in
invalid table name, line 16
and I don't see why?
CREATE OR REPLACE FUNCTION checkAllDateColumns RETURN NUMBER IS
l_count NUMBER;
BEGIN
FOR t IN (SELECT table_name
FROM all_tables
WHERE owner = 'ASK_QUESTION')
LOOP
dbms_output.put_line ('Current table : ' || t.table_name);
FOR c IN (SELECT column_name
FROM all_tab_columns
WHERE TABLE_NAME = t.table_name AND data_type = 'DATE')
LOOP
execute immediate 'select count(*) from :1 where :2 not in (select BD from CALENDAR where is_business_day = 1)'
into l_count
using t.table_name, c.column_name;
IF (l_count > 0) THEN
RETURN 1;
END IF;
END LOOP;
END LOOP;
RETURN 0;
END checkAllDateColumns;
/
Btw: I am fine with the statement stopping on the first mismatch, currently I would like to figure out the dynamic sql...
You can't use a bind variable for the table or column names, only for column values. It's trying to interpret :1 as an identifier, it isn't using the value from the using clause. Your returning into clause should also just be into. This works:
execute immediate 'select count(*) from "' || t.table_name
|| '" where "' || c.column_name
|| '" not in (select BD from CALENDAR where is_business_day = 1)'
into l_count;
You might get a performance difference using a left-join approach, but it depends on your data:
execute immediate 'select count(*) from "' || t.table_name
|| '" t left join calendar c on c.bd = trunc(t."'
|| c.column_name || '") and c.is_business_day = 1 '
|| ' where c.bd is null'
into l_count;
I added a trunc() in case any of the fields might have times in them; you can do the same in the other version too of course.
And in both I've included double-quotes around the table and column names, just in case there are any quoted identifiers - without those there's a risk of getting an ORA-00904 'invalid identifier' error. But hopefully you don't have any to worry about anyway.
You also don't really need nested loops; you can either get the table name from all_tab_columns, or if you prefer, join all_tables and all_tab_columns in a single cursor. You should also be checking that the owner is the same in both tables, in case there are two versions of a table in different schemas.
In Oracle 10g, is there a way to do the following in PL/SQL?
for each table in database
for each row in table
for each column in row
if column is of type 'varchar2'
column = trim(column)
Thanks!
Of course, doing large-scale dynamic updates is potentially dangerous and time-consuming. But here's how you can generate the commands you want. This is for a single schema, and will just build the commands and output them. You could copy them into a script and review them before running. Or, you could change dbms_output.put_line( ... ) to EXECUTE IMMEDIATE ... to have this script execute all the statements as they are generated.
SET SERVEROUTPUT ON
BEGIN
FOR c IN
(SELECT t.table_name, c.column_name
FROM user_tables t, user_tab_columns c
WHERE c.table_name = t.table_name
AND data_type='VARCHAR2')
LOOP
dbms_output.put_line(
'UPDATE '||c.table_name||
' SET '||c.column_name||' = TRIM('||c.column_name||') WHERE '||
c.column_name||' <> TRIM('||c.column_name||') OR ('||
c.column_name||' IS NOT NULL AND TRIM('||c.column_name||') IS NULL)'
);
END LOOP;
END;
Presumably you want to do this for every column in a schema, not in the database. Trying to do this to the dictionary tables would be a bad idea...
declare
v_schema varchar2(30) := 'YOUR_SCHEMA_NAME';
cursor cur_tables (p_schema_name varchar2) is
select owner, table_name, column_name
from all_tables at,
inner join all_tab_columns atc
on at.owner = atc.owner
and at.table_name = atc.table_name
where atc.data_type = 'VARCHAR2'
and at.owner = p_schema;
begin
for r_table in cur_tables loop
execute immediate 'update ' || r.owner || '.' || r.table_name
|| ' set ' || r.column_name || ' = trim(' || r.column_name ||');';
end loop;
end;
This will only work for fields that are VARCHAR2s in the first place. If your database contains CHAR fields, then you're out of luck, because CHAR fields are always padded to their maximum length.