Oracle Object : How to show all the fields in select query? - sql

I have the following Oracle Object :
CREATE TYPE person_typ AS OBJECT (
Id NUMBER,
first_name VARCHAR2(20),
last_name VARCHAR2(25));
And a table :
CREATE TABLE MyTable (
contact person_typ,
contact_date DATE );
I would like to make a select query to show all the fields of the Person_Typ object without specifiying their names.
When I do
select * from MyTable
The column contact is shown as [unsupported data type], I have to use instead :
select T.contact.ID, T.contact.First_name, T.contact.last_name from MyTable T
Is there another way to show the values of the object without specifying the column names ?
Thanks,
Cheers,

I don't use SQL Developer, but according to this article Showing TYPE’d Column Values in SQL Developer you could use option:
Preferences / Database / Advanced / Display Struct Value in Grid
Also you can query user_type_attr (or all_type_attr) to obtain column names. Then copy/paste select part from output and run it or create view as proposed by #sep. Here is my test data and code block:
insert into mytable values (person_typ(1, 'Paulina', 'Thomson'), date '2017-12-17');
insert into mytable values (person_typ(7, 'Keanu', 'Stevens'), date '2017-12-28');
declare
v_sql varchar2(32000);
begin
select listagg('T.CONTACT.'||attr_name||' '||attr_name, ', ')
within group (order by attr_no)
into v_sql
from user_type_attrs
where type_name = 'PERSON_TYP';
v_sql := 'SELECT '||v_sql||' FROM MYTABLE T';
dbms_output.put_line(v_sql);
execute immediate 'CREATE OR REPLACE VIEW VW_CONTACTS AS '||v_sql;
end;
select * from vw_contacts;
Result:
ID FIRST_NAME LAST_NAME
------ -------------------- -------------------------
1 Paulina Thomson
7 Keanu Stevens

I had the same problem and I created a view with all the fields. In your case:
CREATE VIEW v_myTable AS
select T.contact.ID, T.contact.First_name, T.contact.last_name
from MyTable T
and use the view instead of the table
SELECT * from v_myTable

Related

Convert a single row with dynamic number of columns into a single column in Oracle

I have to convert a single row that is obtained via select statement into a single column with concatenated values of the individual columns of the result. The problem is that the columns are unknown and can vary in number.
Let's say the table looks similar to this:
Table USER
Name Surname Age Logindate City
Max Smith 25 20.05.20 NY
I need to SELECT * FROM USER and convert the result into a single string like Max, Smith, 25, 20.05.20, NY or with column names Name: Max, Surname: Smith, Age: 25, Logindate: 20.05.20, City: NY that I can afterwards insert into a column of other table. The name of the table that I'm selecting from is known and hardcoded into the SELECT statement that is executed inside a stored procedure.
Since the number of columns and column names are unknown, I cannot use a CONCAT function. I was also going to be satisfied with the output format of SELECT JSON_OBJECT(*) FROM USER, but the function with such usage of star operator is not supported in Oracle18c (it is in Oracle19c).
The transformation of column values of a single row into a single string seems like a basic operation, but I wasn't able to find any simple solution.
Use the data dictionary to generate the right SQL statement and then use dynamic SQL to execute it.
--Sample tables for input and output:
create table user_table as
select 'Max' Name, 'Smith' Surname, 25 Age, date '2020-05-20' LoginDate, 'NY' City
from dual;
create table concatenated_values(value varchar2(4000));
--Procedure to read all columns from USER_TABLE and write them to CONCATENATED_VALUES.
create or replace procedure concatenate_values(p_table_name varchar2) is
v_sql varchar2(4000);
begin
--Generate a SQL statement to concatenate all the values.
select
'select ' ||
listagg(column_name, '||'',''||') within group (order by column_id) ||
' from ' || owner || '.' || table_name
into v_sql
from all_tab_columns
where owner = user
and table_name = p_table_name
group by owner, table_name;
--Run the SQL statement and insert the value.
execute immediate 'insert into concatenated_values ' || v_sql;
end;
/
--Call the procedure.
begin
concatenate_values('USER_TABLE');
end;
/
--Results:
select * from concatenated_values;
VALUE
-------------------------
Max,Smith,25,20-MAY-20,NY

Is it possible to duplicate all values in a table while updating one or more columns

