SQL cumulative sum until a flag value and resetting the sum - sql

I'm still learning SQL and I'm trying to figure out a problem that I wasn't able to solve. So my problem is that I'm trying to select a table(let say Expense), ordered by date and in the table I have a column named Charged and I want to add charges to be cumulative(This part I figured out). However after that I have another column that will be acting as a flag called PayOut. When the PayOut value is 1 I want the summation of Charged(SumValue) to reset to zero. How would I do this? Here is what I have tried and the current output I get and what output I want. Note: I saw some posts using CTE's but wasn't the same scenario and more complex.
select ex.date,
ex.Charged,
(case when(ex.PayOut=1) then 0
else sum(ex.Charged) over (order by ex.date)end) as SumValue,
ex.PayOut
from Expense ex
order by ex.date asc
The data looks like this
Date Charged PayOut
01/10/2018 10 0
01/20/2018 5 0
01/30/2018 3 0
02/01/2018 0 1
02/11/2018 12 0
02/21/2018 15 0
Output I get
Date Charged PayOut SumValue
01/10/2018 10 0 10
01/20/2018 5 0 15
01/30/2018 3 0 18
02/01/2018 0 1 0
02/11/2018 12 0 30
02/21/2018 15 0 45
Output Wanted
Date Charged PayOut SumValue
01/10/2018 10 0 10
01/20/2018 5 0 15
01/30/2018 3 0 18
02/01/2018 0 1 0
02/11/2018 12 0 12
02/21/2018 15 0 27

Just create group from your PayOut Column and use it as a partition in OVER
WITH Expense AS (
SELECT CAST('01/10/2018' AS DATE) AS Date, 10 AS Charged, 0 AS PayOut
UNION ALL SELECT CAST('01/20/2018' AS DATE), 5, 0
UNION ALL SELECT CAST('01/30/2018' AS DATE), 3, 0
UNION ALL SELECT CAST('02/01/2018' AS DATE), 0, 1
UNION ALL SELECT CAST('02/11/2018' AS DATE), 12, 0
UNION ALL SELECT CAST('02/21/2018' AS DATE), 15, 0
)
SELECT
dat.date
,dat.Charged
,dat.PayOut
,dat.PayOutGroup
,SUM(dat.Charged) OVER (PARTITION BY dat.PayOutGroup ORDER BY dat.date) as SumValue
FROM (
SELECT
e.date
,e.Charged
,e.PayOut
,SUM(e.PayOut) OVER (ORDER BY e.date) AS PayOutGroup
FROM Expense e
) dat

Related

SQL query to get top 24 records, then average the first 12 and bottom 12

I'm attempting to analyze each account's performance (A_Count & B_Count) during their first year versus their second year. This should only return clients who have at least 24 months of totals (records).
Volume Table
Account
ReportDate
A_Count
B_Count
1001A
2019-01-01
47
100
1001A
2019-02-01
50
105
1002A
2019-02-01
50
105
I think I'm on the right track by wanting to grab the top 24 records for each account (only if 24 exist) and then grabbing the top 12 and bottom 12, but not sure how to get there.
I guess ideal output would be:
Account
YR1_A_Avg
YR1_B_Avg
YR2_A_Avg
YR2_B_Avg
FirstDate
LastDate
1001A
47
100
53
115
2019-01-01
2021-12-31
1002A
50
105
65
130
2019-02-01
2022-01-01
1003A
15
180
38
200
2017-05-01
2019-04-01
I'm not too worried about performance.
Assuming there are no gaps in ReportDate (per Account).
select Account
,avg(case when year_index = 1 then A_Count end) as YR1_A_Avg
,avg(case when year_index = 1 then B_Count end) as YR1_B_Avg
,avg(case when year_index = 2 then A_Count end) as YR2_A_Avg
,avg(case when year_index = 2 then B_Count end) as YR2_B_Avg
,min(ReportDate) as FirstDate
,max(ReportDate) as LastDate
from
(
select *
,count(*) over(partition by Account) as cnt
,(row_number() over(partition by Account order by ReportDate)-1)/12 +1 as year_index
from Volume
) t
where cnt >= 24 and year_index <= 2
group by Account

Subtract in Union

