Group Query for oracle - oracle

I have a scenario where I need to fetch all the records within an ID for the one source along with other source. Given below is my input set of records.
ID SOURCE CURR_FLAG TYPE
1 IBM Y P
1 IBM Y OF
1 IBM Y P
2 IBM Y P
2 TCS Y P
3 IBM NULL P
3 CTS NULL P
3 TCS NULL P
4 IBM NULL OF
4 CTS NULL OF
4 TCS Y ON
5 CTS NULL OF
5 TCS Y ON
From the above records, I need to select all the records with source as IBM within that same ID group and other source should be also there for the same ID along with IBM.Also, we need to fetch only those records where at least one record in that ID group with curr_fl='Y'
In the above scenario even though the ID=1 have a source as IBM, but there is no record in that particular group with other source.So we shouldn't fetch that record with that ID.
For the record ID=3 have a source as IBM along with other sources, but there is no record with CURR_FL='Y', my query should not fetch the value.In the case of ID=4, it can fetch all the records with ID=4, as one of the records have value='Y' and it have a combination of IBM with other source.For ID 5 it should not fetch as we dont have any IBM record source within that set
Also within the group which has satisfied the above condition, I need one more condition for type. if there are records with type='P', then I need to fetch only that record.If there are no records with P, then I will search for type='OF' else type='ON'
My Expected output is given below
ID SOURCE CURR_FLAG TYPE
2 IBM Y P
2 TCS Y P
4 IBM NULL OF
4 CTS NULL OF
4 TCS Y ON
I have written a query as given below.But it's running for long and not fetching any results. Is there any better way to modify this query
select
ID,
SOURCE,
CURR_FL,
TYPE
from TABLE a where
exists(select 1 from TABLE B where a.ID=B.ID and a.rowid<>B.rowid and B.source<>a.source)
and exists(select 1 from TABLE C where a.ID=C.ID and C.source ='IBM')
and exists(select 1 from TABLE D where a.ID=D.ID and D.CURR_FL='Y') and
(TYPE,ID) IN (
select case type when 1 then 'P' when 2 then 'OF' else 'ON' END TYPE,ID from
(select ID,
max(priority) keep (dense_rank first order by priority asc) as type
from ( select ID,TYPE,
case TYPE
when 'P' then 1
when 'OF' then 2
when 'ON' then 3
end as priority
from TABLE where ID
in(select ID from TABLE where CURR_FL='Y') AND SOURCE='IBM'
)
group by ID))

First, look for the ids. I would recommend:
select id
from t
group by id
having min(source) <> max(source) and -- at least two sources
sum(case when curr_flag = 'Y' then 1 else 0 end) > 0 and -- at least one Y
sum(case when source = 'IBM' then 1 else 0 end) > 0 -- IBM
To get the base rows, you can use in, exists, or join:
select t.*
from t
where id in (select id
from t
group by id
having min(source) <> max(source) and -- at least two sources
sum(case when curr_flag = 'Y' then 1 else 0 end) > 0 and -- at least one Y
sum(case when source = 'IBM' then 1 else 0 end) > 0 -- IBM
);

Using analytic functions you can check that there is at least one IBM in the group, at least one other source apart from IBM in the group, and at least one flag with Y in the group:
with t(id, source, curr_flag, type) as
(
select 1, 'IBM', 'Y', 'P' from dual union all
select 1, 'IBM', 'Y', 'OF' from dual union all
select 1, 'IBM', 'Y', 'P' from dual union all
select 2, 'IBM', 'Y', 'P' from dual union all
select 2, 'TCS', 'Y', 'P' from dual union all
select 3, 'IBM', NULL, 'P' from dual union all
select 3, 'CTS', NULL, 'P' from dual union all
select 3, 'TCS', NULL, 'P' from dual union all
select 4, 'IBM', NULL, 'OF' from dual union all
select 4, 'CTS', NULL, 'OF' from dual union all
select 4, 'TCS', 'Y', 'ON' from dual union all
select 5, 'CTS', NULL, 'OF' from dual union all
select 5, 'TCS', 'Y', 'ON' from dual
)
select id, source, curr_flag, type
from (select id, source, curr_flag, type,
max(case when source = 'IBM' then 1 end) over (partition by id) ibm,
max(case when source != 'IBM' then 1 end) over (partition by id) not_ibm,
max(case when curr_flag = 'Y' then 1 end) over (partition by id) flag_y
from t)
where ibm = 1
and not_ibm = 1
and flag_y = 1
order by id, source;

