oracle SQL select/join with many-to-one and nulls - sql

I'm having trouble crafting an Oracle SQL query that returns the results I seek. It's possible that what I'm trying to do isn't possible.
For a given code in table one, if the code exists in table two and ANY of the flags are "1", then the status should be "1" in the query results. Otherwise, the status should be "0". If the code doesn't exist at all in table 2, then the status should be null.
tab1
------------
id,code
------------
1,ABC
2,DEF
3,GHI
4,JKL
5,MNO
6,PQR
7,STU
tab2
------------
id,code,flag
------------
1,ABC,0
2,ABC,0
3,DEF,1
4,DEF,1
5,GHI,0
6,GHI,1
7,JKL,1
8,JKL,0
9,MNO,0
10,PQR,1
(query?)
result
------------
id,code,status
------------
1,ABC,0
2,DEF,1
3,GHI,1
4,JKL,1
5,MNO,0
6,PQR,1
7,STU,null
So far, the only query I've been able to come up with is this, which doesn't give the right results in the status column...
select tab1.*, (select * from (
select flag from tab2 where tab2.code = code order by flag desc)
where rownum <=1) as status from tab1;
... status is always "1", which is incorrect.
I'm thinking that instead of using order by and selecting the first result, it might be possible to instead count the number of "1" flags for each code, but I'm not sure if that would work.

My first inclination is to use a subselect:
select t1.*,
(select max(t2.flag)
from table2 t2
where t2.code = t1.code
) as t2flag
from table1 t1;
You can also phrase this as a left join with an aggregation:
select t1.*, t2.flag
(select max(t2.flag)
from table2 t2
where t2.code = t1.code
) as t2flag
from table1 t1 left join
(select t2.code, max(t2.flag) as flag
from table2 t2
group by t2.code
) t2
on t2.code = t1.code;
Both these methods are assuming that flag is either 0 or 1, as in your question.

I'd use a LEFT JOIN:
select t1.id, max(t1.code) code, max(t2.flag) status
from table1 t1
left join table2 t2 on t1.code=t2.code
group by t1.id

Assuming that 0 and 1 are the only two flags, what about something like:
with tab1 as (select 1 id, 'ABC' code from dual union all
select 2 id, 'DEF' code from dual union all
select 3 id, 'GHI' code from dual union all
select 4 id, 'JKL' code from dual union all
select 5 id, 'MNO' code from dual union all
select 6 id, 'PQR' code from dual union all
select 7 id, 'STU' code from dual),
tab2 as (select 1 id, 'ABC' code, 0 flag from dual union all
select 2 id, 'ABC' code, 0 flag from dual union all
select 3 id, 'DEF' code, 1 flag from dual union all
select 4 id, 'DEF' code, 1 flag from dual union all
select 5 id, 'GHI' code, 0 flag from dual union all
select 6 id, 'GHI' code, 1 flag from dual union all
select 7 id, 'JKL' code, 1 flag from dual union all
select 8 id, 'JKL' code, 0 flag from dual union all
select 9 id, 'MNO' code, 0 flag from dual union all
select 10 id, 'PQR' code, 1 flag from dual)
select t1.id,
t1.code,
t2.flag status
from tab1 t1
left join (select code,
max(flag) flag
from tab2
group by code) t2
on (t1.code = t2.code);
ID CODE STATUS
---------- ---- ----------
5 MNO 0
6 PQR 1
2 DEF 1
1 ABC 0
3 GHI 1
4 JKL 1
7 STU

Maybe I'm missing something, but it seems to be as simple as this:
select tab1.id, tab1.code, max(tab2.flag) status
from tab1
left join tab2 on tab1.code = tab2.code
group by tab1.id, tab1.code
order by tab1.id;
A sample SQL Fiddle gives the desired result:
| id | code | status |
|----|------|--------|
| 1 | ABC | 0 |
| 2 | DEF | 1 |
| 3 | GHI | 1 |
| 4 | JKL | 1 |
| 5 | MNO | 0 |
| 6 | PQR | 1 |
| 7 | STU | (null) |