I have a table with many columns, and what I would like to do is duplicate all of the rows in the table, but also update one of the columns to a new value.
For example lets say I have the table below. I want to add to my table a duplicate of each row, except instead of BASIC access, it will have 'ADVANCED':
Before:
NAME, GENDER, ACCESS
----------------------
STEVE, MALE, BASIC
MOLLY, FEMALE, BASIC
After
NAME, GENDER, ACCESS
----------------------
STEVE, MALE, BASIC
MOLLY, FEMALE, BASIC
STEVE, MALE, ADVANCED
MOLLY, FEMALE, ADVANCED
Is there a way to do this without specifying all columns? I have 60 columns in the table, and the structure can change (meaning columns may be added, removed, renamed, etc).
Is it possible in Oracle SQL to automate this?
Just use insert . . . select:
insert into t (name, gender, access)
select name, gender, 'ADVANCED'
from t;
You need to list all the columns. You can shorten the manual process by using a query to generate the list. If you had to do this a lot and always knew you were leaving out access and access is the last column, you could use a view:
create view v_t as
select . . . -- all but access
from t;
insert into t ( . . . )
select v.*, 'ADVANCED'
from v_t;
Or you could use dynamic SQL to generate the statement.
However, I don't recommend any of those. Instead I would be concerned about a data model where you are regularly adding and modifying the columns in a table. That sounds dangerous.
Without specifying all the columns? With some help of a "temporary" table, here's how:
Your current table:
SQL> create table test
2 (name varchar2(10),
3 gender varchar2(20),
4 caccess varchar2(20));
Table created.
SQL> insert into test
2 select 'steve', 'male', 'basic' from dual union all
3 select 'molly', 'female', 'basic' from dual;
2 rows created.
Create a "temporary" table as a copy of the "original" table
update column you want to modify
copy the whole "temporary" table to the "original"
drop the "temporary" table
SQL> create table test_temp as select * From test;
Table created.
SQL> update test_temp set caccess = 'advanced';
2 rows updated.
SQL> insert into test select * From test_temp;
2 rows created.
SQL> drop table test_Temp;
Table dropped.
SQL> select * From test;
NAME GENDER CACCESS
---------- -------------------- --------------------
steve male basic
molly female basic
steve male advanced
molly female advanced
SQL>
Apparently, that works, but - what if the original table is huge? It takes a lot of space, and its copy takes approximately twice as much. Why are you doing that, anyway?
Try below method with anonymous block to avoid listing columns in insert statements
CREATE TABLE ACCESS_CHN
(NAAME VARCHAR2(100),
GENDER VARCHAR2(20),
ACCCESS VARCHAR2(30))
INSERT into ACCESS_CHN values('STEVE','MALE','BASIC');
INSERT into ACCESS_CHN values('MOLLY','FEMALE','BASIC');
COMMIT;
DECLARE
column_list varchar2(2000):=NULL;
plsql_block VARCHAR2(1000);
BEGIN
select LISTAGG(column_name,',') within group (order by column_id)
into column_list
from user_tab_columns
where table_name='ACCESS_CHN';
plsql_block := 'CREATE TABLE ACCESS_CHN_BKP as select '|| column_list || ' from ACCESS_CHN';
EXECUTE IMMEDIATE plsql_block;
plsql_block := 'UPDATE ACCESS_CHN_BKP set ACCCESS=''ADVANCED'' ';
EXECUTE IMMEDIATE plsql_block;
COMMIT;
plsql_block := 'CREATE TABLE ACCESS_CHN_FINAL as select * from ACCESS_CHN
union all
select * from ACCESS_CHN_BKP';
EXECUTE IMMEDIATE plsql_block;
END;
--To rerun drop tables ACCESS_CHN_BKP and ACCESS_CHN_FINAL

If the first field doesn't exists in a table then look at a different field in the same table

