ORACLE 12.2.01 selecting columns from different tables with similar names --> internal column identifier used - oracle

I wrote a SELECT performing a UNION and in each UNION part using some JOINs. The tables, which are joined have partly the same column identifiers.
And if a "SELECT *" is performed, ORACLE decides to display the internal column names instead of the "real" column names.
To show the effect I created two tables (with partly similar column identifiers, "TID" and "TNAME") and filled them with some data:
create table table_one (tid number(10), tname varchar2(10), t2id number(10));
create table table_two (tid number(10), tname varchar2(10));
insert into table_two values (1,'one');
insert into table_two values (2,'two');
insert into table_two values (3,'three');
insert into table_one values (1,'eins',1);
insert into table_one values (2,'zwei',2);
insert into table_one values (3,'drei',3);
The I SELECTED the columns afterwards with the following statement:
select *
from table_one
inner join table_two on table_two.tid = table_one.t2id
where table_one.tid = 1
union
select *
from table_one
inner join table_two on table_two.tid = table_one.t2id
where table_one.tid = 2;
And got this confusing result:
QCSJ_C000000000300000 QCSJ_C000000000300002 T2ID QCSJ_C000000000300001 QCSJ_C000000000300004
1 eins 1 1 one
2 zwei 2 2 two
When the statement is written with tablenames to specify the columns, everything works as I expected:
select table_one.* , table_two.*
from table_one
inner join table_two on table_two.tid = table_one.t2id
where table_one.tid = 1
minus
select *
from table_one
inner join table_two on table_two.tid = table_one.t2id
where table_one.tid = 2;
TID TNAME T2ID TID TNAME
1 eins 1 1 one
2 zwei 2 2 two
Can anybody explain that?
I expanded my tests with two more tables to prevent double usage of table in the statement:
create table table_3 (tid number(10), tname varchar2(10), t4id number(10));
create table table_4 (tid number(10), tname varchar2(10));
insert into table_4 values (1,'one');
insert into table_4 values (2,'two');
insert into table_4 values (3,'three');
insert into table_3 values (1,'eins',1);
insert into table_3 values (2,'zwei',2);
insert into table_3 values (3,'drei',3);
select *
from table_one
inner join table_two on table_two.tid = table_one.t2id
where table_one.tid = 1
union
select *
from table_3
inner join table_4 on table_4.tid = table_3.t4id
where table_3.tid = 2;
select *
from table_one
inner join table_two on table_two.tid = table_one.t2id
where table_one.tid = 1
union
select *
from table_3
inner join table_4 on table_4.tid = table_3.t4id
where table_3.tid = 2;
The result is the same. Oracle uses internal identifiers.

According to Oracle (DocId 2658003.1), this happens when three conditions are met:
ANSI join
UNION / UNION ALL
the same table appears more than once in the query
Aparently, "QCSJ_C" is used internally when Oracle transforms ANSI style joins.
EDIT:
Found a minimal example:
SELECT * FROM dual d1 JOIN dual d2 ON d1.dummy=d2.dummy
UNION
SELECT * FROM dual d1 JOIN dual d2 ON d1.dummy=d2.dummy;
QCSJ_C000000000300000 QCSJ_C000000000300001
X X
It can be fixed by either using non-ANSI join syntax:
SELECT * FROM dual d1, dual d2 WHERE d1.dummy=d2.dummy
UNION
SELECT * FROM dual d1, dual d2 WHERE d1.dummy=d2.dummy;
DUMMY DUMMY_1
X X
Or, preferably by using column names instead of *:
SELECT d1.dummy, d2.dummy FROM dual d1 JOIN dual d2 ON d1.dummy=d2.dummy
UNION
SELECT d1.dummy, d2.dummy FROM dual d1 JOIN dual d2 ON d1.dummy=d2.dummy;
DUMMY DUMMY_1
X X

