Is it possible to get overview of a CTE? - sql

In sql developer I can do
desc table
and get
describe table
Name Null Type
---------------------- ---- -------------
DATE_TIME DATE
KEY VARCHAR2(11)
Is something like this possible with a CTE? For example if there are 10 columns in table, I will get the description of all of them. But what if I want the description of only 2 columns.
with alias as (select col1, col2 from table) desc alias
This doesn't work.

You could apply a trick:
CREATE VIEW dummy AS
WITH ..
SELECT ..
And then:
DESC dummy

No, you can't do that.
A CTE is not stored anywhere in the system catalogs. It's essentially the same as a derived table:
with alias as (select col1, col2 from table)
select * from alias
is the same as
select *
from ( select col1, col2 from table) alias
And you can't describe that derived table either.
The only way you can do something remotely similar is to select from the CTE but with a where condition that selects not rows.
with alias as (select col1, col2 from table)
select * from alias
where 0 = 42;
Then you would at least see the column names and data types (the details of that depend on the SQL client you are using)

You can do this without creating a view, with something a bit more complicated:
create table yourTable(
DATE_TIME DATE,
KEY VARCHAR2(11)
);
You can use DBMS_SQL to describe the columns of a select query:
SQL> DECLARE
2 l_Cursor INTEGER DEFAULT dbms_sql.open_cursor;
3 l_Stmt VARCHAR2(4000);
4 l_colCnt NUMBER DEFAULT 0;
5 l_descTbl dbms_sql.desc_tab;
6 BEGIN
7 dbms_sql.parse(l_Cursor, 'SELECT key FROM yourTable', dbms_sql.native);
8 dbms_sql.describe_columns(l_Cursor, l_colCnt, l_descTbl);
9 --
10 FOR i IN 1..l_colCnt LOOP
11 dbms_output.put_line('Column '||l_descTbl(i).col_name || ' - type '||l_descTbl(i).col_type);
12 END LOOP;
13 --
14 dbms_sql.close_cursor(l_Cursor);
15 END;
16 /
Column KEY - type 1
PL/SQL procedure successfully completed.
The result must be interpreted by this table:
Datatype Number
VARCHAR2 1
NVARCHAR2 1
NUMBER 2
INTEGER 2
LONG 8
ROWID 11
DATE 12
RAW 23
LONG RAW 24
CHAR 96
NCHAR 96
MLSLABEL 106
So, it is saying that the column KEY has type 1, that is a VARCHAR2 .
The same way you can have more informations, about size for example.

Related

Oracle - Anonymous Procedure to loop through multiple tables (dynamically) - Query returning multiple rows

I need to fire the same query on multiple tables. Query might return zero, one or more number of rows.
I can loop through the tables using EXECUTE IMMEDIATE but for returning multiple rows I would need a datatype so I think I would need to keep it as CURSOR.
for ease, lets say I need to execute below query on 2 tables - table1 and table2
Table1 has following columns
datetime
device_name
value1
value2
Table2 has following columns
datetime
device_name
value3
value4
Query to be executed on both the tables as below:
select datetime, count(*) from table_name group by datetime;
Whats the best approach here?
please note that I can't create any DB objects (proc/function). Has to be anonymous block only.
As long as the cursor structures are the same, you can loop through with some dynamic ref cursors, eg
SQL> set serverout on
SQL> declare
2 tablist sys.odcivarchar2list :=
3 sys.odcivarchar2list('ALL_OBJECTS','USER_OBJECTS');
4 rc sys_refcursor;
5
6 date_results sys.odcidatelist := sys.odcidatelist();
7 count_results sys.odcinumberlist := sys.odcinumberlist();
8 begin
9 for i in 1 .. tablist.count
10 loop
11 open rc for
12 replace(q'{select trunc(created,'YYYY'), count(*) from ### group by trunc(created,'YYYY') order by 1}', '###',tablist(i));
13 fetch rc bulk collect into date_results, count_results;
14 close rc;
15
16 dbms_output.put_line(tablist(i));
17 for c in 1 .. date_results.count
18 loop
19 dbms_output.put_line(rpad(date_results(c),20)||lpad(count_results(c),20));
20 end loop;
21 end loop;
22 end;
23 /
ALL_OBJECTS
01-JAN-17 67892
01-JAN-18 6228
USER_OBJECTS
01-JAN-18 1093
PL/SQL procedure successfully completed.

Nested SELECT statement in FROM clause

