query all of the same id based on multiple true conditions - sql

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')
)

Related

How to count number of months of row in Oracle

Im using ORACLE TO check condition in 'DATE_PERIOS' with current time and last time
my ORD0011 table:
-------------------------------------------------------------
ORDER_ID | BS_NO | DATE_PERIOS | STATUS
-------------------------------------------------------------
3000003 HS00001 4-2021 COMPLETE
3000003 HS00183 5-2021 COMPLETE
3000003 HS00776 10-2021 FALSE
3000003 HS00559 11-2021 COMPLETE
3000003 HS00221 12-2021 ACTIVE
3000003 HS00222 1-2022 COMPLETE
--------------------------------------------------------------
when i select 'ORDER_ID' = 3000003, it will output with data following as:
------------------------------
ORDER_ID | HS_TIME
------------------------------
3000003 4
------------------------------
this is my recipe:
If i select DATE_PERIOS is: 1/2022, it will display HS_TIME is 4 (3 times COMPLETE before: 4-2021, 5-2021, 11-2021 + 1)
If i select DATE_PERIOS is: 11/2021, it will display HS_TIME is 3 (2 times COMPLETE before: 4-2021, 5-2021 + 1)
Note: only + 1 when status is COMPLETE
How to count number of Month with condition to output result as above ? Thanks a lot
You can use the below query having case statement to handle the status = 'COMPLETE' -
SELECT CASE WHEN COUNT(CASE WHEN status = 'COMPLETE' THEN 1 ELSE NULL END) > 0 THEN
COUNT(CASE WHEN status = 'COMPLETE' THEN 1 ELSE NULL END) + 1
ELSE NULL
END HS_TIME
FROM tb
WHERE TO_DATE(DATE_PERIOS, 'MM-YYYY') < TO_DATE('01-2020', 'MM-YYYY');
Demo.
One option is to conditionally (that's the CASE expression) add 1 (that's the SUM function) if status is COMPLETE.
WHERE clause requires TO_DATE with appropriate date format. Otherwise, you'd be comparing strings which would lead to wrong result; might be OK if date_perios was stored in YYYYMM format; on the other hand, perhaps you'd want to consider storing date values into the DATE datatype column.
Sample data
SQL> with ord0011 (order_id, date_perios, status) as
2 (select 303, '4-2021' , 'COMPLETE' from dual union all
3 select 303, '5-2021' , 'COMPLETE' from dual union all
4 select 303, '10-2021', 'FALSE' from dual union all
5 select 303, '11-2021', 'COMPLETE' from dual union all
6 select 303, '12-2021', 'ACTIVE' from dual union all
7 select 303, '1-2022' , 'COMPLETE' from dual
8 )
Query itself
9 select order_id,
10 sum(case when status = 'COMPLETE' then 1 else 0 end) hs_time
11 from ord0011
12 where to_date(date_perios, 'mm-yyyy') <= to_date('&par_date', 'mm-yyyy')
13 group by order_id;
Enter value for par_date: 1-2022
ORDER_ID HS_TIME
---------- ----------
303 4
SQL> /
Enter value for par_date: 11-2021
ORDER_ID HS_TIME
---------- ----------
303 3
SQL>
with cte as(
select *,row_number()over(partition by ORDER_ID order by ORDER_ID) as seq
from tb
),
cte2 as(
select *,
case
when DATE_PERIOS = '1-2022'
then (select count(*) + 1 from cte t2 where t1.seq > t2.seq and t2.STATUS = 'COMPLETE')
else 0 end as HS_TIME_1_2022,
case
when DATE_PERIOS = '11-2021'
then (select count(*) + 1 from cte t2 where t1.seq > t2.seq and t2.STATUS = 'COMPLETE')
else 0 end as HS_TIME_11_2022
from cte t1)
select ORDER_ID,max(HS_TIME_1_2022)HS_TIME_1_2022,max(HS_TIME_11_2022)HS_TIME_11_2022
from cte2
group by ORDER_ID
The following output is taken in sql-server but can also be run in Oracle.
Result

How to transform and grouping in SQL