Related

I need 2 count columns in the same query in ORACLE

I'm trying to get the unique number of invoices a company has received and sent out using 2 count() functions. In invoices table there are two columns that are references to the same company id (one is id of a company that is sending an invoice and the other one is id of a company that is receiving an invoice)
This is the code I tried using:
SELECT K.ID,K.NAME,K.CITY, COUNT(*) AS NUM_OF_INVOICES_SENT, COUNT(*) AS NUM_OF_INVOICES_RECEIVED
FROM COMPANY K LEFT JOIN INVOICE F ON F.COMP_SNEDING = K.ID
GROUP BY K.NAME,K.ID,K.CITY
This is for a school project so I am in no means well versed in sql/oracle
actual data invoices:
actual data company:
desired outcome with given actual data:
Here's one option; it doesn't use count, but sum with case expression.
Sample data:
SQL> with
2 invoice (id, amount, comp_sending, comp_receiving) as
3 (select 1, 2000 , 1, 2 from dual union all
4 select 2, 28250, 3, 2 from dual union all
5 select 3, 8700 , 4, 1 from dual union all
6 select 4, 20200, 5, 3 from dual union all
7 select 5, 21500, 3, 4 from dual
8 ),
9 company (id, name, city, state) as
10 (select 1, 'Microsoft', 'Redmond' , 'Washington' from dual union all
11 select 2, 'Ubisoft' , 'Paris' , 'France' from dual union all
12 select 4, 'Starbucks', 'Seattle' , 'Washington' from dual union all
13 select 5, 'Apple' , 'Cupertino', 'California' from dual union all
14 select 3, 'Nvidia' , 'Cupertino', 'California' from dual
15 )
Query begins here:
16 select c.id, c.name,
17 sum(case when c.id = i.comp_sending then 1 else 0 end) cnt_sent,
18 sum(case when c.id = i.comp_receiving then 1 else 0 end) cnt_received
19 from company c left join invoice i on c.id in (i.comp_sending, i.comp_receiving)
20 group by c.id, c.name
21 order by c.id;
ID NAME CNT_SENT CNT_RECEIVED
---------- --------- ---------- ------------
1 Microsoft 1 1
2 Ubisoft 0 2
3 Nvidia 2 1
4 Starbucks 1 1
5 Apple 1 0
SQL>
You can use COUNT if you replace the 0 in the CASE expressions with NULL. So #Littlefoot's query becomes
select c.id, c.name,
COUNT(case when c.id = i.comp_sending then 1 else NULL end) cnt_sent,
COUNT(case when c.id = i.comp_receiving then 1 else NULL end) cnt_received
from company c left join invoice i on c.id in (i.comp_sending, i.comp_receiving)
group by c.id, c.name
order by c.id;
This works because COUNT counts only those rows which have a non-NULL value in the expression which is being counted.
db<>fiddle here

Oracle query to keep looking until value is not 0 anymore