SELECT ID,
CODE,
MAX( STATUS)
FROM
(SELECT DISTINCT T1.ID,
T1.CODE,
FLAG AS STATUS
FROM T1
LEFT JOIN T2
ON T1.CODE = T2.CODE
)
GROUP BY ID,
CODE;

This gives result as you asked:
SELECT tab1.id,tab1.code,max(tab2.flag) as Status
FROM tab1 LEFT JOIN tab2
ON tab1.code=tab2.code
GROUP BY tab2.code
ORDER BY tab1.id;

Related

Dates between date1 and date2 are not working in cross joining

Hi I am trying to filter data with dates between but it insert null 0 rows created its cross joining table I am not aware is there problem due to cross joining or something else. There is no error message
without dates it works fine any solution Please
insert into PAY_IN_OUT2 (EMP_CODE, DATE_IN, DATE_OUT, ATT_DATE, DATE_INA, DATE_OUTA, DATE_INB, DATE_OUTB, DATE_INC, DATE_OUTC, ATT_PRESENT)
select a.EMPLOYEE_ID1, b.DT, b.DT1, B.ATT_DT, B.DT3, B.DT4, B.DT4, B.DT5, B.DT5, B.DT3, 'P'
from CALENDAR_DATES4 b cross join EMPLOYEES a
WHERE A.EMPLOYEE_ID1 BETWEEN 70001 AND 70009
AND B.ATT_DT BETWEEN TO_DATE('10/02/2021', 'DD/MM/YYYY') AND TO_DATE('20/02/2021', 'DD/MM/YYYY') ;
without this its working fine but I have to filter this with dates AND B.ATT_DT BETWEEN TO_DATE('10/02/2021', 'DD/MM/YYYY') AND TO_DATE('20/02/2021', 'DD/MM/YYYY')
If you're cross joining two tables and filtering on each table, and you end up selecting 0 rows, then at least one of your predicates is causing no rows to be returned.
For example, here's a set of queries demonstrating some different results, depending on what the predicates cause to be returned:
WITH t1 AS (SELECT 1 ID, 10 val FROM dual UNION ALL
SELECT 2 ID, 20 val FROM dual UNION ALL
SELECT 3 ID, 30 val FROM dual UNION ALL
SELECT 4 ID, 40 val FROM dual UNION ALL
SELECT 5 ID, 50 val FROM dual),
t2 AS (SELECT 'A' col1, 'AA' col2 FROM dual UNION ALL
SELECT 'B' col1, 'BA' col2 FROM dual UNION ALL
SELECT 'C' col1, 'AC' col2 FROM dual UNION ALL
SELECT 'D' col1, 'DD' col2 FROM dual)
SELECT 1 case_no, -- case 1: no rows expected to be returned (no t1 rows with an id between 6 and 10)
t1.*,
t2.*
FROM t1
CROSS JOIN t2
WHERE t1.id BETWEEN 6 AND 10
AND t2.col2 LIKE 'A%'
UNION ALL
SELECT 2 case_no, -- case 2: no rows expected to be returned (no t2 rows with a col2 starting with "C")
t1.*,
t2.*
FROM t1
CROSS JOIN t2
WHERE t1.id BETWEEN 2 AND 4
AND t2.col2 LIKE 'C%'
UNION ALL
SELECT 3 case_no, -- case 3: no rows expected to be returned (no t1 rows with an id between 6 and 10, and no t2 rows with a col2 starting with "C")
t1.*,
t2.*
FROM t1
CROSS JOIN t2
WHERE t1.id BETWEEN 6 AND 10
AND t2.col2 LIKE 'C%'
UNION ALL
SELECT 4 case_no, -- case 4: 6 rows expected to be returned (3 t1 rows with an id between 2 and 4, and 2 t2 rows with a col2 starting with "A"; 3 x 2 = 6)
t1.*,
t2.*
FROM t1
CROSS JOIN t2
WHERE t1.id BETWEEN 2 AND 4
AND t2.col2 LIKE 'A%';
CASE_NO ID VAL COL1 COL2
---------- ---------- ---------- ---- ----
4 2 20 A AA
4 3 30 A AA
4 4 40 A AA
4 2 20 C AC
4 3 30 C AC
4 4 40 C AC
You can see that only case 4 (rows in both tables match the predicates) has any rows returned by the cross join.
ETA: if you were wanting to see employee rows regardless of whether there are matching date rows or not, you would need to use an outer join, e.g. using my earlier example, case 2 would become:
WITH t1 AS (SELECT 1 ID, 10 val FROM dual UNION ALL
SELECT 2 ID, 20 val FROM dual UNION ALL
SELECT 3 ID, 30 val FROM dual UNION ALL
SELECT 4 ID, 40 val FROM dual UNION ALL
SELECT 5 ID, 50 val FROM dual),
t2 AS (SELECT 'A' col1, 'AA' col2 FROM dual UNION ALL
SELECT 'B' col1, 'BA' col2 FROM dual UNION ALL
SELECT 'C' col1, 'AC' col2 FROM dual UNION ALL
SELECT 'D' col1, 'DD' col2 FROM dual)
SELECT t1.*,
t2.*
FROM t1
LEFT OUTER JOIN t2 ON t2.col2 LIKE 'C%'
WHERE t1.id BETWEEN 2 AND 4;
ID VAL COL1 COL2
---------- ---------- ---- ----
2 20
3 30
4 40