Interesting!
However, I would never use a set operator (UNION, UNION ALL, INTERSECT, MINUS) together with an asterisk (*).
The order of columns can change, maybe not by you but by somebody doing maintenance on the database, or by migrating your database to a new system using export/import, etc. Simple example:
CREATE TABLE t (a INT, b INT, c INT);
SELECT * FROM t;
A B C
ALTER TABLE t MODIFY b INVISIBLE;
ALTER TABLE t MODIFY b VISIBLE;
SELECT * FROM t;
A C B

Related

Oracle How to make SELECT INSIDE A SELECT work?

Just wondering why the following select isn't working:
SELECT
A.FIELD1
, (SELECT PCN FROM (select B.PRIORITY, B.PCN
from
TABLE2 B
WHERE B.CUST= A.CUST
ORDER BY B.PRIORITY)
WHERE ROWNUM = 1) AS PCN
FROM TABLE1 A;
ERROR at line 2: ORA-00904: "A"."CUST": invalid identifier
Important to mention:
TABLE1 has as fields FIELD1, CUST.
TABLE2 has as fields PCN, PRIORITY, CUST.
Thanks in advance.
Your query shouldn't give you that error message, on when you remove the outer qiery this would happen
CREATE tABLE TABLE1 (FIELD1 int, CUST int)
INSERT INTO TABLE1 VALUES(1,1)
1 rows affected
CREATE TABLE TABLE2 (PCN int, PRIORITY int, CUST int)
INSERT INTO TABLE2 VALUES (1,1,1)
1 rows affected
SELECT
A.FIELD1
, (SELECT PCN FROM (select B.PRIORITY, B.PCN
from
TABLE2 B
WHERE B.CUST= A.CUST
ORDER BY B.PRIORITY)
WHERE ROWNUM = 1) AS PCN
FROM TABLE1 A;
FIELD1
PCN
1
1
fiddle
You can't nest inline selects (more than one level) without losing the ability of the inner nested selects being able to reference the parent block. So your query on TABLE2 cannot see the columns from TABLE1 because of this nesting.
Try this:
SELECT a.field1,
pcn.pcn
FROM table1 a,
(SELECT b.cust,
b.priority,
b.pcn,
ROW_NUMBER() OVER (PARTITION BY b.cust ORDER BY b.priority DESC) seq
FROM table2 b) pcn
WHERE a.cust = pcn.cust(+)
AND pcn.seq(+) = 1
That will work well for report queries. If you end up adding a filter on a specific customer, then you would be better off using OUTER APPLY if you have a recent-enough version of Oracle that supports that.
You could try this:
SELECT
A.FIELD1
, (SELECT B.PCN
from
TABLE2 B
WHERE B.CUST= A.CUST
ORDER BY B.PRIORITY
FETCH FIRST 1 ROWS ONLY) AS PCN
FROM TABLE1 A;
FETCH FIRST 1 ROWS ONLY gets you the first ordered record. Works on 12c and up and supports nesting, and no 2nd subquery needed.
Yet another option might be a CTE.
Sample data:
SQL> with
2 table1 (field1, cust) as
3 (select 1, 100 from dual union all
4 select 2, 200 from dual
5 ),
6 table2 (pcn, priority, cust) as
7 (select 10, 1, 100 from dual union all
8 select 20, 2, 100 from dual union all
9 select 30, 1, 200 from dual
10 ),
Query begins here. Rank rows by priority, and then fetch the ones that rank as the highest (line #20):
11 temp as
12 (select a.field1,
13 b.pcn,
14 rank() over (partition by a.field1 order by b.priority desc) rnk
15 from table1 a join table2 b on a.cust = b.cust
16 )
17 select field1,
18 pcn
19 from temp
20 where rnk = 1;
FIELD1 PCN
---------- ----------
1 20
2 30
SQL>
You may use first aggregate function to achieve the same (assuming that you have completely deterministic order by) functionality without nested subquery:
select
a.field1
, (
select max(b.pcn) keep(dense_rank first order by b.priority)
from table2 b
where b.cust = a.cust
) as pcn
from table1 a
which for this sample data
insert into table1 values(1,1);
insert into table1 values(2,2);
insert into table2 values(1,1,1);
insert into table2 values(2,2,1)
returns
FIELD1
PCN
1
1
2
(null)
SQL fiddle

Oracle delete from tableA where a duplicate row is in tableB

As the title says, I am looking for a way to remove all rows from TableA where there is a matching row in TableB.
the Tables A & B have about 30 columns in them so a WHERE A.col1 = B.col1 etc would be a little problematical. Ideally I was hoping for something like
DELETE FROM tableA WHERE IN TableB
(overly simplified by this type of thing)
IN clause can compare all columns returned from select
DELETE FROM tableA WHERE ( col1,col2,col3,.. ) IN ( select col1,col2,col3... FROM TableB );
The brute force way to establish if two records from each table are the same is to just compare every column:
DELETE
FROM tableA a
WHERE EXISTS (SELECT 1 FROM tableB b WHERE a.col1 = b.col1 AND a.col2 = b.col2 AND ...
a.col30 = b.col30);
You could create function which checks structures of tables and, if they are the same, creates string containing correct conditions to compare.
For example here are two tables:
create table t1 (id, name, age) as (
select 1, 'Tom', 67 from dual union all
select 2, 'Tia', 42 from dual union all
select 3, 'Bob', 16 from dual );
create table t2 (id, name, age) as (
select 1, 'Tom', 51 from dual union all
select 3, 'Bob', 16 from dual );
Now use function:
select generate_condition('T1', 'T2') from dual;
result:
T1.ID = T2.ID and T1.NAME = T2.NAME and T1.AGE = T2.AGE
Copy this, paste and run delete query:
delete from t1 where exists (select 1 from t2 where <<PASTE_HERE>>)
Here is the function, adjust it if needed. I used user_tab_columns so if tables are on different schemas you need all_tab_columns and compare owners too. If you have Oracle 11g you can replace loop with listagg(). Second table has to contain all columns of first table and they have to be same type and length.
create or replace function generate_condition(i_t1 in varchar2, i_t2 in varchar2)
return varchar2 is
v varchar2(1000) := '';
begin
for rec in (select column_name, u2.column_id
from user_tab_cols u1
left join (select * from user_tab_cols where table_name = i_t2) u2
using (column_name, data_type, data_length)
where u1.table_name = i_t1 order by u1.column_id)
loop
if rec.column_id is null then
v := 'ERR: incompatible structures';
goto end_loop;
end if;
v := v||' and '||i_t1||'.'||rec.column_name
||' = '||i_t2||'.'||rec.column_name;
end loop;
<< end_loop >>
return(ltrim(v, ' and '));
end;
If you want to avoid running process manually you need dynamic PL/SQL.
create table tableA (a NUMBER, b VARCHAR2(5), c INTEGER);
create table tableB (a NUMBER, b VARCHAR2(5), c INTEGER);
As you said
WHERE A.col1 = B.col1 etc would be a little problematical
you could intersect the tables and mention all columns from tableA one time, like this:
delete tableA
where (a,b,c) in (select * from tableA
intersect
select * from tableB);

Oracle select random rows matching a join condition

My objective is simple, I have to create a temporary table with some random values from a employee table whenever the department is in some particular department (say 2). For the rest of departments I don't care the value, it can be NULL.
Currently I have the following :
create table test
as
select s.DEPTNAME,
cast (
(case when s.DEPTID in (2) then
(SELECT a.ENAME FROM
(SELECT b.ENAME, b.DEPTID FROM EMPLOYEE b
WHERE b.DEPTID IS NOT NULL
ORDER BY DBMS_RANDOM.VALUE) a
WHERE a.DEPTID = s.DEPTID AND ROWNUM = 1
)
END)
AS VARCHAR2(30)) "ENAME" from DEPARTMENT s;
But the main issue here is related to performance. For every department value in 2 we do a sort of EMPLOYEE table to get a single random ENAME.
Is there a better way to do this ? I know sample might work but I want to achieve more randomness.
First idea - join randomly numbered enames:
with
e as (select ename, deptid, row_number() over (order by dbms_random.value) rn
from employee where deptid = 2),
c as (select count(1) cnt from e),
d as (select deptname, deptid, round(dbms_random.value(1, c.cnt)) rn from department, c)
select d.deptname, e.ename from d left join e using (rn, deptid)
SQLFiddle demo
Second possible solution, which worked for me, is to create function returning random ename from table employee
and use it in your query, but it would be probably slower.
Edit - according to comment:
If, for some reason, the first part of your statement is "fixed", then you could use this syntax:
create table test as
select deptname, ename from (
with
e as (select ename, deptid, row_number() over (order by dbms_random.value) rn
from employee where deptid = 2),
c as (select count(1) cnt from e),
d as (select deptname, deptid, round(dbms_random.value(1, c.cnt)) rn
from department cross join c)
select d.deptname, e.ename from d left join e using (rn, deptid));

How to force push predicate through UNION ALL inside a view?

I have a performance problem on a UNION ALL view. The problem can be solved by rewriting the view in two separate views but that kind of defeats the purpose of creating a view.
Here is a simple test case (Oracle 11.2.0.3.0). The real queries use about 10 different tables instead of just 3.
CREATE TABLE t0 (id number, ref_id number);
CREATE INDEX i0 on t0(id);
CREATE TABLE t1 (id number, amount number);
CREATE INDEX i1 on t1(id);
CREATE TABLE t2 (id number, amount number);
CREATE INDEX i2 on t2(id);
insert into t0 select rownum, rownum * 10 from dual connect by rownum <= 100000;
insert into t1 select rownum, rownum * 10 from dual connect by rownum <= 100000;
insert into t2 select rownum, rownum * 10 from dual connect by rownum <= 100000;
CREATE OR REPLACE VIEW v2
AS
SELECT id, sum(amount) AS total_amount
FROM t1
GROUP BY id;
CREATE OR REPLACE VIEW v3
AS
SELECT id
,sum(amount) as total_amount
FROM (SELECT id, amount
FROM t1
UNION ALL
SELECT id, amount
FROM t2)
GROUP BY id
HAVING sum(amount) <> 0
;
CREATE OR REPLACE view v1
AS
SELECT *
FROM v2
UNION ALL
SELECT *
FROM v3;
The following query uses 766 gets. Adding push_pred(a) does nothing for it.
select --+ first_rows
*
from t0, v1 a
where t0.ref_id = a.id
and t0.id = 1;
Next query bottoms out at 16 gets although it does the same thing as the first one, only scans t0 two times instead of one.
select --+ first_rows
*
from t0, v2
where t0.ref_id = v2.id
and t0.id = 1
union all
select *
from t0, v3
where t0.ref_id = v3.id
and t0.id = 1;
What am I missing?

Delete with Left Join in Oracle 10g

I have the following code that works fine in MS SQL Server:
delete grp
from grp
left join my_data
on grp.id1 = my_data.id1
and grp.id2 = my_data.id2
and grp.id3 = my_data.id3
and grp.id4 = my_data.id4
where my_data.id1 is NULL
Basically, I want to delete all occurrence that can be found in grp and don't have any equivalence in my_data. Sadly, it doesn't work in Oracle 10g. I tried using the old syntax for left join (+) but it doesn't work either. Like this:
delete grp
from grp,
my_data
where grp.id1 = my_data.id1 (+)
and grp.id2 = my_data.id2 (+)
and grp.id3 = my_data.id3 (+)
and grp.id4 = my_data.id4 (+)
and my_data.id1 is NULL
A IN clause would works if I didn't have multiple keys but I don't see how I could use it with my data. So, what is the alternative?
Shannon's solution is the way to go: use the operator NOT IN (or NOT EXISTS).
You can however delete or update a join in Oracle, but the synthax is not the same as MS SQL Server:
SQL> DELETE FROM (SELECT grp.*
2 FROM grp
3 LEFT JOIN my_data ON grp.id1 = my_data.id1
4 AND grp.id2 = my_data.id2
5 AND grp.id3 = my_data.id3
6 AND grp.id4 = my_data.id4
7 WHERE my_data.id1 IS NULL);
2 rows deleted
Additionally, Oracle will only let you update a join if there is no ambiguity as to which base row will be accessed by the statement. In particular, Oracle won't risk an update or a delete (the statement will fail) if there is a possibility that a row may appear twice in the join. In this case, the delete will only work if there is a UNIQUE constraint on my_data(id1, id2, id3, id4).
Tables and data:
SQL> create table grp (id1 number null, id2 number null, id3 number null, id4 number null);
Table created.
SQL> create table my_data (id1 number null, id2 number null, id3 number null, id4 number null);
Table created.
SQL> insert into grp values (1, 2, 3, 4);
1 row created.
SQL> insert into grp values (10, 20, 30, 40);
1 row created.
SQL> insert into grp values (1, 2, 30, 40);
1 row created.
SQL> insert into my_data values (1, 2, 3, 4);
1 row created.
SQL> commit;
Commit complete.
Using in. Note Do not use if the IDs in the subquery can be null. Not in of null never returns true.
SQL> delete grp where (id1, id2, id3, id4) not in (select id1, id2, id3, id4 from my_data);
2 rows deleted.
SQL> select * from grp;
ID1 ID2 ID3 ID4
---------- ---------- ---------- ----------
1 2 3 4
Using exists
SQL> rollback;
Rollback complete.
SQL> delete grp where not exists (select * from my_data where grp.id1 = my_data.id1 and grp.id2 = my_data.id2 and grp.id3 = my_data.id3 and grp.id4 = my_data.id4);
2 rows deleted.
SQL> select * from grp;
ID1 ID2 ID3 ID4
---------- ---------- ---------- ----------
1 2 3 4
SQL>
If you want to ensure there is no ambiguity in what's being deleted, you could change Vincent's solution to:
delete from grp where rowid in
(
select
grp.rowid
from
grp left outer join my_data on
grp.id1 = my_data.id1
and grp.id2 = my_data.id2
and grp.id3 = my_data.id3
and grp.id4 = my_data.id4
where
my_data.id1 is NULL
)
Either Vincent's answer https://stackoverflow.com/a/3675205 does not work at all, or it does not work in Oracle 12c. That answer should be improved by specifying the lowest or highest version of Oracle where this works. The proof:
SELECT * FROM v$version where banner like 'Oracle%';
/*
Oracle Database 12c Standard Edition Release 12.2.0.1.0 - 64bit Production
*/
create table a (id int);
create table b (id int);
insert into a select 1 from dual union select 2 from dual;
insert into b select 1 from dual union select 2 from dual union select 3 from dual;
select * from a right join b on b.id = a.id;
/*
1 1
2 2
null 3
*/
delete from (
select b.*
from b
inner join a on a.id = b.id
)
/*
Error at Command Line : 7 Column : 13
Error report -
SQL Error: ORA-01752: cannot delete from view without exactly one key-preserved table
01752. 00000 - "cannot delete from view without exactly one key-preserved table"
*Cause: The deleted table had
- no key-preserved tables,
- more than one key-preserved table, or
- the key-preserved table was an unmerged view.
*Action: Redefine the view or delete it from the underlying base tables.
*/
delete from b
where rowid in (
select b.rowid
from b
inner join a on a.id = b.id
)
/*
2 rows deleted.
*/
select * from a right join b on b.id = a.id
/*
null 3
*/
drop table a;
drop table b;
Bottom line is, use WHERE ROWID IN () at least in 12c.
I can't add a comment because it need 50 reps,so I add a answer here.
I tested Vincent's delete from query, that syntax can't let you delete what you want,at least it's not a common use for all the delete join cases.
At first I create a table using oracle default user scott:
create table emp1 as select * from emp where sal<2000;
I want to delete the records from emp where empno in emp1(just a simple test),so I used this delete from query:
delete from (select a.* from emp a join emp1 b on a.empno=b.empno);
No matter what the table or join order is,left join or inner join,no matter what where clause I use,the sql will delete the corresponding records in emp1.
So I think this delete from query can not let you delete from a specified table.
Loop a cursor will be a better way for these cases.

Resources