I would like to group bycontract date by transforming segment referring to its contract date like below.
contractdate segment
~2020/2/1 a
2020/2/2~2020/4/1 b
2020/4/2~ c
My desired result is to cut contractdate into segment and countthem into result tables.
If someone has opinion,please let me know.
Thanks
my table is like below.
contractdate status
2020/1/2 A
2020/4/2 B
2020/6/5 C
2020/1/2 C
2020/4/4 B
And here is my desired result.
segment A B C
a 1 0 1
b 0 0 0
c 0 2 1
Replace missing bounds with sentinels (or replace notation with weird tilde character at all), then distribute contractdate of statuses into proper ranges:
with sg (contractdate,segment) as (
select '~2020/2/1' , 'a' from dual union all
select '2020/2/2~2020/4/1', 'b' from dual union all
select '2020/4/2~' , 'c' from dual
), ssg as ( -- sanitized sg
select coalesce(to_date(regexp_replace(contractdate,'([^~]*)~([^~]*)','\1'),'YYYY/MM/DD'), date '-4712-1-1') as lowerbound -- source: https://laurentschneider.com/wordpress/2008/01/what-is-the-lowest-and-highest-possible-date-in-oracle.html
, coalesce(to_date(regexp_replace(contractdate,'([^~]*)~([^~]*)','\2'),'YYYY/MM/DD'), date '9999-01-01') as upperbound
, segment
from sg
), st (contractdate,status) as (
select '2020/1/2', 'A' from dual union all
select '2020/4/2', 'B' from dual union all
select '2020/6/5', 'C' from dual union all
select '2020/1/2', 'C' from dual union all
select '2020/4/4', 'B' from dual
)
select segment
, sum(case when status = 'A' then 1 else 0 end)
, sum(case when status = 'B' then 1 else 0 end)
, sum(case when status = 'C' then 1 else 0 end)
from ssg left join st on to_date(st.contractdate,'YYYY/MM/DD') between ssg.lowerbound and ssg.upperbound
group by segment
order by segment
Please use CTE to specify your input data, not plaintext tables. It helps to concentrate on answer instead of text formatting.

SQL - Select only when all data done

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

COUNT with WHERE clause giving more rows than without WHERE clause

This may not be the right forum to ask this but I want to understand the logical error happening in my query.
I have wrote below query to understand how many users have delivered messages greater than sent messages(possibly an error in data capture, just wanted to assess it).
SELECT COUNT(DISTINCT user_id)
FROM wk_24_trigger
UNION
SELECT COUNT(DISTINCT user_id)
FROM (
SELECT *, (CASE WHEN delivered > 0 THEN 1 ELSE 0 END) as D,
(CASE WHEN sent > 0 THEN 1 ELSE 0 END) as S
FROM wk_24_trigger) t
WHERE t.D > t.s
The result which I got are as belows
_c0
1 1056840
2 1819729
I am not getting why row 2 > row 1.
Ideally even if for every entry Delivered > Sent then row 2 and row 1 should have been same
Are you sure that the first row is the result from the first query and the second one from the second query..??
It always need not be..
Try adding alias name after the count in each query and verify the result..
you can check below example as well..
WITH TEMP
AS(
SELECT 'A' USER_ID , 1 DELIVERED , NULL SENT FROM DUAL
UNION
SELECT 'B' ID , 10 A , 1 B FROM DUAL
UNION
SELECT 'C' ID , NULL A , 1 B FROM DUAL
UNION
SELECT 'D' ID , -1 A , 1 B FROM DUAL
)
SELECT COUNT(DISTINCT USER_ID), 'QUERY_1' QUERY
FROM TEMP
UNION
(SELECT COUNT(DISTINCT USER_ID), 'QUERY_2'
FROM (
SELECT USER_ID,DELIVERED,SENT,
(CASE
WHEN DELIVERED > 0 THEN
1
ELSE
0
END) D,
(CASE
WHEN SENT > 0 THEN
1
ELSE
0
END) S
FROM TEMP) T
WHERE T.D > T.S);
and system output is as below..
COUNT(DISTINCTUSER_ID) QUERY
1 1 QUERY_2
2 4 QUERY_1
the same could be your case as well..

