SQL - Select only when all data done - sql

So, I have data in the one-to-many table:
id user_id type status
1 1 A Done
2 1 B Done
3 1 C Done
4 1 D Ready
5 2 A Done
6 2 B Ready
7 2 C Done
8 2 D Ready
What's possible way to select all of D when status Ready and all of A,B,C wth the same user_id status is Done
If one of A, B, C with the same user_id status is not 'Done', then it is not selected.
I'm stuck on the WHERE function
SELECT * FROM table WHERE type = 'D' and status = 'Ready' and (A,B,C with the same user_id is Done)

You can use NOT EXISTS in a correlated subquery.
SELECT *
FROM table AS t1
WHERE type = 'D'
AND status = 'Ready'
AND NOT EXISTS (
SELECT 1
FROM table AS t2
WHERE type IN ('A', 'B', 'C')
AND t1.user_id = t2.user_id
AND status != 'Done'
)

You could use window functions, if your database supports them:
select id, user_id, type, status
from (
select
t.*,
max(case when type <> 'D' and status <> 'Done' then 1 else 0 end)
over(partition by user_id) flag
from mytable t
) t
where type = 'D' and status = 'Ready' and flag = 0

Related

query all of the same id based on multiple true conditions

I have this table :
id act st_dt end_dt stats
1 a 01/01/20 05/01/20 done
1 b 04/01/20 09/02/20 done
1 c 09/02/20 null not done
1 d 09/02/20 09/02/20 done
2 a 09/03/19 14/05/20 done
2 b 09/02/20 25/06/20 done
2 c 01/03/20 22/03/20 done
2 d 09/02/20 null not done
3 a 11/05/20 13/09/19 done
3 b 09/02/20 04/07/20 done
3 c 01/02/20 30/02/20 done
3 d 11/02/20 24/02/20 done
I want to query all activities 'act' of the same ID having activity a >= 01/01/20 and activity d status is done,
so the result should look like this:
id act st_dt end_dt stats
1 a 01/01/20 05/01/20 done
1 b 04/01/20 09/02/20 done
1 c 09/02/20 null not done
1 d 09/02/20 09/02/20 done
the two conditions are met for this id, i did this :
select * from
(
select
a.* ,
case when (act = 'a' and end_dt > to_date('01/01/20','dd/mm/yy')) and (act = 'd' and status = 'done') then 1 end) flag
from
table a
)
where flag = 1;
but it won't do the required,it'll query only activity 'a' and 'd'
You can use exists:
select a.*
from a
where exists (select 1
from a a2
where a2.id = a.id and a2.act = 'a' and a2.st_dt <= date '2020-01-01'
) and
exists (select 1
from a a2
where a2.id = a.id and a2.act = 'd' and a2.stats = 'done'
) ;
If you like to learn something new:
Creation of your table:
create table tab as
with t (id,act, start_date, end_date, status) as
(
select 1,'a',to_date('01/01/2020','dd/mm/yyyy'), to_date('05/01/2020','dd/mm/yyyy'), 'done' from dual union all
select 1,'b',to_date('04/01/2020','dd/mm/yyyy'), to_date('09/02/2020','dd/mm/yyyy'), 'done' from dual union all
select 1,'c',to_date('09/02/2020','dd/mm/yyyy'), null , 'not done' from dual union all
select 1,'d',to_date('09/02/2020','dd/mm/yyyy'), to_date('09/02/2020','dd/mm/yyyy'), 'done' from dual union all
select 2,'a',to_date('09/03/2019','dd/mm/yyyy'), to_date('14/05/2020','dd/mm/yyyy'), 'done' from dual union all
select 2,'b',to_date('09/02/2020','dd/mm/yyyy'), to_date('25/06/2020','dd/mm/yyyy'), 'done' from dual union all
select 2,'c',to_date('01/03/2020','dd/mm/yyyy'), to_date('22/03/2020','dd/mm/yyyy'), 'done' from dual union all
select 2,'d',to_date('09/02/2020','dd/mm/yyyy'), null , 'not done' from dual union all
select 3,'a',to_date('11/05/2020','dd/mm/yyyy'), to_date('13/09/2019','dd/mm/yyyy'), 'done' from dual union all
select 3,'b',to_date('09/02/2020','dd/mm/yyyy'), to_date('04/07/2020','dd/mm/yyyy'), 'done' from dual union all
select 3,'c',to_date('01/02/2020','dd/mm/yyyy'), to_date('29/02/2020','dd/mm/yyyy'), 'done' from dual union all --End_date is wrong in your input data I changed it to 29
select 3,'d',to_date('11/02/2020','dd/mm/yyyy'), to_date('24/02/2020','dd/mm/yyyy'), 'done' from dual
)
select * from t
Here is the solution that works on 12c and later
select * from tab
match_recognize
(
partition by id
order by start_date
all rows per match
pattern (a random_rows* d)
define a as act = 'a' and a.start_date >= date'2020-01-01',
d as act = 'd' and d.status = 'done'
);
One option would be using aggregated conditionals through an analytic function to determine whether both conditions are satisfied at the same time :
WITH a2 AS
(
SELECT a.*, SUM(CASE WHEN act = 'a' AND end_dt >= date'2020-01-01'
THEN 1
ELSE 0
END)
OVER(PARTITION BY id) *
SUM(CASE WHEN act = 'd' AND stats = 'done'
THEN 1
ELSE 0
END)
OVER(PARTITION BY id) AS satisfies
FROM a
)
SELECT id, act, st_dt, end_dt, stats
FROM a2
WHERE satisfies = 1
Demo
First get in two subqueries the ID of the well started and well done jobs.
INTERSECT the results to get jobs with both conditions and use this IDset in the IN predicate
Query
select *
from tab a
where id in (
select id
from tab a
where (act = 'a' and st_dt >= to_date('01/01/20','dd/mm/yy'))
INTERSECT
select id
from tab a
where (act = 'd' and stats = 'done')
)

