I have a line:
sum(purchases) over(partition by category order by value_day range between interval '1' month preceding and current row)
If value_day = Aug 21, it returns sum from and included July 21 till and included Aug 21, but I need from and included July 22 till and included Aug 21.
How can I do that?
You can use an expression to define the starting point of the window. So you can
Subtract a month from the current date
Add a day to it
Giving something like:
sum ( purchases ) over (
partition by category
order by value_day
range between ( value_day - ( add_months ( value_day, -1 ) + 1 ) ) preceding
and current row
)
You can either:
Use two windowed functions, your one to add everything from the past month and then subtract a second one that just covers the range you do not want to include; or
Use a correlated sub-query rather than windowed analytic functions.
SELECT t.*,
sum(purchases) over(
partition by category
order by value_day
range between interval '1' month preceding and current row
) -
COALESCE(
sum(purchases) over(
partition by category
order by value_day
range between interval '1' month preceding and interval '1' month preceding
),
0
) AS total1,
( SELECT SUM(s.purchases)
FROM table_name s
WHERE t.category = s.category
AND ADD_MONTHS(t.value_day, -1) + INTERVAL '1' DAY <= s.value_day
AND s.value_day <= t.value_day
) AS total2
FROM table_name t;
Which, for the sample data:
CREATE TABLE table_name (category, value_day, purchases) AS
SELECT 1, DATE '2022-01-01' + LEVEL - 1, LEVEL
FROM DUAL
CONNECT BY LEVEL <= 50;
Outputs:
CATEGORY
VALUE_DAY
PURCHASES
TOTAL1
TOTAL2
...
...
...
...
...
1
01-FEB-22
32
527
527
1
02-FEB-22
33
558
558
1
03-FEB-22
34
589
589
1
04-FEB-22
35
620
620
1
05-FEB-22
36
651
651
1
06-FEB-22
37
682
682
1
07-FEB-22
38
713
713
1
08-FEB-22
39
744
744
1
09-FEB-22
40
775
775
1
10-FEB-22
41
806
806
1
11-FEB-22
42
837
837
1
12-FEB-22
43
868
868
1
13-FEB-22
44
899
899
1
14-FEB-22
45
930
930
1
15-FEB-22
46
961
961
1
16-FEB-22
47
992
992
1
17-FEB-22
48
1023
1023
1
18-FEB-22
49
1054
1054
1
19-FEB-22
50
1085
1085
db<>fiddle here
Related
I have a requirement to fetch previous row or lag records where there are some missing previous ids.
Database : Oracle 12c
Example data:
BRANCH
PERIOD
QTY
105
319
17
105
320
20
105
321
32
105
322
61
107
319
17
107
321
18
107
322
16
108
319
21
108
322
27
I want the results in below format:
If you see for branch 107 : the period 319 is missing and for branch 108 : 320,321 are missing. So if there are any missing previous records then the prev_period_<>_Qty columns should be 0.
Can you please help in achieving this.
BRANCH
PERIOD
QTY
PREV_PERIOD_1_QTY
PREV_PERIOD_2_QTY
PREV_PERIOD_3_QTY
105
319
17
0
0
0
105
320
20
17
0
0
105
321
32
20
17
0
105
322
61
32
20
17
107
319
17
0
0
0
107
321
18
0
17
0
107
322
16
18
0
17
108
319
21
0
0
0
108
322
27
0
0
21
From Oracle 12, you can use MATCH_RECOGNIZE to do row-by-row processing:
SELECT branch,
period,
qty,
COALESCE(prev_period_1_qty, 0) AS prev_period_1_qty,
COALESCE(prev_period_2_qty, 0) AS prev_period_2_qty,
COALESCE(prev_period_3_qty, 0) AS prev_period_3_qty
FROM table_name
MATCH_RECOGNIZE (
PARTITION BY branch
ORDER BY period DESC
MEASURES
curr.period AS period,
curr.qty AS qty,
prev1.qty AS prev_period_1_qty,
prev2.qty AS prev_period_2_qty,
prev3.qty AS prev_period_3_qty
ONE ROW PER MATCH
AFTER MATCH SKIP TO NEXT ROW
PATTERN (curr prev1? prev2? prev3?)
DEFINE
prev1 AS curr.period - 1 = period,
prev2 AS curr.period - 2 = period,
prev3 AS curr.period - 3 = period
)
ORDER BY branch, period
Or, using LAG:
SELECT branch,
period,
qty,
CASE
WHEN p1 = period - 1
THEN q1 ELSE 0
END AS prev_period_1_qty,
CASE
WHEN p1 = period - 2 THEN q1
WHEN p2 = period - 2 THEN q2
ELSE 0
END AS prev_period_2_qty,
CASE
WHEN p1 = period - 3 THEN q1
WHEN p2 = period - 3 THEN q2
WHEN p3 = period - 3 THEN q3
ELSE 0
END AS prev_period_3_qty
FROM (
SELECT t.*,
LAG(period, 1) OVER (PARTITION BY branch ORDER BY period) AS p1,
LAG(period, 2) OVER (PARTITION BY branch ORDER BY period) AS p2,
LAG(period, 3) OVER (PARTITION BY branch ORDER BY period) AS p3,
LAG(qty, 1, 0) OVER (PARTITION BY branch ORDER BY period) AS q1,
LAG(qty, 2, 0) OVER (PARTITION BY branch ORDER BY period) AS q2,
LAG(qty, 3, 0) OVER (PARTITION BY branch ORDER BY period) AS q3
FROM table_name t
)
Which, for the sample data:
CREATE TABLE table_name (BRANCH, PERIOD, QTY) AS
SELECT 105, 319, 17 FROM DUAL UNION ALL
SELECT 105, 320, 20 FROM DUAL UNION ALL
SELECT 105, 321, 32 FROM DUAL UNION ALL
SELECT 105, 322, 61 FROM DUAL UNION ALL
SELECT 107, 319, 17 FROM DUAL UNION ALL
SELECT 107, 321, 18 FROM DUAL UNION ALL
SELECT 107, 322, 16 FROM DUAL UNION ALL
SELECT 108, 319, 21 FROM DUAL UNION ALL
SELECT 108, 322, 27 FROM DUAL;
Both output:
BRANCH
PERIOD
QTY
PREV_PERIOD_1_QTY
PREV_PERIOD_2_QTY
PREV_PERIOD_3_QTY
105
319
17
0
0
0
105
320
20
17
0
0
105
321
32
20
17
0
105
322
61
32
20
17
107
319
17
0
0
0
107
321
18
0
17
0
107
322
16
18
0
17
108
319
21
0
0
0
108
322
27
0
0
21
db<>fiddle here
I have the following table :
cohort
month cohort
orders
cumulated orders
2021-01
0
126
126
2021-01
1
5
131
2021-01
2
4
135
2021-02
0
131
131
2021-02
1
9
140
2021-02
2
8
148
And now I want to have the following table where I divide each repeat orders by the number of orders of month 0 :
cohort
month cohort
orders
cumulated orders
cumulated in %
2021-01
0
126
126
100%
2021-01
1
5
131
104%
2021-01
2
4
135
107%
2021-02
0
131
131
100%
2021-02
1
9
140
107%
2021-02
2
8
148
114%
My only hint is to create a CASE statement, but I don't want each month to update the query by adding the line
WHEN cohort="2021-08" THEN cumulated orders / 143
where 143 is the number of orders of cohort 2021-08 at month cohort =0
Has someone got an idea how to get this table ?
A case expression isn't needed. You can use first_value():
select t.*,
( cumulated_order /
first_value(orders) over (partition by cohort order by month_cohort)
) as ratio
from t;
If you really wanted a case, you could use:
select t.*,
( cumulated_order /
max(case when month_cohort = 0 then orders end) over (partition by cohort)
) as ratio
from t;
Consider below
select *,
round(100 * cumulated_orders /
sum(if(month_cohort = 0, orders, 0)) over(partition by cohort)
) as cumulated_in_percent
from `project.dataset.table`
if applied to sample data in your question - output is
Lets say I have a table which holds all exports for some time back in Microsoft SQL database:
Name:
ExportTable
Columns:
id - numeric(18)
exportdate - datetime
In order to get the number of exports per week I can run the following query:
SELECT DATEPART(ISO_WEEK,[exportdate]) as 'exportdate', count(exportdate) as 'totalExports'
FROM [ExportTable]
Group By DATEPART(ISO_WEEK,[exportdate])
order by exportdate;
Returns:
exportdate totalExports
---------- ------------
27 13
28 12
29 15
30 8
31 17
32 10
33 7
34 15
35 4
36 18
37 10
38 14
39 14
40 21
41 19
Would it be possible to aggregate the week results by quarter so the output becomes something like the bellow?
UPDATE
Sorry for not being crystal clear, I would like the current result to add upp with previous result up to a new quarter.
Note week 41 contains 21+19 = 40
Week 39 contains 157 (13+12+15+8+17+10+7+15+4+18+10+14+14)
exportdate totalExports Quarter
---------- ------------ -------
27 13 3
28 25 3
29 40 3
30 48 3
31 65 3
32 75 3
33 82 3
34 97 3
35 101 3
36 119 3
37 129 3
38 143 3
39 157 3 -- Sum of 3 Quarter values.
40 21 4 -- New Quarter show current week value
41 40 4 -- (21+19)
You can use this.
SELECT
DATEPART(ISO_WEEK,[exportdate]) as 'exportdate'
, SUM( count(exportdate) ) OVER ( PARTITION BY DATEPART(QUARTER,MIN([exportdate])) ORDER BY DATEPART(ISO_WEEK,[exportdate]) ROWS UNBOUNDED PRECEDING ) as 'totalExports'
, DATEPART(QUARTER,MIN([exportdate])) [Quarter]
FROM [ExportTable]
Group By DATEPART(ISO_WEEK,[exportdate])
order by exportdate;
You could use a case statement to separate the dates into quarters.
e.g.
CASE
WHEN EXPORT_DATE BETWEEN '1' AND '4' THEN 1
WHEN Export_Date BETWEEN '5' and '9' THEN 2
ELSE 0 AS [Quarter]
END
Its just an example but you get the idea.
You could then use the alias from the case
SELECT DATEPART(ISO_WEEK,[exportdate]) as 'exportdate', count(exportdate) as 'totalExports', DATEPART(quarter,[exportdate]) as quarter FROM [ExportTable] Group By DATEPART(ISO_WEEK,[exportdate]), DATEPART(quarter,[exportdate]) order by exportdate;
I have a table ScheduleRotationDetail that contains these as columns:
ScheduleRotationID ScheduleID Ordinal Duration
379 61 1 1
379 379 2 20
379 512 3 1
379 89 4 20
I have a query that goes like this in order to get the day of the year each schedule is supposed to start on:
SELECT ScheduleID, Ordinal, Duration,
,Duration * 7 AS DurationDays
,( SELECT ( ISNULL( SUM(ISNULL( Duration, 0 )), 0 ) - 1 ) * 7
FROM ScheduleRotationDetail WHERE ScheduleRotationID = srd.ScheduleRotationID
AND Ordinal <= srd.Ordinal ) AS StartDay
FROM ScheduleRotationDetail srd
WHERE srd.ScheduleRotationID = 379
That outputs this as the result set:
ScheduleID Ordinal Duration DurationDays StartDay
61 1 1 7 0
379 2 20 140 140
512 3 1 7 147
89 4 20 140 287
Yet what I need the start day column values to be are:
0
7
147
154
I have tried CTEs but can't get it to work so I've come to here for advice.
It looks like you want a cumulative sum. In SQL Server 2012+, you can do:
SELECT ScheduleID, Ordinal, Duration,
SUM(Duration*7) OVER (ORDER BY Ordinal) - Duration*7 as StartDate
FROM ScheduleRotationDetail srd ;
In earlier versions, you can use APPLY for this purpose (or a correlated subquery).
I want to show the date field can not group.
My Query:
SELECT DAY(T1.UI_CreateDate) AS DATEDAY, SUM(1) AS TOTALCOUNT
FROM mydb.dbo.LP_UseImpression T1 WHERE T1.UI_BR_BO_ID = 45
GROUP BY DAY(T1.UI_CreateDate)
Result:
DATEDAY TOTALCOUNT
----------- -----------
15 186
9 1
3 2
26 481
21 297
27 342
18 18
30 14
4 183
25 553
13 8
22 469
16 1
17 28
20 331
28 90
14 33
8 1
But i want to show the full date...
Example result:
DATEDAY TOTALCOUNT
----------- -----------
15/06/2015 186
9/06/2015 1
3/06/2015 2
26/06/2015 481
21/06/2015 297
27/06/2015 342
18/06/2015 18
30/06/2015 14
4/06/2015 183
25/06/2015 553
13/06/2015 8
22/06/2015 469
16/06/2015 1
17/06/2015 28
20/06/2015 331
28/06/2015 90
14/06/2015 33
8/06/2015 1
I want to see the results...
I could not get a kind of results...
How can I do?
Thanx!
How about just casting to date to remove any time component:
SELECT CAST(T1.UI_CreateDate as DATE) AS DATEDAY, COUNT(*) AS TOTALCOUNT
FROM mydb.dbo.LP_UseImpression T1
WHERE T1.UI_BR_BO_ID = 45
GROUP BY CAST(T1.UI_CreateDate as DATE)
ORDER BY DATEDAY;
SUM(1) for calculating the count does work. However, because SQL has the COUNT(*) function, it seems a bit awkward.
So you can group by DAY(T1.UI_CreateDate) or use full date for grouping. But these are different . As both these dates '2015-04-15' and '2015-12-15' result in same DAY value of 15.
Assuming you want to group on DAY rather than date please try the below version of query:
SELECT DISTINCT
T1.UI_CreateDate as DATEDAY,
count(1) over (PARTITION BY DAY(T1.UI_CreateDate) ) AS TOTALCOUNT
FROM mydb.dbo.LP_UseImpression T1 WHERE T1.UI_BR_BO_ID = 45
sql fiddle for demo: http://sqlfiddle.com/#!6/c3337/1