How to force LEFT OUTER JOIN return NULL or empty?

SELECT table1.id, table2.name
FROM table1
LEFT JOIN table2
ON table1.id = table2.id;
so if table1.id = NULL I want to return NULL or empty string from table2, i.e.
ID, NAME
1 name1
2 name2
null null
3 name3
but now getting "column ambiguously defined" error since table2 has many NULLs in ID column
It does return what you want, without any errors:
SELECT table1.id, table2.name
FROM table1
LEFT JOIN table2
ON table1.id = table2.id;
Which, for the sample data:
CREATE TABLE table1 ( id ) AS
SELECT 1 FROM DUAL UNION ALL
SELECT 2 FROM DUAL UNION ALL
SELECT NULL FROM DUAL UNION ALL
SELECT 3 FROM DUAL;
CREATE TABLE table2 ( id, name ) AS
SELECT 1, 'name1' FROM DUAL UNION ALL
SELECT 2, 'name2' FROM DUAL UNION ALL
SELECT 3, 'name3' FROM DUAL;
Outputs:
ID | NAME
---: | :----
1 | name1
2 | name2
3 | name3
null | null
db<>fiddle here

How to select first x records from second table

I would like to get all records from first table and only x records from second table.
How many records from second table I have info in first table :
My tables are
table1 :
WITH table1(a,b) AS
(
SELECT 'aa',3 FROM dual UNION ALL
SELECT 'bb',2 FROM dual UNION ALL
SELECT 'cc',4 FROM dual
)
SELECT *
FROM table1;
a | b (number of records from table2 (x))
------
aa | 3
bb | 2
cc | 4
table2 :
WITH table2(a,b) AS
(
SELECT 'aa','1xx' FROM dual UNION ALL
SELECT 'aa','2yy' FROM dual UNION ALL
SELECT 'aa','3ww' FROM dual UNION ALL
SELECT 'aa','4zz' FROM dual UNION ALL
SELECT 'aa','5qq' FROM dual UNION ALL
SELECT 'bb','1aa' FROM dual UNION ALL
SELECT 'bb','2bb' FROM dual UNION ALL
SELECT 'bb','3cc' FROM dual UNION ALL
SELECT 'cc','1oo' FROM dual UNION ALL
SELECT 'cc','2uu' FROM dual UNION ALL
SELECT 'cc','3tt' FROM dual UNION ALL
SELECT 'cc','4zz' FROM dual UNION ALL
SELECT 'cc','5rr' FROM dual
)
SELECT *
FROM table2;
a | b
--------
aa | 1xx
aa | 2yy
aa | 3ww
aa | 4zz
aa | 5qq
bb | 1aa
bb | 2bb
bb | 3cc
bb | 4dd
bb | 5ee
cc | 1oo
cc | 2uu
cc | 3tt
cc | 4zz
cc | 5rr
Expected Result:
a | b
--------
aa | 1xx
aa | 2yy
aa | 3ww
bb | 1aa
bb | 2bb
cc | 1oo
cc | 2uu
cc | 3tt
cc | 4zz
You can use ROW_NUMBER() analytic function with LEFT/RIGHT OUTER JOIN among the tables :
WITH t2 AS
(
SELECT t2.a,t2.b, ROW_NUMBER() OVER (PARTITION BY t2.a ORDER BY t2.b) AS rn
FROM table2 t2
)
SELECT t2.a, t2.b
FROM t2
LEFT JOIN table1 t1
ON t1.a = t2.a
WHERE rn <= t1.b
Demo
You need to write something like:
SELECT a,
b
FROM Table2 T,
( SELECT LEVEL L FROM DUAL
CONNECT BY LEVEL <= (SELECT MAX(b) FROM Table1)
) A
WHERE T.b>= A.L
ORDER BY T.a;
Ideally you should have a ordering column in table2. When you say first X rows it does not make any sense unless you have something like an id or date field to order the records.
Anyway, assuming the number part of the column b in table 2 for ordering and assuming the number would be followed by 2 characters only such as xx,yy etc you can use the logic below
Select Tb1.a, Tb1.b
from
(Select t.*, row_number() over (partition by a order by substr(b,1,length(b)-2)) as seq
from Table2 t) Tb1
join Table1 Tb2
on Tb1.a = Tb2.a
Where Tb1.seq <= Tb2.b;
Demo - https://dbfiddle.uk/?rdbms=oracle_11.2&fiddle=3030b2372bcbb007606bbb6481af9884
Again it's just a job for lateral:
WITH prep AS
(
SELECT *
FROM tab1,
LATERAL
(
SELECT LEVEL AS lvl
FROM dual
CONNECT BY LEVEL <= b
)
)
SELECT p.a, t2.b
FROM prep p
JOIN tab2 t2
ON p.lvl = regexp_substr(t2.b,'^\d+')
AND p.a = t2.a
ORDER BY p.a, p.lvl