I am using Oracle 11.
I have 2 tables
TblA with columns id, entity_id and effective_date.
TblADetail with columns id and value.
If Value = 0 for the effective date, I want to keep looking for the next effective date until I found value <> 0 anymore.
The below query only look for value on 3/10/21.
If value = 0, I want to look for value on 3/11/21. If that's not 0, I want to stop.
But, if that's 0, I want to look for value on 3/12/21. If that's not 0, I want to stop.
But, if that's 0, I want to keep looking until value is not 0.
How can I do that ?
SELECT SUM(pd.VALUE)
FROM TblA p,TblADetail pd
WHERE p.id = pd.id
AND p.effective_date = to_date('03/10/2021','MM/DD/YYYY')
AND TRIM (p.entity_id) = 123
Sample data:
TblA
id entity_id effective_date
1 123 3/10/21
2 123 3/11/21
3 123 3/12/21
TblADetail
id value
1 -136
1 136
2 2000
3 3000
In the above data, for entity_id 123, starting from effective_date 3/10/21, I would like to to return value 2000 (from TblADetail) effective_date 3/11/21.
So, starting from a certain date, I want the results from the minimum date that has non-zero values.
Thank you.
You can do what you need to do by grouping the sum on the effective date, and using the MIN analytic function to find the earliest date. Once you've done that, you simply need to select the date that matches the earliest date.
E.g.:
with tbla as (select 1 id, ' 123' entity_id, to_date('10/03/2021', 'dd/mm/yyyy') effective_date from dual union all
select 2 id, ' 123' entity_id, to_date('11/03/2021', 'dd/mm/yyyy') effective_date from dual union all
select 3 id, ' 123' entity_id, to_date('12/03/2021', 'dd/mm/yyyy') effective_date from dual),
tbla_detail as (select 1 id, -136 value from dual union all
select 1 id, 136 value from dual union all
select 2 id, 2000 value from dual union all
select 3 id, 3000 value from dual),
results as (select a.effective_date,
sum(ad.value) sum_value,
min(case when sum(ad.value) != 0 then a.effective_date end) over () min_effective_date
from tbla a
inner join tbla_detail ad on a.id = ad.id
where a.effective_date >= to_date('10/03/2021', 'dd/mm/yyyy')
and trim(a.entity_id) = '123'
group by a.effective_date)
select sum_value
from results
where effective_date = min_effective_date;
SUM_VALUE
----------
2000
Straightforward; read comments within code. Sample data in lines #1 - 13, query begins at line #14.
SQL> with
2 -- sample data
3 tbla (id, entity_id, effective_date) as
4 (select 1, 123, date '2021-03-10' from dual union all
5 select 2, 123, date '2021-03-11' from dual union all
6 select 3, 123, date '2021-03-12' from dual
7 ),
8 tblb (id, value) as
9 (select 1, -136 from dual union all
10 select 1, 136 from dual union all
11 select 2, 2000 from dual union all
12 select 3, 3000 from dual
13 ),
14 tblb_temp as
15 -- simple grouping per ID
16 (select id, sum(value) value
17 from tblb
18 group by id
19 )
20 -- return TBLA values whose ID equals TBLB_TEMP's minimum ID
21 -- whose value isn't zero
22 select a.id, a.entity_id, a.effective_date
23 from tbla a
24 where a.id = (select min(b.id)
25 from tblb_temp b
26 where b.value > 0
27 );
ID ENTITY_ID EFFECTIVE_
---------- ---------- ----------
2 123 03/11/2021
SQL>

Oracle Select statement without using listagg

DB : Oracle 11G
Below is a query subset result in which I want to fetch only ID =1, also i want to display if this value is there in the result
NAME = 'S123', if its there return the output as an indicator in the query result
ID, NAME,....(other columns)
1, 'I123',...
3, 'S123',...
4, 'W123',...
6, 'C123',...
....,
....,
eg., output
ID, NAME , value_present_ind('S123')
1, 'I123', 'Y'
without using sub-queries on the select statement
without using listagg
WITH demo_data AS ( SELECT 1 AS ID, 'I123' AS NAME FROM DUAL UNION ALL
SELECT 3 AS ID, 'S123' AS NAME FROM DUAL UNION ALL
SELECT 4 AS ID, 'W123' AS NAME FROM DUAL UNION ALL
SELECT 5 AS ID, 'C123' AS NAME FROM DUAL )
SELECT MAX(CASE WHEN id = 1 THEN id ELSE 0 END) AS id,
MAX(CASE WHEN id = 1 THEN name ELSE NULL END) AS name,
MAX(CASE WHEN name = 'I123' THEN 'Y' ELSE 'N' END) AS value_present_ind
FROM demo_data;

Oracle Query to get max of date_column and if null present in that column, then that row should be returned

