cumulative using case statement in Oracle's SQL - sql

I have a simple data
Date Count by english count by chinese
08-Mar-19 12 54
09-Mar-19 15 66
10-Mar-19 45 32
11-Mar-19 21 70
12-Mar-19 57 64
29-Mar-19 43 53
30-Mar-19 67 21
I want to group this data by week and the sum should be cumulative.The date starts from 8 march so the week should be calculated that way only. So the result should be
count by english count by chinese
08-MAR-19-14-MAR-19 150 286
15-MAR-19-22-MAR-19 150 286 (no data so same as above)
23-MAR-19-30-MAR-19 260 360
Tried using cumulative and sum but not able to achieve it

You can generate your week ranges, then use an outer join to see which data fits in each week, and use an analytic sum to get the result you want;
with week_ranges (date_from, date_to) as (
select min_date + ((level - 1) * 7), min_date + (level * 7)
from (
select min(some_date) as min_date, ceil((max(some_date) - min(some_date)) / 7) as weeks
from your_table
)
connect by level <= weeks
)
select distinct wr.date_from, wr.date_to - 1 as date_to,
sum(count_english) over (order by wr.date_from) as count_english,
sum(count_chinese) over (order by wr.date_from) as count_chinese
from week_ranges wr
left join your_table yt
on yt.some_date >= wr.date_from
and yt.some_date < wr.date_to
order by date_from;
which with your sample data gets:
DATE_FROM DATE_TO COUNT_ENGLISH COUNT_CHINESE
---------- ---------- ------------- -------------
2019-03-08 2019-03-14 150 286
2019-03-15 2019-03-21 150 286
2019-03-22 2019-03-28 150 286
2019-03-29 2019-04-04 260 360
Note this is splitting it up into four 7-days weeks, rather than one of 7 days and two of 8 days...
db<>fiddle

Here's one option; note that "my weeks" are different than yours because - your data is somewhat inconsistent as they vary from 6 to 7 days. That's also why the final result is different, but the general idea should be OK.
SQL> alter session set nls_date_format = 'dd.mm.yyyy';
Session altered.
SQL> with test (datum, cbe) as
2 -- sample data
3 (select date '2019-03-08', 12 from dual union all
4 select date '2019-03-09', 15 from dual union all
5 select date '2019-03-10', 45 from dual union all
6 select date '2019-03-11', 21 from dual union all
7 select date '2019-03-12', 57 from dual union all
8 select date '2019-03-29', 43 from dual union all
9 select date '2019-03-30', 67 from dual
10 ),
11 span as
12 -- min and max date value, so that we could create a "calendar"
13 (select min(datum) mindat,
14 max(datum) maxdat
15 from test
16 ),
17 periods as
18 -- "calendar" whose periods are weeks
19 (select s.mindat + (level - 1) * 7 datum_from,
20 (s.mindat + level * 7) - 1 datum_to
21 from span s
22 connect by level <= (s.maxdat - s.mindat) / 7 + 1
23 )
24 -- running sum per weeks
25 select distinct
26 p.datum_from,
27 p.datum_to,
28 sum(t.cbe) over (order by p.datum_from) sum_cbe
29 from test t full outer join periods p on t.datum between p.datum_from and p.datum_to
30 order by p.datum_from;
DATUM_FROM DATUM_TO SUM_CBE
---------- ---------- ----------
08.03.2019 14.03.2019 150
15.03.2019 21.03.2019 150
22.03.2019 28.03.2019 150
29.03.2019 04.04.2019 260
SQL>

Related

SQL - Splitting a row with week range into multiple rows

