Dynamic table name in pl/sql - oracle

I need to select columns from a table Table_A, However there is another table which has the same schema Table_B. The query should determine the from table dynamically. For ex. if Table_A has more rows then use Table_A else use Table_B.
Query something like this
select employee, salary, id from (condition to count rows and select the table )table;
Is this possible without using cursors and EXECUTE IMMEDIATE??.

Normally, you would use dynamic SQL for this sort of thing. That would involve either using the DBMS_SQL package, EXECUTE IMMEDIATE or doing an OPEN <<cursor>> FOR <<SQL statement string>>.
If you really want to use static SQL, you could query both tables and only return one set of results. I cannot envision a situation where this would really make sense but you can certainly do it
Create a FOO and a FOO2 table. FOO2 has two rows to the one row from FOO
SQL> create table foo( col1 number );
Table created.
SQL> create table foo2( col1 number );
Table created.
SQL> insert into foo values( 1 );
1 row created.
SQL> insert into foo2 values( 1 );
1 row created.
SQL> insert into foo2 values( 2 );
1 row created.
Run the query. This will return all the data from FOO2
SQL> ed
Wrote file afiedt.buf
1 select col1
2 from (select the_union.*,
3 max(cnt) over () max_cnt
4 from (select col1, count(*) over () cnt from foo
5 union all
6 select col1, count(*) over () from foo2) the_union)
7* where cnt = max_cnt
SQL> /
COL1
----------
1
2
Insert more rows into FOO. Now the same query will return all the data from FOO
SQL> insert into foo values( 3 );
1 row created.
SQL> insert into foo values( 5 );
1 row created.
SQL> commit;
Commit complete.
SQL> select col1
2 from (select the_union.*,
3 max(cnt) over () max_cnt
4 from (select col1, count(*) over () cnt from foo
5 union all
6 select col1, count(*) over () from foo2) the_union)
7 where cnt = max_cnt;
COL1
----------
1
3
5
As I said, though, I cannot fathom a situation where it would actually make sense to do this.

I am completely guessing at what you are really trying to do, but I am thinking you want to use a synonym. I am guessing that there is some sort of event that triggers when you should be using TableA vs. TableB. Create a synonym "my_table" pointing to TableA and have your view select from "my_table". Then whenever you want your view to point to TableB instead, you just switch your synonym to point to TableB, and you don't have to do anything to the view.
For more info, read about synonyms in the Concepts Guide.

Related

(Oracle)Correlated subquery usage

Subquery query1 below works fine.
But when I put equi condition in -sort of- nested clause like query2, it shows error ORA-00904.
Is this wrong usage of correlated subquery or it is because of other reason?
--Query1: It shows expected result.
SELECT
O.ENAME
O,SAL
,(SELECT COUNT(*)
FROM SCOTT.EMP I
WHERE I.SAL>O.SAL --correlated to outer
) AS RESULT
from SCOTT.EMP O;
--Query2:ORA-00904: "O"."SAL": invalid identifier shows. How to modify to use correlated subquery?
SELECT
O.ENAME
O,SAL
,(
WITH TEMP AS
(
SELECT COUNT(*)
FROM SCOTT.EMP I
WHERE I.SAL>O.SAL --I have put equi condistion here
)
SELECT * FROM TEMP
) AS RESULT
from SCOTT.EMP O;
I believe the second option is a wrong use of correlated subqueries, not because of the comparison, but for the use of the with clause. I would like to remember that you should avoid correlated subqueries as much as possible.
The WITH clause, or subquery factoring clause, may be processed as an inline view or resolved as a temporary table. The advantage of the latter is that repeated references to the subquery may be more efficient as the data is easily retrieved from the temporary table, rather than being requeried by each reference.
In your third column of your second query you want to get the result from the inline view. The problem is that parsing of the inline view is done independently and therefore cannot have references to anything in the outer query.
SQL> create table emp ( ename varchar2(10) , sal number ) ;
Table created.
SQL> insert into emp values ( 'AAA' , 1000 ) ;
insert into emp values ( 'BBB' , 1000 ) ;
insert into emp values ( 'CCC' , 1000 ) ;
insert into emp values ( 'DDD' , 1000 ) ;
1 row created.
SQL> SQL>
1 row created.
SQL> SQL>
1 row created.
SQL> SQL>
1 row created.
SQL> select * from emp ;
ENAME SAL
---------- ----------
AAA 1000
BBB 1000
CCC 1000
DDD 1000
To write the query with inline view, the filter must be done in the outer query
SELECT
O.ENAME
O,SAL
,(
WITH TEMP AS
(
SELECT * FROM EMP
)
SELECT count(*) FROM TEMP t WHERE t.SAL>O.SAL
) AS RESULT
from EMP O;
O SAL RESULT
---------- ---------- ----------
AAA 1000 0
BBB 1000 0
CCC 1000 0
DDD 1000 0
It has been explained that the second query does not uses with correctly.
Let me suggest, however, that your query can be simpler and more efficiently phrased. For each employee, you want to count how many employees have a greater salary. Window functions are the way to go here:
select e.*, rank() over(order by salary desc) - 1 result
from scott.emp e