SQL Assign Custom values to those rows with similar IDs

|id|last|
|2 |NULL|
|2 |2018|
|3 |NULL|
|3 |NULL|
|4 |2011|
|4 |2013|
This is what my current table looks like. A new 'status' column is to be created for each 'id' that must have the below 3 values.
1 - If Similar id and only one NULL value
2 - If Similar id and no NULL value
0 - If Similar id and both NULL value
EXAMPLE: Id 2 will get 1, id 3 will be assigned 0 and id 4 will get 2. There can be only 2 similar ids in the id table (there are no 3 values of 2 or 4)
I could find the similar id, but having difficulties writing the cases
select id
from table
group by id
having count(id) = 2
We can determine the status values by using aggregation:
WITH cte AS (
SELECT id,
CASE WHEN COUNT(*) > 1 AND COUNT(CASE WHEN last IS NULL THEN 1 END) = 1
THEN 1
WHEN COUNT(*) > 1 AND COUNT(CASE WHEN last IS NULL THEN 1 END) = 0
THEN 2
WHEN COUNT(*) > 1 AND COUNT(CASE WHEN last IS NULL THEN 1 END) = COUNT(*)
THEN 0 ELSE -1 END AS status
FROM yourTable
GROUP BY id
)
SELECT t1.*, t2.status
FROM yourTable t1
INNER JOIN cte t2
ON t1.id = t2.id;
Note that I assign a status value of -1 to any id which does not meet one of the three criteria. This would include any id which only appears once, among other edge cases.
You can do it this way
select a.id, last,
case
when exists(select 1 from _table b where a.id = b.id and coalesce(b.last,0) <> coalesce(a.last,0) and (a.last is null or b.last is null))
then 1
when exists(select 1 from _table b where a.id = b.id
and coalesce(b.last,0) <> coalesce(a.last,0))
and not exists(select 1 from _table b where a.id = b.id
and b.last is null)
then 2
when exists(select 1 from _table b where a.id = b.id )
and exists(select 1 from _table b where a.id = b.id and b.last is null and a.last is null having count(*) =
(select count(*) from _table b where a.id = b.id))
then 0
end as status
from _table a
Output:
id last status
2 NULL 1
2 2018 1
3 NULL 0
3 NULL 0
4 2011 2
4 2013 2
If you want one row per id:
select id,
(case count(*) filter (value is null)
when 1 then 1
when 0 then 2
when 2 then 3
end) as status
from t
group by id;
If you want this as a column on the original data, use window functions:
select t.*,
(case count(*) filter (value is null) over (partition by id)
when 1 then 1
when 0 then 2
when 2 then 3
end) as status
from t;