I have this data, where I want to generate the last row "on the fly" from the first two:
Group
1yr
2yrs
3yrs
date
code
Port
19
-15
88
1/1/2020
arp
Bench
10
-13
66
1/1/2020
arb
Diff
9
2
22
I am trying to subtract the Port & Bench returns and have the difference on the new row. How can I do this?
Here's my code so far:
Select
date
Group,
Code,
1 yr returnp,
2 yrs returnp,
3yrs return
From timetable
union
Select
date,
Group,
Code,
1 yr returnb,
2 yrs returnb,
3yrs returnb
From timetable
Seems to me that a UNION ALL in concert with a conditional aggregation should do the trick
Note the sum() is wrapped in an abs() to match desired results
Select *
From YourTable
Union All
Select [Group] = 'Diff'
,[1yr] = abs(sum([1yr] * case when [Group]='Bench' then -1 else 1 end))
,[2yrs] = abs(sum([2yrs] * case when [Group]='Bench' then -1 else 1 end))
,[3yrs] = abs(sum([3yrs] * case when [Group]='Bench' then -1 else 1 end))
,[date] = null
,[code] = null
from YourTable
Results
Group 1yr 2yrs 3yrs date code
Port 19 -15 88 2020-01-01 arp
Bench 10 -13 66 2020-01-01 arb
Diff 9 2 22 NULL NULL
If you know there is always 2 rows, something like this would work
SELECT * FROM timetable
UNION ALL
SELECT
MAX(1yr) - MIN(1yr),
MAX(2yrs) - MIN(2yrs),
MAX(3yrs) - MIN(3yrs),
null,
null,
FROM timetable

SQL count number of users every 7 days

I am new to SQL and I need to find count of users every 7 days. I have a table with users for every single day starting from April 2015 up until now:
...
2015-05-16 00:00
2015-05-16 00:00
2015-05-17 00:00
2015-05-17 00:00
2015-05-17 00:00
2015-05-17 00:00
2015-05-17 00:00
2015-05-18 00:00
2015-05-18 00:00
...
and I need to count the number of users every 7 days (weekly) so I have data weekly.
SELECT COUNT(user_id), Activity_Date FROM TABLE_NAME
I need output like this:
TotalUsers week1 week2 week3 ..........and so on
82 80 14 16
I am using DB Visualizer to query Oracle database.
You should try following,
Select
sum(Week1) + sum(Week2) + sum(Week3) + sum(Week4) + sum(Week5) as Total,
sum(Week1) as Week1,
sum(Week2) as Week2,
sum(Week3) as Week3,
sum(Week4) as Week4,
sum(Week5) as Week5
From (
select
case when week = 1 then 1 else 0 end as Week1,
case when week = 2 then 1 else 0 end as Week2,
case when week = 3 then 1 else 0 end as Week3,
case when week = 4 then 1 else 0 end as Week4,
case when week = 5 then 1 else 0 end as Week5
from
(
Select
CEILING(datepart(dd,visitdate)/7+1) week,
user_id
from visitor
)T
)D
Here is Fiddle
You need to add month & year in the result as well.
SELECT COUNT(user_id), Activity_Date FROM TABLE_NAME WHERE Activity_Date > '2015-06-31';
That would get the amount of users for the last 7 days.
This is my test table:
user_id act_date
1 01/04/2015
2 01/04/2015
3 04/04/2015
4 05/04/2015
..
This is my query:
select week_offset, count(*) nb from (
select trunc((act_date-to_date('01042015','DDMMYYYY'))/7) as week_offset from test_date)
group by week_offset
order by 1
and this is the output:
week_offset nb
0 6
1 3
4 5
5 7
6 3
7 1
18 1
Week offset is the number of the week from 01/04/2015, and we can show the first day of the week.
See here for live testing.
How do you define your weeks? Here's an approach for SQL Server that starts each seven-day block relative to the start of April. The expressions will vary according to your specific needs:
select
dateadd(
dd,
datediff(dd, cast('20150401' as date), Activity_Date) / 7 * 7,
cast('20150401' as date)
) as WeekStart,
count(*)
from T
group by datediff(dd, cast('20150401' as date), Activity_Date) / 7
Oracle:
select
trunc(Activity_date, 'DAY') as WeekStart,
count(*)
from T
group by trunc(Activity_date, 'DAY') /* D and DAY are the same thing */

Count parts of total value as columns per row (pivot table)