How do I need to change my sql to get what I want in this case?

I have a table like following:
id value date
1 5 2015-01-10
2 5 2015-06-13
3 5 2015-09-05
4 11 2015-02-11
5 11 2015-01-10
6 11 2015-01-25
As can be seen, every value appears 3 times with different date. I want to write a query that returns the unique values that has the maximum date, which would be the following for the above table:
id value date
3 5 2015-09-05
4 11 2015-02-11
How could I do it?
This is the updated question:
The real question I am encountering is a little bit more complicated than the simplified version above. I thought I can move a step further once I know the answer to the simplified version, but I guest I was wrong. So, I am updating the question herein.
I have 2 tables like following:
Table 1
id id2 date
1 2 2015-01-10
2 5 2015-06-13
3 9 2015-09-05
4 10 2015-02-11
5 26 2015-01-10
6 65 2015-01-25
Table 2
id id2 data
1 2 A
2 5 A
3 9 A
4 10 B
5 26 B
6 65 B
Here, Table 1 and Table 2 are joined by id2
What I want to get is two records as follows:
id2 date data
9 2015-01-10 A
10 2015-02-11 B
You can use row_number to select the rows with the greatest date per value
select * from (
select t2.id2, t1.date, t2.data,
row_number() over (partition by t2.data order by t1.date desc) rn
from table1 t1
join table2 t2 on t1.id = t2.id2
) t where rn = 1
select a.id, a.value, a.date
from mytable a,
( select id, max(date) maxdate
from mytable b
group by id) b
where a.id = b.id
and a.date = b.maxdate;
Oracle Setup:
CREATE TABLE Table1 ( id, id2, "date" ) AS
SELECT 1, 2, DATE '2015-01-10' FROM DUAL UNION ALL
SELECT 2, 5, DATE '2015-06-13' FROM DUAL UNION ALL
SELECT 3, 9, DATE '2015-09-05' FROM DUAL UNION ALL
SELECT 4, 10, DATE '2015-02-11' FROM DUAL UNION ALL
SELECT 5, 26, DATE '2015-01-10' FROM DUAL UNION ALL
SELECT 6, 65, DATE '2015-01-25' FROM DUAL;
CREATE TABLE Table2 ( id, id2, data ) AS
SELECT 1, 2, 'A' FROM DUAL UNION ALL
SELECT 2, 5, 'A' FROM DUAL UNION ALL
SELECT 3, 9, 'A' FROM DUAL UNION ALL
SELECT 4, 10, 'B' FROM DUAL UNION ALL
SELECT 5, 26, 'B' FROM DUAL UNION ALL
SELECT 6, 65, 'B' FROM DUAL;
Query:
SELECT MAX( t1.id ) KEEP ( DENSE_RANK LAST ORDER BY t1."date" ) AS id,
MAX( t1.id2 ) KEEP ( DENSE_RANK LAST ORDER BY t1."date" ) AS id2,
MAX( t1."date" ) AS "date",
t2.data
FROM Table1 t1
INNER JOIN
Table2 t2
ON ( t1.id = t2.id AND t1.id2 = t2.id2 )
GROUP BY t2.data
Output:
ID ID2 date DATA
---------- ---------- ------------------- ----
3 9 2015-09-05 00:00:00 A
4 10 2015-02-11 00:00:00 B
Query 2:
SELECT id,
id2,
"date",
data
FROM (
SELECT t1.*,
t2.data,
ROW_NUMBER() OVER ( PARTITION BY t2.data ORDER BY t1."date" DESC ) AS rn
FROM Table1 t1
INNER JOIN
Table2 t2
ON ( t1.id = t2.id AND t1.id2 = t2.id2 )
)
WHERE rn = 1;
Output:
ID ID2 date DATA
---------- ---------- ------------------- ----
3 9 2015-09-05 00:00:00 A
4 10 2015-02-11 00:00:00 B

