Oracle join tables and always include all possible results - oracle

I am trying to join two tables that look like the following:
Table 1
Letter | Value
A 2
B 5
Table 2
Letter | Number
A 1
C 7
I am trying to join these tables so that, regardless of what is in the tables at the time, there will always be an A,B,C record in the result. In other words, this would be displayed:
Letter | Value | Number
A 2 1
B 5 null
C null 7
The three letter records should always be displayed regardless of if they are in the tables. So assume table2 looks like this:
Letter | Number
A 1
Then I want the following results even though there is now no 'C' record:
Letter | Value | Number
A 2 1
B 5 null
C null null
Could anyone show how to do this?

You can use a SELECT ... FROM DUAL with a CONNECT BY clause to generate a stable set of letters in case some letters don't appear in either table.
with base_set_of_letters as (
select chr(rownum + 64) as Letter -- ascii 65=A, 66=B, 67=C, ...
from dual
connect by rownum <= 3) -- increase this number if you want more letters
select l.letter, t1.value, t2.xNumber
from base_set_of_letters l
left join Table1 t1 on t1.letter = l.letter
left join Table2 t2 on t2.letter = l.letter
order by l.letter;
SQL Fiddle Demo

If you were just looking to get all rows that existed joined, irregardless of whether data existed on both sides of the relationship, you'd be looking for a FULL OUTER JOIN:
SELECT COALESCE(t1.LETTER, t2.LETTER) AS LETTER,
t1.VALUE,
t2.NUM
FROM TABLE_1 t1
FULL OUTER JOIN TABLE_2 t2
ON t2.LETTER = t1.LETTER
ORDER BY COALESCE(t1.LETTER, t2.LETTER);
which produces
LETTER VALUE NUM
A 2 1
B 5 (null)
C (null) 7
But this would only give you rows where you've got a key value ('A', 'B', or 'C') - however, if one is missing you'd get nothing. IMO you need a third table, perhaps called ALPHABET, containing all possible letters:
CREATE TABLE ALPHABET (LETTER CHAR(1));
and populated with 'A', 'B', 'C', etc. In this case your join would become:
SELECT a.LETTER,
t1.VALUE,
t2.NUM
FROM ALPHABET a
FULL OUTER JOIN TABLE_1 t1
ON t1.LETTER = a.LETTER
FULL OUTER JOIN TABLE_2 t2
ON t2.LETTER = a.LETTER
which, if ALPHABET is only populated with ('A, 'B', 'C', and 'D'), produces
LETTER VALUE NUM
A 2 1
B 5 (null)
C (null) 7
D (null) (null)
SQLFiddle here (and note that in the fiddle I changed the variable name NUMBER to NUM to eliminate the need to double-quote it everywhere it's used).

Related

Merge query to distribute equal number of records between 100 users

I have two tables in Oracle, in first table I have 100 users and in second table I have 100000 records. I want to distribute equal amount of records between them.....
Instead of writing updating and using rownum <= 1000 to distribute data....I want to write merge statement that can divide equal number of records between 100 users.
Table 1
column A Column B column c
1 Pre 90008765
2 Pre 90008766 and so on like this
Table 2
column a Column B column C Column d
1 null null null
2 null null null
And so on will have 100000 records
and between these two tables column a will be common in which we can apply join condition..... please guide me with merge query
If I understand correctly these words "write merge statement that can divide equal number of records between 100 users", you want this:
merge into table2 tgt
using (
select tb.rwd, ta.a
from (select rownum rn, a, b, c, count(1) over () cnt from table1) ta
join (select rowid rwd, rownum rn, a, b, c, d from table2) tb
on mod(ta.rn, cnt) = mod(tb.rn, cnt)) src
on (tgt.rowid = src.rwd)
when matched then update set a = src.a
dbfiddle
This statement assigns rows from T1 to rows in T2 in sequence 1-2-3-...-1-2-3-..., using function mod(). Of course you can update other columns if you need, not only A.

select statement should return count as zero if no row return using group by clause

I have a table student_info, it has column "status", status can be P (present), A (absent), S (ill), T ( transfer), L (left).
I am looking for expected output as below.
status count(*)
P 12
S 1
A 2
T 0
L 0
But output is coming like as below:
Status Count(*)
P 12
S 1
A 2
we need rows against status T and L as well with count zero though no record exist in DB.
#mkuligowski's approach is close, but you need an outer join between the CTE providing all of the possible status values, and then you need to count the entries that actually match:
-- CTE to generate all possible status values
with stored_statuses (status) as (
select 'A' from dual
union all select 'L' from dual
union all select 'P' from dual
union all select 'S' from dual
union all select 'T' from dual
)
select ss.status, count(si.status)
from stored_statuses ss
left join student_info si on si.status = ss.status
group by ss.status;
STATUS COUNT(SI.STATUS)
------ ----------------
P 12
A 2
T 0
S 1
L 0
The CTE acts as a dummy table holding the five statuses you want to count. That is then outer joined to your real table - the outer join means the rows from the CTE are still included even if there is no match - and then the rows that are matched in your table are counted. That allows the zero counts to be included.
You could also do this with a collection:
select ss.status, count(si.status)
from (
select column_value as status from table(sys.odcivarchar2list('A','L','P','S','T'))
) ss
left join student_info si on si.status = ss.status
group by ss.status;
It would be preferable to have a physical table which holds those values (and their descriptions); you could also then have a primary/foreign key relationship to enforce the allowed values in your existing table.
If all the status values actually appear in your table, but you have a filter which happens to exclude all rows for some of them, then you could get the list of all (used) values from the table itself instead of hard-coding it.
If your initial query was something like this, with a completely made-up filter:
select si.status, count(*)
from student_info si
where si.some_condition = 'true'
group by si.status;
then you could use a subquery to get all the distinct values from the unfiltered table, outer join from that to the same table, and apply the filter as part of the outer join condition:
select ss.status, count(si.status)
from (
select distinct status from student_info
) ss
left join student_info si on si.status = ss.status
and si.some_condition = 'true'
group by ss.status;
It can't stay as a where clause (at least here, where it's applying to the right-hand-side of the outer join) because that would override the outer join and effectively turn it back into an inner join.
You should store somewhere your statuses (pherhaps in another table). Otherwise, you list them using subquery:
with stored_statuses as (
select 'P' code, 'present' description from dual
union all
select 'A' code, 'absent' description from dual
union all
select 'S' code, 'ill' description from dual
union all
select 'T' code, 'transfer' description from dual
union all
select 'L' code, 'left' description from dual
)
select ss.code, count(*) from student_info si
left join stored_statuses ss on ss.code = si.status
group by ss.code

I want a unique result for my query.It should return only one result

Ok let me try to give a breif and as simple as possible explanation for my issue.
I have two queries .
Query 1:-
select columnA,columnB,columnC,columnD...
from tablex,tabley ....
where (set of conditions)
GROUP BY .... (all non aggregate function columns)
ORDER BY ... (two columns lets say columnB and columnC)
Please note here that columnA is actually an aggregate function .
Query 2:-
select '',columnB,columnC,columnD...
from tablex,tabley ....
where (set of conditions)
GROUP BY .... (all non aggregate function columns)
ORDER BY ... (two columns lets say columnB and columnC)
Pleae note that columnA is deliberately kept blank.The columns B,C,D are the same as query 1 as well as the group by and order by clauses.
The where conditions in query 2 will have all the conditions related to the aggregation of column A omitted so this query WILL ALWAYS RETURN a result with column 'A' has blank for the same values of B,C,D.
I have done a union of the two queries as follows :-
QUERY2
UNION
QUERY1
GROUP BY...
ORDER BY...
Now here are some of the expected results:-
If any of the where conditions in query 1 fail(this will happen if it
finds that one of the tables in the where conditions doesn't have
data for a given UNIQUE ID)then query 1 does not return any result
in which case the results of query 2 are returned. This is perfectly
fine.
The where conditions in query 1 are all satisfied and we have one row returned. In this case query 2 will also return one row , therby resulting in the entire output having 2 rows ,one with a NON-NULL VALUE OF A followed by B,C,D and another row with a NULL VALUE of A followed by the same B,C,D values.Basically a duplicate row with only value of 'A' being different.
Scenario 2 is where I have a problem. I want a result where it only returns ONE ROW in scenario 2, i.e it should return the row with a NON-NULL value of A and discard the second row.
Is there any way I can have this done ?
I am using a Oracle12c database .
Would something like this be OK? q1 and q2 represent your current queries.
SQL> with
2 q1 as (select 1 a, 2 b, 3 c from dual union
3 select 10, 20, 30 from dual
4 ),
5 q2 as (select null a, 2 b, 3 c from dual union
6 select 10, 20, 30 from dual
7 ),
8 inter as (select a, b, c from q1
9 union
10 select a, b, c from q2
11 )
12 select max(a) a, b, c
13 from inter
14 group by b, c
15 order by 1, 2, 3;
A B C
---------- ---------- ----------
1 2 3
10 20 30
SQL>

OUTER JOIN from different column names to same column

I am trying to select columns from different tables (the columns have different name) and use an outer join to get them in a single table. How do I do this?
(I am using sqlplus)
Here is an example:
Table a:
a.NAME1 a.NAME2 a.RATING
Jack Sparrow 4
Table b:
b.FIRSTNAME b.LASTNAME b.RATING
Jack Sparrow 7
Table 3:
c.F_NAME c.L_NAME c.RATING
Jack Sparrow 6
I would like a table like this:
NAME RATING
Jack 4
7
6
I tried this code
SELECT
a.NAME1 AS NAME,
b.FIRSTNAME AS NAME,
c.F_NAME AS NAME,
a.RATING AS RATING,
b.RATING AS RATING,
c.RATING AS RATING
FROM a
FULL OUTER JOIN (b
CROSS JOIN c)
ON (a.NAME1 = b.FIRSTNAME
AND a.NAME1 = c.F_NAME);
But that didn't work. How do I go about achieving this?
It does not sound like you want to join the tables at all. If you joined three tables each with 1 row, you would end up with a result set that had a single row and many columns. Since your goal is to end up with three rows of data, you would want to use a union all
SELECT a.name1, a.rating
FROM a
UNION ALL
SELECT b.firstname, b.rating
FROM b
UNION ALL
SELECT c.f_name, c.rating
FROM c
If you want to eliminate duplicate rows, use a union rather than a union all.
select a.NAME1, a.NAME2, a.RATING, b.RATING, c.RATING
from a
left outer join b on b.FIRSTNAME = a.NAME1 and b.LASTNAME = a.NAME2
left outer join c on c.F_NAME = a.NAME1 and c.L_NAME = a.NAME2

Oracle query self-join?

I've simplified this example but hopefully the example provides enough substance to make sense.
If I have a table such as the following...
ITEM GROUP
---- -----
A 1
B 1
C 1
D 2
E 2
F 3
G 4
... and I am provided items A, B, D and F, I would like to contruct a query that will return those details along with the additional items in the associated groups, C and E.
It seems that I should be able to do some sort of inner join but I'm not clear on how it could be done. It would be best if this were done in a single query due to the constraints of the environment.
Thanks much!
If I understand you correctly, this would work.
SELECT item,
group_num
FROM table_name
WHERE grroup_num IN (SELECT group_num
FROM table_name
WHERE item IN ('A', 'B', 'D', 'F'))
You could also write it as an EXISTS
SELECT item,
group_num
FROM table_name a
WHERE EXISTS( SELECT 1
FROM table_name b
WHERE a.group_num = b.group_num
AND b.item IN ('A','B','D','F') )

Resources