I'm stuck with a seemingly easy query, but couldn't manage to get it working the last hours.
I have a table files that holds file names and some values like records in this file, DATE of creation (create_date), DATE of processing (processing_date) and so on. There can be multiple files for a create date in different hours and it is likely that they will not get processed in the same day of creaton, in fact it can even take up to three days or longer for them to get processed.
So let's assume I have these rows, as an example:
create_date | processing_date
------------------------------
2012-09-10 11:10:55.0 | 2012-09-11 18:00:18.0
2012-09-10 15:20:18.0 | 2012-09-11 13:38:19.0
2012-09-10 19:30:48.0 | 2012-09-12 10:59:00.0
2012-09-11 08:19:11.0 | 2012-09-11 18:14:44.0
2012-09-11 22:31:42.0 | 2012-09-21 03:51:09.0
What I want in a single query is to get a grouped column truncated to the day create_date with 11 additional columns for the differences between the processing_date and the create_date, so that the result should roughly look like this:
create_date | diff0days | diff1days | diff2days | ... | diff10days
------------------------------------------------------------------------
2012-09-10 | 0 2 1 ... 0
2012-09-11 | 1 0 0 ... 1
and so on, I hope you get the point :)
I have tried this and so far it works getting a single aggregated column for a create_date with a difference of - for example - 3:
SELECT TRUNC(f.create_date, 'DD') as created, count(1) FROM files f WHERE TRUNC(f.process_date, 'DD') - trunc(f.create_date, 'DD') = 3 GROUP BY TRUNC(f.create_date, 'DD')
I tried combining the single queries and I tried sub-queries, but that didn't help or at least my knowledge about SQL is not sufficient.
What I need is a hint so that I can include the various differences as columns, like shown above. How could I possibly achieve this?
That's basically the pivoting problem:
SELECT TRUNC(f.create_date, 'DD') as created
, sum(case TRUNC(f.process_date, 'DD') - trunc(f.create_date, 'DD')
when 0 then 1 end) as diff0days
, sum(case TRUNC(f.process_date, 'DD') - trunc(f.create_date, 'DD')
when 1 then 1 end) as diff1days
, sum(case TRUNC(f.process_date, 'DD') - trunc(f.create_date, 'DD')
when 2 then 1 end) as diff2days
, ...
FROM files f
GROUP BY
TRUNC(f.create_date, 'DD')
SELECT CreateDate,
sum(CASE WHEN DateDiff(day, CreateDate, ProcessDate) = 1 THEN 1 ELSE 0 END) AS Diff1,
sum(CASE WHEN DateDiff(day, CreateDate, ProcessDate) = 2 THEN 1 ELSE 0 END) AS Diff2,
...
FROM table
GROUP BY CreateDate
ORDER BY CreateDate
As you are using Oracle 11g you can also get desired result by using pivot query.
Here is an example:
-- sample of data from your question
SQL> create table Your_table(create_date, processing_date) as
2 (
3 select '2012-09-10', '2012-09-11' from dual union all
4 select '2012-09-10', '2012-09-11' from dual union all
5 select '2012-09-10', '2012-09-12' from dual union all
6 select '2012-09-11', '2012-09-11' from dual union all
7 select '2012-09-11', '2012-09-21' from dual
8 )
9 ;
Table created
SQL> with t2 as(
2 select create_date
3 , processing_date
4 , to_date(processing_date, 'YYYY-MM-DD')
- To_Date(create_date, 'YYYY-MM-DD') dif
5 from your_table
6 )
7 select create_date
8 , max(diff0) diff0
9 , max(diff1) diff1
10 , max(diff2) diff2
11 , max(diff3) diff3
12 , max(diff4) diff4
13 , max(diff5) diff5
14 , max(diff6) diff6
15 , max(diff7) diff7
16 , max(diff8) diff8
17 , max(diff9) diff9
18 , max(diff10) diff10
19 from (select *
20 from t2
21 pivot(
22 count(dif)
23 for dif in ( 0 diff0
24 , 1 diff1
25 , 2 diff2
26 , 3 diff3
27 , 4 diff4
28 , 5 diff5
29 , 6 diff6
30 , 7 diff7
31 , 8 diff8
32 , 9 diff9
33 , 10 diff10
34 )
35 ) pd
36 ) res
37 group by create_date
38 ;
Result:
Create_Date Diff0 Diff1 Diff2 Diff3 Diff4 Diff5 Diff6 Diff7 Diff8 Diff9 Diff10
--------------------------------------------------------------------------------
2012-09-10 0 2 1 0 0 0 0 0 0 0 0
2012-09-11 1 0 0 0 0 0 0 0 0 0 1

sql query for finding slots for next four days avoiding sunday