Is there a way to select a field from a table and if that field doesn't exist then select a different field from the same table? example:
SELECT MY_FIELD from MY_TABLE
error: "MY_FIELD": invalid identifier
is there any way to check if it exists and if it does then use that field for the query, if it doesn't exist then use example:
SELECT my_field2 from client.
My problem is
I am writing a report that will be used on two databases, but the field names on occasion can be named slightly different depending on the database.
What you really need to do is talk to your management / development leads about why the different databases are not harmonized. But, since this is a programming site, here is a programming answer using dynamic SQL.
As has been pointed out, you could create views in the different databases to provide yourself with a harmonized layer to query from. If you are unable to create views, you can do something like this:
create table test ( present_column NUMBER );
insert into test select rownum * 10 from dual connect by rownum <= 5;
declare
l_rc SYS_REFCURSOR;
begin
BEGIN
OPEN l_rc FOR 'SELECT missing_column FROM test';
EXCEPTION
WHEN others THEN
OPEN l_rc FOR 'SELECT present_column FROM test';
END;
-- This next only works in 12c and later
-- In earlier versions, you've got to process l_rc on your own.
DBMS_SQL.RETURN_RESULT(l_rc);
end;
This is inferior to the other solutions (either harmonizing the databases or creating views). For one thing, you get no compile time checking of your queries this way.
That won't compile, so - I'd say not. You might try with dynamic SQL which reads contents of the USER_TAB_COLUMNS and create SELECT statement on-the-fly.
Depending on reporting tool you use, that might (or might not) be possible. For example, Apex offers (as reports's source) a function that returns query, so you might use it there.
I'd suggest a simpler option - create views on both databases which have unified column names, so that your report always selects from the view and works all the time. For example:
-- database 1:
create view v_client as
select client_id id,
client_name name
from your_table;
-- database 2:
create view v_client as
select clid id,
clnam name
from your_table;
-- reporting tool:
select id, name
from v_client;
This can be done in a single SQL statement using DBMS_XMLGEN.GETXML, but it gets messy. It would probably be cleaner to use dynamic SQL or a view, but there are times when it's difficult to create supporting objects.
Sample table:
--Create either table.
create table my_table(my_field1 number);
insert into my_table values(1);
insert into my_table values(2);
create table my_table(my_field2 number);
insert into my_table values(1);
insert into my_table values(2);
Query:
--Get the results by converting XML into rows.
select my_field
from
(
--Convert to an XMLType.
select xmltype(clob_results) xml_results
from
(
--Conditionally select either MY_FIELD1 or MY_FIELD2, depending on which exists.
select dbms_xmlgen.GetXML('select my_field1 my_field from my_table') clob_results
from user_tab_columns
where table_name = 'MY_TABLE'
and column_name = 'MY_FIELD1'
--Stop transformations from running the XMLType conversion on nulls.
and rownum >= 1
union all
select dbms_xmlgen.GetXML('select my_field2 my_field from my_table') clob_results
from user_tab_columns
where table_name = 'MY_TABLE'
and column_name = 'MY_FIELD2'
--Stop transformations from running the XMLType conversion on nulls.
and rownum >= 1
)
--Only convert non-null values.
where clob_results is not null
)
cross join
xmltable
(
'/ROWSET/ROW'
passing xml_results
columns
my_field number path 'MY_FIELD'
);
Results:
MY_FIELD
--------
1
2
Here's a SQL Fiddle if you want to see it running.

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

how to get table name from column value in oracle sql?

I have a main table that has two columns with table names and id's. And I have those tables with table names in my DB.
For example, I find particular table name, selecting id. And then I want to populate table with that name with data. And I want to do that in one query. How I can do that?
The goal: to populate with data all tables at once, that has the names that similar with values in table name column from main table.
That is how I'm getting the list of tables. I should probably loop through it.
select tbl from asp_tbl where asp in (
select id from (
SELECT * FROM DIMENSION WHERE EXTERNALKEY LIKE 'W16%')
);
And then I will try to merge the data from other tables inside the table that needs to be populated:
MERGE INTO tbl d
USING
(SELECT ? nums, ? names from data_table) s
ON(d.product = s.product and d.ga = s.ga and d.metric_id = s.metric_id)
WHEN MATCHED THEN UPDATE SET d.names = s.names
WHEN NOT MATCHED THEN INSERT (nums, names)values(s.nums,s.names);
Did I provide enough info?
As I understand you need some stored procedure witch may fulfil a table with some test data. If so you may write something like:
create procedure fulfil_test_data (p_table_name varchar2) is
begin
for x IN (select tbl from asp_tbl where asp in (
SELECT table_id FROM DIMENSION WHERE EXTERNALKEY LIKE p_table_name )) loop
execute immediate 'insert into '|| x.tbl ||' (nums, names)
select level , chr(ascci(''A'') + mod(level,26)) from dual connect by level < 1001';
end loop;
end;
/
And call it
begin
fulfil_test_data('W16%');
end;
/