I want to get data from table which name is keeping in another table. Trying to get this as described below leads to getting result from nested SELECT only
select * from (select value from ex_scheme.ex_tab where name = 'ex_name.current_table_name')
I mean, I've got equivalent result as from just
select value from ex_scheme.ex_tab where name = 'ex_name.current_table_name'
query.
UPDATED
Ok, lets double-check if I was correctly understood.
I have to see one table data (lets call this table "table1"). I need to know this table name. And I know where its name is keeping. It is in another table (call it "names_table") in column "name" (row with column value = 'table1'). And I can get it by query
select name from names_table where value = 'table1'
If you know in advance the column and its type, you can build some dynamic SQL to dynamically query a table or another.
For example, say you have tables like the following:
create table table1(col) as (select 1 from dual);
create table table2(col) as (select 2 from dual);
create table tab_of_tabs (tab_name) as (select 'TABLE1' from dual);
You can use dynamic SQL to build a query that scans a table whose name is the result of a query:
SQL> declare
2 vSQL varchar2(1000);
3 vResult number;
4 begin
5 select 'select sum(col) from ' || tab_name -- build the query
6 into vSQL
7 from tab_of_tabs;
8 --
9 execute immediate vSQL into vResult; -- run the query
10 --
11 dbms_output.put_line('Result: ' || vResult);
12 end;
13 /
Result: 1
PL/SQL procedure successfully completed.
SQL>
If I understand correctly, you could use a nested query in a where clause. For example,
select * from table1 where table1.name in (select name from table2);
This assumes there's a column "name" in table1. The result of this query should return the rows in table1 that are in table2.
try giving alias
select n.* from (select value from ex_scheme.ex_tab where name = 'ex_name.current_table_name') n;
Update:
It is in another table (call it "names_table") in column "name" (row
with column value = 'table1').
this query will work
select n.* from (select name from ex_scheme.ex_tab where name = 'ex_name.current_table_name') n;
sub query fetches name of table from another table .

How to call an indirect table in FROM