How to join oracle tables

I have two tables.
Table 1
ID STRING
1 ABC
2 CDE
3 FGH
Table 2
ID STRING
1 xyz
2 uvw
4 abc
I want the output as
ID STRING STRING2
1 ABC xyz
2 CDE uvw
3 FGH null
4 null abc
which join should I use. Is it possible to do this in simple SQL query?
with
t1 as
(select 1 id, 'ABC' string from dual
union
select 2, 'CDE' from dual
union
select 3, 'FGH' from dual
),
t2 as
(select 1 id, 'xyz' string from dual
union
select 2, 'uvw' from dual
union
select 4, 'abc' from dual)
select COALESCE(t1.id,t2.id) id, t1.string, t2.string string2
from t1 full outer join t2 on (t1.id = t2.id)
order by 1
What you can do is use Union to combine two different result sets. That will give you exactly what you're looking for:
SELECT tab1.ID,
tab1.name,
tab2.name2
FROM tab1 tab1
LEFT JOIN tab2 tab2 ON tab1.ID = tab2.ID
UNION
SELECT tab2.ID,
tab1.name,
tab2.name2
FROM tab1 tab1
RIGHT JOIN tab2 tab2 ON tab1.ID = tab2.ID
You can see that here-> http://sqlfiddle.com/#!4/cf9e2/10
Hope this helps!!!
I guess a full join would be correct
select * from tab1 t1 full join tab2 t2 on t1.id = t2.id