How to select column value from a nested table

I created 1 object.
create type tab_billing as object(invoice_no number,
customername varchar2(100)
);
Now i created a table with the object as a column.
CREATE TABLE tab1 (col1 number,COL2 tab_billing);
Is there anyway I can ONLY select invoice_no from the tab1.
select col2 from tab1;
Is givng me both invoice_no and customername. Substr function is not working here.
You can query the column value's object field directly, but to avoid confusing the object name resolution steps you have to supply and use a table alias:
select t1.col2.invoice_no from tab1 t1;
This is mentioned in the documentation:
To avoid inner capture and similar problems resolving references, Oracle Database requires you to use a table alias to qualify any dot-notational reference to subprograms or attributes of objects.
Qualifying the column with the the table name isn't enough; using select tab1.col2.invoice_no from tab1 gets ORA-00904. You have to use a table alias - although, slightly bizarrely, it still works if the alias is the same as the table name, so select tab1.col2.invoice_no from tab1 tab1 (i.e. aliasing tab1 as tab1, which is normally redundant) works too.
Quick demo:
create type tab_billing as object(invoice_no number,
customername varchar2(100)
);
/
Type TAB_BILLING compiled
CREATE TABLE tab1 (col1 number,COL2 tab_billing);
Table TAB1 created.
insert into tab1 values (1, tab_billing(42, 'Test'));
1 row inserted.
select t1.col2.invoice_no from tab1 t1;
COL2.INVOICE_NO
---------------------------------------
42
You can use TREAT:
SQL> create type tab_billing as object(invoice_no number,
2 customername varchar2(100)
3 );
4 /
Type created.
SQL> CREATE TABLE tab1 (col1 number,COL2 tab_billing);
Table created.
SQL> insert into tab1 values (1, tab_billing(10, 'ten')) ;
1 row created.
SQL> select col1,
2 TREAT(col2 AS tab_billing).invoice_no as invoice_no,
3 TREAT(col2 AS tab_billing).customername as customername
4 from tab1;
COL1 INVOICE_NO CUSTOMERNAME
------ ---------- --------------------
1 10 ten

Correctly use collection method

CREATE OR REPLACE TYPE nvarchar2_list_type AS TABLE OF NVARCHAR2(100);
CREATE TABLE test_table(
id number primary key,
cars_list nvarchar2_list_type
)
NESTED TABLE cars_list STORE AS cars_list_storage_table;
insert into test_table(id, cars_list)
values(1, nvarchar2_list_type( 'AUDI', 'MERCEDES') );
All above operations completed success, 1 rows inserted in table test_table, now i write this function:
create or replace function get_cnt
return number
as
ret_val number;
begin
SELECT cars_list.COUNT
INTO ret_val
from test_table where id = 1;
return ret_val;
end;
This gives error: ORA-00904: "CARS_LIST"."COUNT": invalid identifier
Tell please what is wrong here?
As I know, COUNT method must be used just so (from here)
No, you cannot use count method in this situation. You have SQL nested table at hand, count method is used only with PL/SQL collections.
To count number of nested table's elements you can either unnest that nested table or use scalar sub-query:
Unnesting:
SQL> select id
2 , count(*) as cnt
3 from test_table t
4 cross join table(t.cars_list)
5 group by id
6 ;
ID CNT
---------- ----------
1 2
Scalar sub-query:
SQL> select id
2 , (select count(column_value)
3 from table(t.cars_list)) as cnt
4 from test_table t
5 ;
ID CNT
---------- ----------
1 2
Use
Select
Cardinality(cars_list) from test_table
I can't explain why that doesn't work, but this does:
select (select count(*) from table(cars_list))
into ret_val
from test_table
where id = 1;
Oracle is expecting column name or function in its select list, but what you are giving is collection build in method that operates on collections only.
You can achieve the same using scalar sub query
SELECT (select count(1) from table(cars_list)) as "COUNT"
FROM test_table
WHERE id = 1;

delete row by id