Oracle query with group

I have a scenario where I need to fetch all the records within an ID for the same 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 IBM NULL P
3 IBM NULL P
4 IBM NULL OF
4 IBM NULL OF
4 IBM Y ON
From the above settings, I need to select all the records with source as IBM within that same ID group.Within the ID group if there is at least one record with a source other than IBM, then I don't want any record from that ID group. 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=3 have a source as IBM, 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'.
Also within the group which has satisfied the above condition, I need one more condition for source_type. if there are records with source_type='P', then I need to fetch only that record.If there are no records with P, then I will search for source_type='OF' else source_type='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
not exists(select 1 from TABLE B where a.ID = B.ID and source <> 'IBM')
and exists(select 1 from TABLE C where a.ID = C.ID and 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))
I think you can just do a single aggregation over your table by ID and check for the yes flag as well as assert that no non IBM source appears. I do this in a CTE below, and then join back to your original table to return full matching records.
WITH cte AS (
SELECT
ID,
CASE WHEN SUM(CASE WHEN TYPE = 'P' THEN 1 ELSE 0 END) > 0
THEN 1
WHEN SUM(CASE WHEN TYPE = 'OF' THEN 1 ELSE 0 END) > 0
THEN 2
WHEN SUM(CASE WHEN TYPE = 'ON' THEN 1 ELSE 0 END) > 0
THEN 3 ELSE 4 END AS p_type
FROM yourTable
GROUP BY ID
HAVING
SUM(CASE WHEN CURR_FLAG = 'Y' THEN 1 ELSE 0 END) > 0 AND
SUM(CASE WHEN SOURCE <> 'IBM' THEN 1 ELSE 0 END) = 0
)
SELECT t1.*
FROM yourTable t1
INNER JOIN cte t2
ON t1.ID = t2.ID
WHERE
t2.p_type = 1 AND t1.TYPE = 'P' OR
t2.p_type = 2 AND t1.TYPE = 'OF' OR
t2.p_type = 3 AND t1.TYPE = 'ON';

To retrieve records having only two specific values

Have the following Data in the table
Example Table
ID Value
1 a
1 b
1 c
2 a
2 b
2 c
3 a
3 b
I need to retrieve records having ID with only two values a and b.
So i am expecting only the Record with ID 3 .
Can anyone help me with the query
I guess you could do something like
select
ID,
sum(case when value = 'a' then 1
when value = 'b' then 1
else 3 end)
from
table1
group by id
having
sum (case when value = 'a' then 1
when value = 'b' then 1
else 3 end) =2
SQL Fiddle
That will work:
select x.id from
(
select id from mytable where value = 'a'
union all
select id from mytable where value = 'b'
) x
group by x.id
having COUNT(*) = 2
and not exists (select * from mytable t where t.id = x.id and value <> 'a' and value <> 'b')

Exclude value of a record in a group if another is present

In the example table below, I'm trying to figure out a way to sum amount over id for all marks where mark 'C' doesn't exist within an id. When mark 'C' does exist in an id, I want the sum of amounts over that id, excluding the amount against mark 'A'. As illustration, my desired output is at the bottom. I've considered using partitions and the EXISTS command, but I'm having trouble conceptualizing the solution. If any of you could take a look and point me in the right direction, it would be greatly appreciated :)
sample table:
id mark amount
------------------
1 A 1
2 A 3
2 B 2
3 A 2
4 A 1
4 B 3
5 A 1
5 C 3
6 A 2
6 C 2
desired output:
id sum(amount)
-----------------
1 1
2 5
3 2
4 4
5 3
6 2
select
id,
case
when count(case mark when 'C' then 1 else null end) = 0
then
sum(amount)
else
sum(case when mark <> 'A' then amount else 0 end)
end
from sampletable
group by id
Here is my effort:
select id, sum(amount) from table t where not t.id = 'A' group by id
having id in (select id from table t where mark = 'C')
union
select id, sum(amount) from table t where t.id group by id
having id not in (select id from table t where mark = 'C')
SELECT
id,
sum(amount) AS sum_amount
FROM atable t
WHERE mark <> 'A'
OR NOT EXISTS (
SELECT *
FROM atable
WHERE id = t.id
AND mark = 'C'
)
GROUP BY
id
;