I have the following table structure and data in the database table:
ID
Year
StartWeek
EndWeek
AllocationPercent
5
2021
34
35
50
6
2021
1
3
5
I need to split the multi-week rows into multiple single-week rows, and the end result should be:
ID
Year
StartWeek
EndWeek
AllocationPercent
5
2021
34
34
50
5
2021
35
35
50
6
2021
1
1
5
6
2021
2
2
5
6
2021
3
3
5
Any help with this would be highly appreciated! There are a lot of threads regarding splitting date ranges into multiple rows but I cannot seem to modify those to fit my use case. I know that most likely I need a tally table with the week numbers (which I already have).
Another way to think about this is, because we know the max weeknumber is 53, to generate the set of all possible week numbers, then outer join to that set each week in any source row that is within that range.
;WITH n(n) AS
(
SELECT 0 UNION ALL SELECT n+1 FROM n WHERE n <= 53
)
SELECT w.ID,
w.Year,
StartWeek = n.n,
EndWeek = n.n,
w.AllocationPercent
FROM n
INNER JOIN dbo.TableName AS w
ON n.n BETWEEN w.StartWeek AND w.EndWeek
ORDER BY w.ID, w.Year, n.n;
Results:
ID
Year
StartWeek
EndWeek
AllocationPercent
5
2021
34
34
50
5
2021
35
35
50
6
2021
1
1
5
6
2021
2
2
5
6
2021
3
3
5
Example db<>fiddle
You can use recursive cte :
;with cte as (
select t.id, t.year, t.startweek, t.endweek, t.AllocationPercent
from t
union all
select id, year, startweek + 1, endweek, AllocationPercent
from cte c
where startweek < endweek
)
select id, year, startweek, startweek as endweek, AllocationPercent
from cte
order by id, startweek, endweek;
db fiddle

Oracle SQL - Add numbers separated by delimiter, columnwise