SQL query - sum of values by status for date interval

I get crazy because of one query. I have a table like following and I want to get a data - Summa of Values by Status For every Date in interval.
Table
Id Name Value Date Status
1 pro1 2 01.04.14 0
2 pro1 8 02.04.14 1
3 pro2 6 02.04.14 1
4 pro3 0 03.04.14 0
5 pro4 7 03.04.14 0
6 pro4 2 03.04.14 0
7 pro4 4 03.04.14 1
8 pro4 6 04.04.14 1
9 pro4 1 04.04.14 1
For example,
Input: Name = pro4, minDate = 01.02.14, maxDate = 04.09.14
Output:
Date Values sum for 0 Status Values sum for 1 Status
01.04.14 0 0
02.04.14 0 0
03.04.14 9 (=7+2) 4 (only 4 exist)
04.04.14 0 7 (6+1)
In 01.02.14 and 02.04.14 dates, pro4 has not values by status, but I want to show that rows, because I need all dates in that interval. Can anyone help me to create this query?
Edit:
I can not change structure, I have already that table with data. Every day exist in table many times (minimum 1 time)
Thanks in advance.
Assuming you have a row for each date in the table, use conditional aggregation:
select date,
sum(Case when name = 'pro4' and status = 0 then Value else 0 end) as values_0,
sum(case when name = 'pro4' and status = 1 then Value else 0 end) as values_1
from Table t
where date >= '2014-04-01' and date <= '2014-04-09'
group by date
order by date;
If you don't have this list of dates, you can take this approach instead:
with dates as (
select cast('2014-04-01' as date) as thedate
union all
select dateadd(day, 1, thedate)
from dates
where thedate < '2014-04-09'
)
select dates.thedate,
sum(Case when status = 0 then Value else 0 end) as values_0,
sum(case when status = 1 then Value else 0 end) as values_1
from dates left outer join
table t
on t.date = dates.thedate and t.name = 'pro4'
group by dates.thedate;
just an assumption query :
select Distinct date ,case when status = 0 and MAX(date) then SUM(value) ELSE 0 END Status0 ,
case when status = 1 and MAX(date) then SUM(value) ELSE 0 END Status1 from table
To expand my comment the complete query is
WITH [counter](N) AS
(SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1)
, days(N) AS (
SELECT row_number() over (ORDER BY (SELECT NULL)) FROM [counter])
, months (N) AS (
SELECT N - 1 FROM days WHERE N < 13)
, calendar ([date]) AS (
SELECT DISTINCT cast(dateadd(DAY, days.n
, dateadd(MONTH, months.n, '20131231')) AS date)
FROM months
CROSS JOIN days
)
SELECT a.Name
, c.Date
, [Sum of 0] = SUM(CASE Status WHEN 0 THEN Value ELSE 0 END)
, [Sum of 1] = SUM(CASE Status WHEN 1 THEN Value ELSE 0 END)
FROM Calendar c
LEFT JOIN myTable a ON c.Date = a.Date AND a.name = 'pro4'
WHERE c.date BETWEEN '20140201' AND '20140904'
GROUP BY c.Date, a.Name
ORDER BY c.Date
Note that the condition on the name need to be in the JOIN, otherwise you'll get only the date of your table.
If you need multiple years just add another CTE for the count and a dateadd(YEAR,...) in the CTE calendar
This is not really the exact query, but I think you can get that by having a query that looks like:
select date, status, sum(value) from table
where (date between mindate and maxdate) and name = product_name
group by date, status;
this page gives more info.
EDIT
So the above query only gives a part of the answer required by the OP. A LEFT OUTER JOIN of the original table and the result of the above query on thedate and status fields will give the missing info.
e.g.
select x.date, x.status, x.sum_of_values from table as y
left outer join
(select date, status, sum(value) as sum_of_values
from table
where (date between mindate and maxdate) and name = product_name
group by date, status) as x
on y.date= x.date and y.status = x.status
order by x.date;