Please tell me what I am doing wrong. I have tried the following:
SELECT *
FROM 'A'||(TO_CHAR(SYSDATE, 'YYMMDD'))||'B'
SELECT *
FROM CAST('A'||(TO_CHAR(SYSDATE, 'YYMMDD'))||'B' AS varchar2(8))
SELECT *
FROM table( CAST('A'||(TO_CHAR(SYSDATE, 'YYMMDD'))||'B' AS varchar2(8)))
DEFINE tName = CAST('A'||(TO_CHAR(SYSDATE, 'YYMMDD'))||'B' AS varchar2(8))
SELECT *
FROM &tName
SET tName AS CAST('A'||(TO_CHAR(SYSDATE, 'YYMMDD'))||'B' AS varchar2(8))
SELECT *
FROM &tName
SET #tName AS CAST('A'||(TO_CHAR(SYSDATE, 'YYMMDD'))||'B' AS varchar2(8))
SELECT *
FROM #tName
SELECT *
FROM
(
SELECT CAST('A'||(TO_CHAR(SYSDATE, 'YYMMDD'))||'B' AS varchar2(8))
FROM DUAL
)
(Note: I have also tried adding the owner before the table in all of the examples above, as shown in the second example below)
I have verified that
SELECT CAST('A'||(TO_CHAR(SYSDATE, 'YYMMDD'))||'B' AS varchar2(8))
FROM DUAL
displays A170429B
SELECT CAST('owner.A'||(TO_CHAR(SYSDATE, 'YYMMDD'))||'B' AS varchar2(8))
FROM DUAL
displays owner.A170429B
but I can't get the FROM statement to see it as a Table
The database creates a new table every day; which, is why I am trying to do this.
As others have observed, partitioning is the elegant way to arrange this. But Partitioning remains a licensed extra on the Enterprise Edition,and that's expensive. So here's a couple of cheaper options.
The first option is to do what you are trying to do now, and query each table by name. For this approach to work you need to use dynamic SQL.
Here are some tables - t170428, t170429, t170430 - which all look like this
create table t170428 (
id number not null primary key
, type varchar2(10)
, col1 varchar2(10)
, col2 number
, col3 date not null
)
/
To query them we need a SQL type with a signature that matches the tables' projection:
create or replace type tyymmdd_t as object (
id number
, type varchar2(10)
, col1 varchar2(10)
, col2 number
, col3 date
);
/
create or replace type tyymmdd_nt as table of tyymmdd_t
/
Here is a function which dynamic builds a table name from a passed date and returns a nested table of rows from that table:
create or replace function get_date_table
( p_target_date in date)
return tyymmdd_nt
is
return_value tyymmdd_nt;
begin
execute immediate
' select tyymmdd_t(id, type, col1, col2, col3) from t'
||to_char(p_target_date, 'yymmdd')
bulk collect into return_value;
return return_value;
end;
/
To query a table we use the table() function like this:
SQL> select * from table(get_date_table(sysdate));
ID TYPE COL1 COL2 COL3
---------- ---------- ---------- ---------- ---------
9 D2 SUN 1 30-APR-17
10 D2 SUN 2 30-APR-17
SQL> select * from table(get_date_table(date'2017-04-28'));
ID TYPE COL1 COL2 COL3
---------- ---------- ---------- ---------- ---------
1 D1 FRI 1 28-APR-17
2 D1 FRI 2 28-APR-17
3 D1 FRI 3 28-APR-17
4 D1 FRI 4 28-APR-17
5 D1 FRI 5 28-APR-17
SQL> select * from table(get_date_table(sysdate+1));
select * from table(get_date_table(sysdate+1))
*
ERROR at line 1:
ORA-00942: table or view does not exist
ORA-06512: at "FOX.GET_DATE_TABLE", line 7
SQL>
The second option is Partition Views. This is an old technique (from the previous millennium!) which allows us to build a view over multiple tables using the UNION ALL operator and get many benefits of Partitioning such as partition pruning - provided you're using a really old version of Oracle. Partition Views were deprecated in 8.0 and Oracle stopped supporting them in 9iR2. The documentation dates back to Oracle7 Find out more.
Anyway, the principle of partitioned views is this:
Enforce check constraints on the "partition key"
Build indexes on the partition key columns
Gather stats
Build the view
Like the table projection, the constraints and indexes must be the same for all tables.
alter table t170428 add constraint t170428_ptn_key_ck check (col3 = date '2017-04-28');
alter table t170429 add constraint t170429_ptn_key_ck check (col3 = date '2017-04-29');
alter table t170430 add constraint t170430_ptn_key_ck check (col3 = date '2017-04-30');
create unique index t170428_ptn_idx on t170428(col3, id) compress 1;
create unique index t170429_ptn_idx on t170429(col3, id) compress 1;
create unique index t170430_ptn_idx on t170430(col3, id) compress 1;
exec dbms_stats.gather_table_stats('FOX', 'T170428', cascade=>true)
exec dbms_stats.gather_table_stats('FOX', 'T170429', cascade=>true)
exec dbms_stats.gather_table_stats('FOX', 'T170430', cascade=>true)
create or replace view v_all_the_dates as
select * from t170428
union all
select * from t170429
union all
select * from t170430
/
Because Oracle don't support Partition Views in later versions of the database this approach won't give you partition pruning. But it could still be quite efficient providing you are rigorous about the indexing and check constraints.
A third option which might fit is an external table. The creation of dated tables suggests a daily load. If these just act as staging tables for data which arrive as files you could use an external table to access the data. The table would be a stable structure; all you would need to change is the location of the daily feed file. Find out more.
DBMS_XMLGEN can create a completely dynamic SQL statement without any additional privileges.
(But before you use the difficult SQL below, I recommend you try again to get more access to the database and use one of APC's solutions. Write access to the vendor's application schema is not required. The objects can be created in a separate user schema that will not interfere with the application.)
First, create a test table with today's date in the name
begin
execute immediate '
create table a'||to_char(sysdate, 'YYMMDD')||'b
(
column1 number,
column2 number
)';
end;
/
Add a test row. One potential problem with this solution is that when the inner query returns no rows it throw an error ORA-06502: PL/SQL: numeric or value error/nORA-06512: at "SYS.XMLTYPE", line 272/nORA-06512: at line 1.
begin
execute immediate 'insert into a'||to_char(sysdate, 'YYMMDD')||'b values (1,2)';
commit;
end;
/
This query reads from the table without knowing the table's name. It does however require knowing the table's columns. (If the vendor creates different columns in each table that's a more difficult problem that will definitely require custom objects.)
--Step 3: Convert XML back into a row.
select column1, column2
from
(
--Step 2: Create an XML result set of a dynamic query.
select
xmltype(dbms_xmlgen.getxml(
(
--Step 1: SELECT statement that generates a SELECT statement.
select
'select column1, column2
from a'||to_char(sysdate, 'YYMMDD')||'b'
from dual
)
)) xml_results
from dual
)
cross join
xmltable
(
'/ROWSET/ROW'
passing xml_results
columns
column1 number path 'COLUMN1',
column2 number path 'COLUMN2'
);
Results:
COLUMN1 COLUMN2
------- -------
1 2

Oracle SQL Find Highest ID Across All Tables

Every one of my tables has an "id" field. I need to be able to find the highest ID across all of them, given that the list of tables might change.
Is there any way to get the list of tables in an oracle database, aggregate their rows (only ids), and then get the max()?
P.S. This is for updating a sequence which has gone out of whack.
Here is some simple dynamic SQL driving off the data dictionary:
SQL> set serveroutput on
SQL> declare
2 l_id pls_integer;
3 max_id pls_integer;
4 max_tab_name varchar2(30);
5 begin
6 max_id := 0;
7 for r in ( select table_name
8 from user_tab_columns
9 where column_name = 'ID' )
10 loop
11 execute immediate 'select max(id) from '||r.table_name
12 into l_id;
13 if l_id > max_id
14 then
15 max_id := l_id;
16 max_tab_name := r.table_name;
17 end if;
18 end loop;
19 dbms_output.put_line('Highest score = '||max_id||' table='||max_tab_name);
20 end;
21 /
Highest score = 2010070705 table=SESSIONS
PL/SQL procedure successfully completed.
SQL>
If the sequence services tables across several schemas, you will need to drive off ALL_TAB_COLUMNS and include OWNER in the query.
How about this?
SELECT MAX(ID)
FROM
(
SELECT MAX(ID) AS ID FROM CUSTOMER
UNION ALL
SELECT MAX(ID) AS ID FROM EMPLOYEE
UNION ALL
SELECT MAX(ID) AS ID FROM MANAGER
);
Repeat the UNION ALL for all tables that you need to search from.
how about querying the sequence that drives the id's for CURRVAL...
if you need to find out also what table that id is in, then construct a new table to track the id centrally and add triggers to populate on insert.

Getting weird issue with TO_NUMBER function in Oracle

I have been getting an intermittent issue when executing to_number function in the where clause on a varchar2 column if number of records exceed a certain number n. I used n as there is no exact number of records on which it happens. On one DB it happens after n was 1 million on another when it was 0.1. million.
E.g. I have a table with 10 million records say Table Country which has field1 varchar2 containing numberic data and Id
If I do a query as an example
select *
from country
where to_number(field1) = 23
and id >1 and id < 100000
This works
But if I do the query
select *
from country
where to_number(field1) = 23
and id >1 and id < 100001
It fails saying invalid number
Next I try the query
select *
from country
where to_number(field1) = 23
and id >2 and id < 100001
It works again
As I only got invalid number it was confusing, but in the log file it said
Memory Notification: Library Cache Object loaded into SGA
Heap size 3823K exceeds notification threshold (2048K)
KGL object name :with sqlplan as (
select c006 object_owner, c007 object_type,c008 object_name
from htmldb_collections
where COLLECTION_NAME='HTMLDB_QUERY_PLAN'
and c007 in ('TABLE','INDEX','MATERIALIZED VIEW','INDEX (UNIQUE)')),
ws_schemas as(
select schema
from wwv_flow_company_schemas
where security_group_id = :flow_security_group_id),
t as(
select s.object_owner table_owner,s.object_name table_name,
d.OBJECT_ID
from sqlplan s,sys.dba_objects d
It seems its related to SGA size, but google did not give me much help on this.
Does anyone have any idea about this issue with TO_NUMBER or oracle functions for large data?
which has field1 varchar2 containing
numberic data
This is not good practice. Numeric data should be kept in NUMBER columns. The reason is simple: if we don't enforce a strong data type we might find ourselves with non-numeric data in our varchar2 column. If that were to happen then a filter like this
where to_number(field1) = 23
would fail with ORA-01722: invalid number.
I can't for certain sure say this is what is happening in your scenario, because I don't understand why apparently insignificant changes in the filters of ID have changed the success of the query. It would be instructive to see the execution plans for the different versions of the queries. But I think it is more likely to be a problem with your data than a bug in the SGA.
Assuming you know that the given range of ids will always result in field1 containing numeric data, you could do this instead:
select *
from (
select /*+NO_MERGE*/ *
from country
where id >1 and id < 100000
)
where to_number(field1) = 23;
Suggest doing the following to determine for sure whether there are records containing non-numeric data. As others have said, variations in the execution plan and order of evaluation could explain why the error does not appear consistently.
(assuming SQLPlus as the client)
SET SERVEROUTPUT ON
DECLARE
x NUMBER;
BEGIN
FOR rec IN (SELECT id, field1 FROM country) LOOP
BEGIN
x := TO_NUMBER( rec.field1 );
EXCEPTION
WHEN OTHERS THEN
dbms_output.put_line( rec.id || ' ' || rec.field1 );
END;
END LOOP;
END;
/
An alternative workaround to your original issue would be to rewrite the query to avoid implicit type conversion, e.g.
SELECT id, TO_NUMBER( field1 )
FROM county
WHERE field1 = '23'
AND <whatever condition on id you want, if any>
Consider writing an IS_NUMBER PL/SQL function:
CREATE OR REPLACE FUNCTION IS_NUMBER (p_input IN VARCHAR2) RETURN NUMBER
AS
BEGIN
RETURN TO_NUMBER (p_input);
EXCEPTION
WHEN OTHERS THEN RETURN NULL;
END IS_NUMBER;
/
SQL> SELECT COUNT(*) FROM DUAL WHERE IS_NUMBER ('TEST') IS NOT NULL;
COUNT(*)
----------
0
SQL> SELECT COUNT(*) FROM DUAL WHERE IS_NUMBER ('123.45') IS NOT NULL;
COUNT(*)
----------
1