I have doubts in sql query.
I have slots table. It basically contain maximum slots ,maximum slots for am and Pm
DayName slots AM PM
1 Monday 50 30 20
2 Tuesday 50 30 20
3 Wednesday 50 30 20
4 Thursday 50 30 20
5 Friday 25 25 0
6 Saturday 15 15 0
7 Sunday 0 0 0
I have appointment table. This table is used for adding appointment
table structure
Appointdate iS_AM
8/7/2011 12:00:00 AM 1
8/5/2011 12:00:00 AM 1
8/6/2011 12:00:00 AM 1
8/2/2011 12:00:00 AM 1
8/2/2011 12:00:00 AM 1
8/2/2011 12:00:00 AM 0
8/3/2011 12:00:00 AM 0
8/4/2011 12:00:00 AM 1
8/4/2011 12:00:00 AM 0
If it is 1 it is Am else PM.
I need to display remaining available slots for the next four days.
I need to avoid sundays.
How can we avoid sundays.
my query so far is this
with cte as
(
select dateName(dw,appoint_date) dayN,convert(varchar(12),appoint_date,101) appoint_date, sum(case is_am when 1 then 1 else 0 end) as AM,
sum(case is_am when 0 then 1 else 0 end) as PM ,sum (case is_am when 0 then 1 when 1 then 1 end) as Total
from pda_appoint where
convert(varchar(12),appoint_date,111) between
Convert(varchar(10), getdate() ,111) and Convert(varchar(10), dateadd(dd,3,getdate()) ,111)
group by appoint_date
)
select p.AM-cte.AM as [Rem AM],p.PM-cte.PM as [Rem PM],p.slots-cte.Total as [Rem Total] from cte inner join pda_slots p on cte.dayN=day_name
Output is as follows
remMax remAm remPM
28 19 47
30 19 49
29 19 48
23 0 23
I need to avoid sundays when calculating next four days and is my sql query is correct
How about that.
SELECT TOP 4
dateName(dw,a.appoint_date) dayN,
(s.AM - SUM(case a.is_am when 1 then 1 else 0 end)) AS Remaining AM,
(s.PM - SUM(case a.is_am when 0 then 1 else 0 end)) as Remaining PM,
(s.slots - COUNT(a.is_am)) AS Remaining Total Slots
FROM
pda_appoint a, slot s
WHERE
dateName(dw,a.appoint_date) = s.DayName
AND dateName(dw,a.appoint_date) != 'Sunday'
AND a.appoint_date > GETDATE()
GROUP BY a.appoint_date
ORDER BY a.appoint_date
How about this
declare #t table (DayName1 varchar(25), slots int, am int, pm int)
insert #t values('Monday',50,30,20)
insert #t values('Tuesday',50,30,20)
insert #t values('Wednesday',50,30,20)
insert #t values('Thursday',50,30,20)
insert #t values('Friday',50,30,20)
insert #t values('Saturday',50,30,20)
insert #t values('Sunday',50,30,20)
declare #t1 table (appoint_date datetime, is_am int)
insert #t1 values('8/9/2011',0)
insert #t1 values('8/10/2011',0)
insert #t1 values('8/10/2011',1)
/* You can create the below as a Table valued function that will return the values for next 4 days .you need to pass #appoint_date as a parameter*/
declare #appoint_date datetime
set #appoint_date='8/6/2011'
;with cte as
(
select dateName(dw,#appoint_date) dayN,
convert(varchar(12),#appoint_date,101) appoint_date,
1 as num
Union all
select
dateName(dw,DATEADD(day, 1, appoint_date)) dayN,
convert(varchar(12),DATEADD(day, 1, appoint_date),101) appoint_date,
num+1
from cte
where num<5
)
select top 4 dayN,(c.AM-temp.AM) as AM,(c.PM-temp.PM) as PM,(c.Slots-Temp.Total) as Total
from
(
select TOP 4
dateName(dw,a.appoint_date) dayN,
SUM(case b.is_am when 1 then 1 else 0 end) AS AM,
SUM(case b.is_am when 0 then 1 else 0 end) as PM,
COUNT(b.is_am) AS Total
from cte a left outer join #t1 b
on a.appoint_date=b.appoint_date
where a.dayN !='Sunday'
group by a.appoint_date
)Temp
inner join #t c on Temp.dayN=c.dayname1
dayN AM PM Total
Saturday 30 20 50
Monday 30 20 50
Tuesday 30 19 49
Wednesday 29 19 48