I have multiple rows with values like
a_b_c_d_e_f and x_y_z_m_n_o
and I need a SQL query with a result like a+x_b+y_c+z_d+m.......
Sample data as requested
What I am willing to do is aggregate it at Datetime..aggregating Total is simple, but how can I do that for the last column, thanks.
Expected Result
Here's one option; read comments within code. I didn't feel like typing too much so two dates will have to do.
Sample data (you already have that & don't type it. Code you need begins at line #10):
SQL> with
2 -- sample data
3 test (datum, total, col) as
4 (select date '2020-07-20', 100, '10,0,20,30,0' from dual union all
5 select date '2020-07-20', 150, '15,3,40,30,2' from dual union all
6 --
7 select date '2020-07-19', 200, '50,6,50,30,8' from dual union all
8 select date '2020-07-19', 300, '20,1,40,10,2' from dual
9 ),
Split CSV values into rows. Note the RB value which will help us sum matching values
10 -- split comma-separated values into rows
11 temp as
12 (select
13 datum,
14 total,
15 to_number(regexp_substr(col, '\d+', 1, column_value)) val,
16 column_value rb
17 from test cross join
18 table(cast(multiset(select level from dual
19 connect by level <= regexp_count(col, ',') + 1
20 ) as sys.odcinumberlist))
21 ),
Computing summaries is simple; nothing special about it. We'll keep the RB value as it'll be needed in the last step:
22 -- compute summaries
23 summary as
24 (select datum,
25 sum(total) total,
26 sum(val) sumval,
27 rb
28 from temp
29 group by datum, rb
30 )
The last step. Using LISTAGG, aggregate comma-separated values back, but this time added to each other:
31 -- final result
32 select datum,
33 total,
34 listagg(sumval, ',') within group (order by rb) new_col
35 from summary
36 group by datum, total
37 order by datum desc, total;
DATUM TOTAL NEW_COL
------------------- ---------- --------------------
20.07.2020 00:00:00 250 25,3,60,60,2
19.07.2020 00:00:00 500 70,7,90,40,10
SQL>

oracle select data between date range using connect by clause

I have data something like this
date count
01-JAN-2015 10
02-JAN-2015 20
03-JAN-2015 30
01-FEB-2015 4
02-FEB-2015 8
03-FEB-2015 12
01-MAR-2015 5
02-MAR-2015 10
03-MAR-2015 15
01-APR-2015 6
02-APR-2015 12
03-APR-2015 18
01-MAY-2015 7
02-MAY-2015 14
03-MAY-2015 21
01-JUN-2015 8
02-JUN-2015 16
03-JUN-2015 24
01-JUL-2015 8
02-JUL-2015 16
03-JUL-2015 24
I need result group by months with variable number of months from current month
Example If I need only for next 2 months from today result is
MAR-2015 24
APR-2015 36
and If I need only for next 3 months from today result is
MAR-2015 24
APR-2015 36
MAY-2015 42
I have query to the get variable months with start date and end date of month
SELECT TO_CHAR(TRUNC(ADD_MONTHS(sysdate,level),'MM'),'MON-yyyy') MNTH ,
TO_CHAR(TRUNC(ADD_MONTHS(sysdate,level),'MM'),'dd-MON-yyyy') strt_date,
TO_CHAR(TRUNC(LAST_DAY(ADD_MONTHS(SYSDATE, level))),'dd-MON-yyyy') end_date
FROM dual
CONNECT BY LEVEL <= p_level
Where p_level is variable number of months like 2,3,4....
Can any 1 help using SQL query without using PL/SQL
You don't need to use a connect by clause at all.
select to_char(trunc(t.date, 'mm'), 'MON-YY')
, count(1)
from your_table_here t
where trunc(t.date, 'mm') > sysdate
and trunc(t.date, 'mm') < add_months(sysdate, :months)
group by trunc(t.date, 'mm')
Just insert the correct value for :months variable.

querying how many weeks between a date range and showing them their dates

I need to count how many weeks and listing them up in a table with their respective date range.
so what i have for now is
select countinous_weeks, decode(countinous_weeks-52,0,trunc(countinous_weeks),trunc(countinous_weeks)+1)
from (
select (TO_DATE('01-01-1995', 'DD/MM/YYYY') - TO_DATE('01-01-1994','DD/MM/YYYY'))/7 countinous_weeks
from dual) wks
it only shows how many weeks within that range. What im aiming to do is showing them up in 53 rows and showing the date range for each week. So lets say for the week one
WEEK RANGE
1 01-01-1994 Until 07-01-1994 ... etc
Please help me with this query.. much appreciated
This is interesting. It has got the following things involved -
DATE ROW GENERATOR
Week number
ROW_NUMBER() to assign rank to dates in each set of week
Finally LISTAGG to aggregate the rows fetched from step 3
Let's see it working -
SQL> WITH DATA AS
2 (SELECT to_date('01/01/1994', 'DD/MM/YYYY') date1,
3 to_date('31/12/1994', 'DD/MM/YYYY') date2
4 FROM dual
5 )
6 SELECT the_week,
7 listagg(the_date, ' until ') within GROUP (
8 ORDER BY to_date(the_date, 'DD/MM/YYYY')) the_date_range
9 FROM
10 (SELECT the_week,
11 the_date,
12 row_number() over(partition BY the_week order by the_week, to_date(the_date, 'DD/MM/YYYY')) rn
13 FROM
14 (SELECT TO_CHAR(date1+level-1, 'WW') the_week ,
15 TO_CHAR(date1 +level-1, 'DD/MM/YYYY') the_date
16 FROM data
17 CONNECT BY LEVEL <= date2-date1+1
18 )
19 )
20 WHERE rn in( 1, 7)
21 GROUP BY the_week
22 /
TH THE_DATE_RANGE
-- ---------------------------------------------
01 01/01/1994 until 07/01/1994
02 08/01/1994 until 14/01/1994
03 15/01/1994 until 21/01/1994
04 22/01/1994 until 28/01/1994
05 29/01/1994 until 04/02/1994
06 05/02/1994 until 11/02/1994
07 12/02/1994 until 18/02/1994
08 19/02/1994 until 25/02/1994
09 26/02/1994 until 04/03/1994
10 05/03/1994 until 11/03/1994
11 12/03/1994 until 18/03/1994
12 19/03/1994 until 25/03/1994
13 26/03/1994 until 01/04/1994
14 02/04/1994 until 08/04/1994
15 09/04/1994 until 15/04/1994
16 16/04/1994 until 22/04/1994
17 23/04/1994 until 29/04/1994
18 30/04/1994 until 06/05/1994
19 07/05/1994 until 13/05/1994
20 14/05/1994 until 20/05/1994
21 21/05/1994 until 27/05/1994
22 28/05/1994 until 03/06/1994
23 04/06/1994 until 10/06/1994
24 11/06/1994 until 17/06/1994
25 18/06/1994 until 24/06/1994
26 25/06/1994 until 01/07/1994
27 02/07/1994 until 08/07/1994
28 09/07/1994 until 15/07/1994
29 16/07/1994 until 22/07/1994
30 23/07/1994 until 29/07/1994
31 30/07/1994 until 05/08/1994
32 06/08/1994 until 12/08/1994
33 13/08/1994 until 19/08/1994
34 20/08/1994 until 26/08/1994
35 27/08/1994 until 02/09/1994
36 03/09/1994 until 09/09/1994
37 10/09/1994 until 16/09/1994
38 17/09/1994 until 23/09/1994
39 24/09/1994 until 30/09/1994
40 01/10/1994 until 07/10/1994
41 08/10/1994 until 14/10/1994
42 15/10/1994 until 21/10/1994
43 22/10/1994 until 28/10/1994
44 29/10/1994 until 04/11/1994
45 05/11/1994 until 11/11/1994
46 12/11/1994 until 18/11/1994
47 19/11/1994 until 25/11/1994
48 26/11/1994 until 02/12/1994
49 03/12/1994 until 09/12/1994
50 10/12/1994 until 16/12/1994
51 17/12/1994 until 23/12/1994
52 24/12/1994 until 30/12/1994
53 31/12/1994
53 rows selected.
SQL>
Here is the query.
SELECT LEVEL , to_char( TO_DATE('01-01-1995', 'DD/MM/YYYY') + ( level * 7 ) - 7) || ' until ' || to_char( TO_DATE('01-01-1995', 'DD/MM/YYYY') + ( level * 7 ) - 1 ) as range
FROM DUAL
CONNECT BY LEVEL <= ( select (TO_DATE('01-01-1995', 'DD/MM/YYYY') - TO_DATE('01-01-1994','DD/MM/YYYY'))/7 countinous_weeks
from dual )
Ok - here another solution which I find a bit easier to read:
SELECT LEVEL running_number
,TO_CHAR( start_date + ( LEVEL - 1 ) * 7, 'WW' ) iso_date_week_number
,TO_CHAR( start_date + ( LEVEL - 1 ) * 7, 'DD-MM-YYYY' )
|| ' until '
|| TO_CHAR( start_date + ( LEVEL ) * 7, 'DD-MM-YYYY' )
FROM
( SELECT TO_DATE('01-01-1994', 'DD-MM-YYYY') start_date
,TO_DATE('01-01-1995', 'DD-MM-YYYY') end_date
FROM DUAL
)
CONNECT BY start_date + ( LEVEL - 1 ) * 7 < end_date;
I use connect by and for each Level I add 7 days, until I have reached the end date.

extracting total days of a month and then use it to get average sales per day

Hi i've been working on this project and need to get this.
SELECT sf.ORDER_QNT, dd.ACTUAL_DATE, dd.MONTH_NUMBER
FROM sales_fact sf,
date_dim dd
WHERE dd.date_id = sf.date_id
AND dd.MONTH_NUMBER = 1;
the result is the following:
ORDER_QNT ACTUAL_DATE MONTH_NUMBER
---------- ----------- ------------
1100 05/01/13 1
100 05/01/13 1
140 06/01/13 1
110 07/01/13 1
200 08/01/13 1
500 08/01/13 1
230 08/01/13 1
500 08/01/13 1
200 08/01/13 1
53 15/01/13 1
53 22/01/13 1
Now, I want to get the average for that month (average per day).
SELECT sum(sf.ORDER_QNT)/31 as AVGPERDAY
FROM sales_fact sf,
date_dim dd
WHERE dd.date_id = sf.date_id
AND dd.MONTH_NUMBER = 1;
The question is, instead of putting 31, how can I get the total day of the month? and how can I apply that to the SELECT query. I'm pretty good with logic(c++), but this database is pretty new to me. I'm using Oracle 11g by the way. Thank you for any help.
The question is, instead of putting 31, how can I get the total day of the month?
Pick any one solution :
1.
You can add a month to a date and substract both the dates :
ADD_MONTHS(date_col, 1) - date_col
Example :
SQL> WITH dates AS(
2 SELECT to_date('05/01/13','mm/dd/rr') dt FROM dual UNION ALL
3 SELECT to_date('06/01/13','mm/dd/rr') dt FROM dual UNION ALL
4 SELECT to_date('02/01/13','mm/dd/rr') dt FROM dual)
5 SELECT ADD_MONTHS(dt, 1) - dt num_of_days_per_month
6 from dates
7 /
NUM_OF_DAYS_PER_MONTH
---------------------
31
30
28
Or,
You can extract the last day of the month :
EXTRACT(DAY FROM LAST_DAY (date_col))
Example :
SQL> WITH dates AS(
2 SELECT to_date('05/01/13','mm/dd/rr') dt FROM dual UNION ALL
3 SELECT to_date('06/01/13','mm/dd/rr') dt FROM dual UNION ALL
4 SELECT to_date('02/01/13','mm/dd/rr') dt FROM dual)
5 SELECT EXTRACT(DAY FROM LAST_DAY(dt)) num_of_days_per_month
6 from dates
7 /
NUM_OF_DAYS_PER_MONTH
---------------------
31
30
28