I had a situation like to read max(end_time_) when delete_reason_ != 'deleted', but when end_time_ has null, the query should return 2nd row only.
SELECT MAX(END_TIME_) FROM TASK_HISTORY WHERE DELETE_REASON_ != 'deleted'
is returning me 1st Row. But, my desired result should return 2nd row. As per Oracle documentation, Aggregate functions like max, sum, min shouldn't consider null values unlike Count.
Is there a way to get the Null value as max if null present, otherwise, max(end_time_) should be my desired output.
Any help will be appreciated.
thank you.
If I understood you correctly, here's one option:
SQL> alter session set nls_date_format = 'dd.mm.yyyy';
Session altered.
SQL> with test (end_time_, delete_reason_) as
2 (select date '2017-12-13', 'completed' from dual union
3 select null, null from dual union
4 select date '2017-12-12', 'deleted' from dual union
5 select date '2018-01-05', 'unknown' from dual
6 ),
7 inter as
8 (select row_number() over (order by end_time_ desc) rn,
9 end_time_, delete_reason_
10 from test
11 where nvl(delete_reason_, 'x') <> 'deleted'
12 )
13 select end_time_ From inter
14 where rn = 1;
END_TIME_
----------
NULL
SQL>
SQL> with test (end_time_, delete_reason_) as
2 (select date '2017-12-13', 'completed' from dual union
3 -- select null, null from dual union
4 select date '2017-12-12', 'deleted' from dual union
5 select date '2018-01-05', 'unknown' from dual
6 ),
7 inter as
8 (select row_number() over (order by end_time_ desc) rn,
9 end_time_, delete_reason_
10 from test
11 where nvl(delete_reason_, 'x') <> 'deleted'
12 )
13 select end_time_ From inter
14 where rn = 1;
END_TIME_
----------
05.01.2018
SQL>
Finally I got a query something like this to achieve null value as result instead of date when I use max() function.
SELECT task_def_key_
, CASE WHEN MAX(CASE WHEN end_time_ IS NULL THEN 1 ELSE 0 END) = 0 THEN MAX(end_time_) END
FROM TASK_HISTORY WHERE DELETE_REASON_ != 'deleted'
GROUP BY task_def_key_
select * from (select * from TASK_HISTORY where DELETE_REASON_ != 'deleted' order by end_time desc) where rownum=1
This will work for your problem

Oracle 9.2 pivot distinct value

The pivot function is available from Oracle 11 and i will need similar result using Oracle 9.2.
The main argument is that i need to pivot some values with a distinct result in a table like this:
id col3
1 a
1 b
--
2 a
2 a
2 b
--
3 a
3 b
3 c
My result sould be something like this
id a b c
1 1 1 0
2 1 1 0
3 1 1 1
To create a "manual" pivot i'm using a case/when but I am not able to understand how to get distinct value.
Right now the query is this:
with t as
( select 1 as id, 'a' as col1 from dual union all
select 1 as id, 'b' from dual union all
select 2 as id, 'a' from dual union all
select 2 as id, 'a' from dual union all
select 2 as id, 'b' from dual union all
select 3 as id, 'a' from dual union all
select 3 as id, 'b' from dual union all
select 3 as id, 'c' from dual
)
select t.id,
count(case when t.col1 = 'a' then 1 end) a,
count(case when t.col1 = 'b' then 1 end) b,
count(case when t.col1 = 'c' then 1 end) c
This produce correct value but obviously it just "count" the total a/b/c value and not the distinct.
thanks for the support
If I correctly understand your need, you can try something like the following; it aggregates by id and counts the distinct values of col3:
with t as
( select 1 as id, 'a' as col1 from dual union all
select 1 as id, 'b' from dual union all
select 2 as id, 'a' from dual union all
select 2 as id, 'a' from dual union all
select 2 as id, 'b' from dual union all
select 3 as id, 'a' from dual union all
select 3 as id, 'b' from dual union all
select 3 as id, 'c' from dual
)
select id,
count(distinct decode (col1, 'a', id, null)) a,
count(distinct decode (col1, 'b', id, null)) b,
count(distinct decode (col1, 'c', id, null)) c
from t
group by id
Of course the query depends on the number of different values of col3, but this is the same problem than pivot.

Resources