I'm in the middle of creating a tool similar to the SQL Developer table data viewer. My db is Oracle based.
I simply need to delete eg.: 'row number 3' from a SELECT result. That table doesn't have any PK nor unique records. I've tried various techniques with ROWNUM etc. but no luck.
Oracle has a ROWID pseudocolumn that you can use for this purpose in simple cases.
select rowid, ... from your_table where ... ;
delete from your_table where rowid = <what you got above>;
If your interface allows the user to make complex views/joins/aggregates, then knowing what the user intended to delete (so knowing what set of rowids to gather and what set of tables to delete from) is going to be tricky.
Warning: rowids are unique only within a given table, and, quoting the above documentation:
If you delete a row, then Oracle may reassign its rowid to a new row inserted later.
So be very, very careful if you do this.
Assuming that it is a standard heap-organized table (index-organized tables and clusters potentially introduce additional complexity), if you don't have any other way to identify a row, you can use the ROWID pseudocolumn. This gives you information about the physical location of a row on disk. This means that the ROWID for a particular row can change over time and the ROWID can and will be reused when you delete a row and then a subsequent INSERT operation inserts a new row that happens to be in the same physical location on disk. For most applications, it is reasonable to assume that the ROWID will remain constant between the time that you execute the query and the time that you issue the DELETE but you shouldn't try to store the ROWID for any period of time.
For example, if we create a simple two-column table and a few rows
SQL> create table foo( col1 number, col2 varchar2(10) );
Table created.
SQL> insert into foo values( 1, 'Justin' );
1 row created.
SQL> insert into foo values( 1, 'Justin' );
1 row created.
SQL> insert into foo values( 2, 'Bob' );
1 row created.
SQL> insert into foo values( 2, 'Charlie' );
1 row created.
SQL> commit;
Commit complete.
We can SELECT the ROWID and then DELETE the third row using the ROWID
SQL> select *
2 from foo;
COL1 COL2
---------- ----------
1 Justin
1 Justin
2 Bob
2 Charlie
SQL> select rowid, col1, col2
2 from foo;
ROWID COL1 COL2
------------------ ---------- ----------
AAAfKXAAEAABt7vAAA 1 Justin
AAAfKXAAEAABt7vAAB 1 Justin
AAAfKXAAEAABt7vAAC 2 Bob
AAAfKXAAEAABt7vAAD 2 Charlie
SQL> delete from foo where rowid = 'AAAfKXAAEAABt7vAAC';
1 row deleted.
SQL> select * from foo;
COL1 COL2
---------- ----------
1 Justin
1 Justin
2 Charlie
Try using ROWID instead of ROWNUM.

Oracle Equivalent to MySQL INSERT IGNORE?

I need to update a query so that it checks that a duplicate entry does not exist before insertion. In MySQL I can just use INSERT IGNORE so that if a duplicate record is found it just skips the insert, but I can't seem to find an equivalent option for Oracle. Any suggestions?
If you're on 11g you can use the hint IGNORE_ROW_ON_DUPKEY_INDEX:
SQL> create table my_table(a number, constraint my_table_pk primary key (a));
Table created.
SQL> insert /*+ ignore_row_on_dupkey_index(my_table, my_table_pk) */
2 into my_table
3 select 1 from dual
4 union all
5 select 1 from dual;
1 row created.
Check out the MERGE statement. This should do what you want - it's the WHEN NOT MATCHED clause that will do this.
Do to Oracle's lack of support for a true VALUES() clause the syntax for a single record with fixed values is pretty clumsy though:
MERGE INTO your_table yt
USING (
SELECT 42 as the_pk_value,
'some_value' as some_column
FROM dual
) t on (yt.pk = t.the_pke_value)
WHEN NOT MATCHED THEN
INSERT (pk, the_column)
VALUES (t.the_pk_value, t.some_column);
A different approach (if you are e.g. doing bulk loading from a different table) is to use the "Error logging" facility of Oracle. The statement would look like this:
INSERT INTO your_table (col1, col2, col3)
SELECT c1, c2, c3
FROM staging_table
LOG ERRORS INTO errlog ('some comment') REJECT LIMIT UNLIMITED;
Afterwards all rows that would have thrown an error are available in the table errlog. You need to create that errlog table (or whatever name you choose) manually before running the insert using DBMS_ERRLOG.CREATE_ERROR_LOG.
See the manual for details
I don't think there is but to save time you can attempt the insert and ignore the inevitable error:
begin
insert into table_a( col1, col2, col3 )
values ( 1, 2, 3 );
exception when dup_val_on_index then
null;
end;
/
This will only ignore exceptions raised specifically by duplicate primary key or unique key constraints; everything else will be raised as normal.
If you don't want to do this then you have to select from the table first, which isn't really that efficient.
Another variant
Insert into my_table (student_id, group_id)
select distinct p.studentid, g.groupid
from person p, group g
where NOT EXISTS (select 1
from my_table a
where a.student_id = p.studentid
and a.group_id = g.groupid)
or you could do
Insert into my_table (student_id, group_id)
select distinct p.studentid, g.groupid
from person p, group g
MINUS
select student_id, group_id
from my_table
A simple solution
insert into t1
select from t2
where not exists
(select 1 from t1 where t1.id= t2.id)
This one isn't mine, but came in really handy when using sqlloader:
create a view that points to your table:
CREATE OR REPLACE VIEW test_view
AS SELECT * FROM test_tab
create the trigger:
CREATE OR REPLACE TRIGGER test_trig
INSTEAD OF INSERT ON test_view
FOR EACH ROW
BEGIN
INSERT INTO test_tab VALUES
(:NEW.id, :NEW.name);
EXCEPTION
WHEN DUP_VAL_ON_INDEX THEN NULL;
END test_trig;
and in the ctl file, insert into the view instead:
OPTIONS(ERRORS=0)
LOAD DATA
INFILE 'file_with_duplicates.csv'
INTO TABLE test_view
FIELDS TERMINATED BY ','
(id, field1)
How about simply adding an index with whatever fields you need to check for dupes on and say it must be unique? Saves a read check.
yet another "where not exists"-variant using dual...
insert into t1(id, unique_name)
select t1_seq.nextval, 'Franz-Xaver' from dual
where not exists (select 1 from t1 where unique_name = 'Franz-Xaver');

Resources