Counts and divide from two different selects with dates - sql

I have a table with this kind of structure (Sample only)
ID | STATUS | DATE |
--- -------- ------
1 OPEN 31-01-2022
2 CLOSE 15-11-2021
3 CLOSE 21-10-2021
4 OPEN 11-10-2021
5 OPEN 28-09-2021
I would like to know the counts of close vs open records by week. So it will be count(close)/count(open) where close.week = open.week
If there are no matching values, need to return 0 of course.
I got to this query below
SELECT *
FROM
(SELECT COUNT(*) AS 'CLOSE', DATEPART(WEEK, DATE) AS 'WEEKSA', DATEPART(YEAR, DATE) AS 'YEARA' FROM TABLE
WHERE STATUS IN ('CLOSE')
GROUP BY DATEPART(WEEK, DATE),DATEPART(YEAR, DATE)) TMPA
FULL OUTER JOIN
(SELECT COUNT(*) AS 'OPEN', DATEPART(WEEK, DATE) AS 'WEEKSB', DATEPART(YEAR, DATE) AS 'YEARB' FROM TABLE
WHERE STATUS IN ('OPEN')
GROUP BY DATEPART(WEEK, DATE),DATEPART(YEAR, DATE)) TMPB
ON TMPA.WEEKSA = TMPB.WEEKSB AND TMPA.YEARA = TMPB.YEARB
My results are as below (sample only)
close | weeksa | yeara | open | weeksb | yearb |
------ -------- ------ ------- ------- ------
3 2 2021
1 3 2021
1 4 2021
2 20 2021 2 20 2021
7 22 2021
2 23 2021
7 26 2021
7 27 2021
2 28 2021 14 28 2021
2 29 2021
10 30
24 31 2021
2 32 2021 5 32
4 33 2021
1 34 2021 13 34 2021
6 35 2021
1 36 2021
1 38 2021
1 39 2021
2 41 2021
4 43 2021
1 45 2021
2 46 2021 25 46 2021
1 47 2021 5 47 2021
4 48 2021
1 49 2021 20 49 2021
1 50 2021 17 50 2021
1 51 2021
How do I do the math now?
If I do another select the query fails. So I guess either syntax is bad or the whole concept is wrong.
The required result should look like this (Sample)
WEEK | YEAR | RATIO |
----- ------ -------
2 2021 0
3 2021 0
4 2021 0
5 2021 0.93
20 2021 0.1
22 2021 0
23 2021 0
26 2021 0
1 2022 0.75
2 2022 0.23
4 2022 0.07
Cheers!

I have added some test data to check the logic, adding the same in the code.
;with cte as(
select 1 ID, 'OPEN' as STATUS, cast('2021 -01-31' as DATE) DATE
union select 10 ID, 'CLOSE' as STATUS, cast('2021 -01-31' as DATE) DATE
union select 11 ID, 'CLOSE' as STATUS, cast('2021 -01-31' as DATE) DATE
union select 12 ID, 'CLOSE' as STATUS, cast('2021 -01-31' as DATE) DATE
union select 22 ID, 'CLOSE' as STATUS, cast('2021 -01-31' as DATE) DATE
union select 32 ID, 'CLOSE' as STATUS, cast('2021 -01-31' as DATE) DATE
union select 2,'CLOSE',cast('2021-11-28' as DATE)
union select 3,'CLOSE',cast('2021-10-21' as DATE)
union select 8,'CLOSE',cast('2021-10-21' as DATE)
union select 9,'CLOSE',cast('2021-10-21' as DATE)
union select 4,'OPEN', cast('2021-10-11' as DATE)
union select 5,'CLOSE', cast('2021-09-28' as DATE)
union select 6,'OPEN', cast('2021-09-27' as DATE)
union select 7,'CLOSE', cast('2021-09-26' as DATE) )
, cte2 as (
select DATEPART(WEEK,date) as week_number,* from cte)
,cte3 as(
select week_number,year(date) yr,count(case when status = 'open' then 1 end)open_count,count(case when status <> 'open' then 1 end) close_count from cte2 group by week_number,year(date))
select week_number as week,yr as year,
cast(case when open_count = 0 then 1.0 else open_count end /
case when close_count = 0 then 1.0 else close_count end as numeric(3,2)) as ratio
from cte3

Related

SQL calculate MTD out of YTD

I try to calculate in SQL MTD cost of a table.
I have following table:
year
user
month
type
cost
blank
2021
a
1
type1
10
0
2021
a
2
type1
20
0
2021
a
3
type1
35
0
2022
a
1
type1
5
0
2022
a
2
type1
35
0
2021
b
1
type1
10
0
2021
b
2
type1
30
0
What I need to have is now the MTD cost as per year, user and type
year
user
month
type
cost
costMTD
blank
2021
a
1
type1
10
10
0
2021
a
2
type1
20
10
0
2021
a
3
type1
35
15
0
2022
a
1
type1
5
5
0
2022
a
2
type1
35
30
0
2021
b
1
type1
10
10
0
2021
b
2
type1
30
20
0
Can i do this with a query in SQL?
I tried like this but it doesn't work:
SELECT t1.year, t1.user, t1.month, t1.type, t1.cost,
iif(t1.month = '1', t1.cost, t1.cost- t2.cost) AS costMTD, 0 AS blank
FROM dbo.usercosts AS t1 INNER JOIN dbo.usercosts AS t2
ON t1.year = t2.year
AND t1.user= t2.user
AND t1.type= t2.type
AND t2.month = iif(t2.month = '1', '1', t1.month - 1)
You can use LAG to see the previous row's cost.
select
year, user, month, type, cost,
cost - coalesce(lag(cost) over(partition by user order by year, month), 0) as costmtd,
0 as blank
from dbo.usercosts as
order by user, year, month;

ORACLE: Splitting a string into multiple rows

I am trying to split a string "HHHWWWHHHHWWWWWHHWWWWWHHWWWWWHH"
is there any possibility to make like :
H
H
H
W
W
W
BRANCH_CODE YEAR MONTH HOLIDAY_LIST
1 001 2021 1 HHHWWWHHHHWWWWWHHWWWWWHHWWWWWHH
2 001 2021 2 WWWWWHHWWWWWHHWWWWWHHWHWWWHH
From Oracle 12, you can use:
SELECT branch_code, year, month, day, holiday
FROM branches
CROSS JOIN LATERAL (
SELECT LEVEL AS day,
SUBSTR(holiday_list, LEVEL, 1) AS holiday
FROM DUAL
CONNECT BY LEVEL <= LENGTH(holiday_list)
)
Which, for the sample data:
CREATE TABLE branches (BRANCH_CODE, YEAR, MONTH, HOLIDAY_LIST) AS
SELECT '001', 2021, 1, 'HHHWWWHHHHWWWWWHHWWWWWHHWWWWWHH' FROM DUAL UNION ALL
SELECT '001', 2021, 2, 'WWWWWHHWWWWWHHWWWWWHHWHWWWHH' FROM DUAL
Outputs:
BRANCH_CODE
YEAR
MONTH
DAY
HOLIDAY
001
2021
1
1
H
001
2021
1
2
H
001
2021
1
3
H
001
2021
1
4
W
...
...
...
...
...
001
2021
1
29
W
001
2021
1
30
H
001
2021
1
31
H
001
2021
2
1
W
001
2021
2
2
W
001
2021
2
3
W
...
...
...
...
...
001
2021
2
26
W
001
2021
2
27
H
001
2021
2
28
H
db<>fiddle here
If it's Oracle:
with data AS (
select 'WWWWWHHWWWWWHHWWWWWHHWHWWWHH' AS letters
from dual
)
select substr (
letters,
level,
1
) value
from data
connect by level <=
length ( letters )

I want cummulative row for a given input

I have table like below
Months cnt
Jan 2
Feb 3
Mar 5
I want output like below
Months cnt
Jan 2
Feb 2
Feb 3
Mar 2
Mar 3
Mar 5
I tried using below query but not getting the required output
Select distinct months, cnt, level
from (select months, cnt, rownum row_cnt
from tablename)
connect by level <= row_cnt
Order by months, cnt, level
Here's one option which converts month's names into their ordinal number (1 for Jan, 2 for Feb, etc.) and then - using self join - returns the result.
SQL> with test (months, cnt) as
2 (select 'jan', 2 from dual union all
3 select 'feb', 3 from dual union all
4 select 'mar', 5 from dual
5 ),
6 temp as
7 (select
8 months,
9 to_number(to_char(to_date(months, 'mon', 'nls_date_language=english'), 'mm')) mon,
10 cnt
11 from test
12 )
13 select a.months, b.cnt
14 from temp a join temp b on a.mon >= b.mon
15 order by a.mon, b.cnt;
MON CNT
--- ----------
jan 2
feb 2
feb 3
mar 2
mar 3
mar 5
6 rows selected.
SQL>
You need a self join:
select t.months, tt.cnt
from tablename t inner join tablename tt
on extract(month from to_date(t.Months,'MON')) >= extract(month from to_date(tt.Months,'MON'))
order by extract(month from to_date(t.Months,'MON')), tt.cnt
See the demo.
Results:
> MONTHS | CNT
> :----- | --:
> Jan | 2
> Feb | 2
> Feb | 3
> Mar | 2
> Mar | 3
> Mar | 5

Add missing data from previous month or year cumulatively

Say I have the following data:
select 1 id, 'A' name, '2007' year, '04' month, 5 sales from dual union all
select 2 id, 'A' name, '2007' year, '05' month, 2 sales from dual union all
select 3 id, 'B' name, '2008' year, '12' month, 3 sales from dual union all
select 4 id, 'B' name, '2009' year, '12' month, 56 sales from dual union all
select 5 id, 'C' name, '2009' year, '08' month, 89 sales from dual union all
select 13 id,'B' name, '2016' year, '01' month, 10 sales from dual union all
select 14 id,'A' name, '2016' year, '02' month, 8 sales from dual union all
select 15 id,'D' name, '2016' year, '03' month, 12 sales from dual union all
select 16 id,'E' name, '2016' year, '04' month, 34 sales from dual
I want to cumulatively add up all the sales across all years and their respective periods (months). The output should look like the following:
name year month sale opening bal closing bal
A 2007 04 5 0 5
A 2007 05 2 5 7
B 2008 12 3 12 15
A 2008 04 0 5 5 -- to be generated
A 2008 05 0 7 7 -- to be generated
B 2009 12 56 15 71
C 2009 08 89 71 160
A 2009 04 0 5 5 -- to be generated
A 2009 05 0 7 7 -- to be generated
B 2016 01 10 278 288
B 2016 12 0 71 71 -- to be generated
A 2016 02 8 288 296
A 2016 04 0 5 5 -- to be generated
A 2016 05 0 7 7 -- to be generated
D 2016 03 12 296 308
E 2016 04 34 308 342
C 2016 08 0 160 160 -- to be generated
The Opening balance is the closing balance of previous month, and if it goes into next year than the opening balance for next year is the closing balance of the previous year. It should be able to work like this for subsequent years. I've got this part working. However, I don't know how to get around ths missing in say 2009 that exists in 2008. For instance the key A,2008,04 and also A,2008,05 does not exist in 2009 and the code should be able to add it in 2009 like above. Same applies for other years and months.
I'm working on Oracle 12c.
Thanks in advance.
A variation on #boneists approach, starting with your sample data in a CTE:
with t as (
select 1 id, 'A' name, '2007' year, '04' month, 5 sales from dual union all
select 2 id, 'A' name, '2007' year, '05' month, 2 sales from dual union all
select 3 id, 'B' name, '2008' year, '12' month, 3 sales from dual union all
select 4 id, 'B' name, '2009' year, '12' month, 56 sales from dual union all
select 5 id, 'C' name, '2009' year, '08' month, 89 sales from dual union all
select 13 id,'B' name, '2016' year, '01' month, 10 sales from dual union all
select 14 id,'A' name, '2016' year, '02' month, 8 sales from dual union all
select 15 id,'D' name, '2016' year, '03' month, 12 sales from dual union all
select 16 id,'E' name, '2016' year, '04' month, 34 sales from dual
),
y (year, rnk) as (
select year, dense_rank() over (order by year)
from (select distinct year from t)
),
r (name, year, month, sales, rnk) as (
select t.name, t.year, t.month, t.sales, y.rnk
from t
join y on y.year = t.year
union all
select r.name, y.year, r.month, 0, y.rnk
from y
join r on r.rnk = y.rnk - 1
where not exists (
select 1 from t where t.year = y.year and t.month = r.month and t.name = r.name
)
)
select name, year, month, sales,
nvl(sum(sales) over (partition by name order by year, month
rows between unbounded preceding and 1 preceding), 0) as opening_bal,
nvl(sum(sales) over (partition by name order by year, month
rows between unbounded preceding and current row), 0) as closing_bal
from r
order by year, month, name;
Which gets the same result too, though it also doesn't match the expected results in the question:
NAME YEAR MONTH SALES OPENING_BAL CLOSING_BAL
---- ---- ----- ---------- ----------- -----------
A 2007 04 5 0 5
A 2007 05 2 5 7
A 2008 04 0 7 7
A 2008 05 0 7 7
B 2008 12 3 0 3
A 2009 04 0 7 7
A 2009 05 0 7 7
C 2009 08 89 0 89
B 2009 12 56 3 59
B 2016 01 10 59 69
A 2016 02 8 7 15
D 2016 03 12 0 12
A 2016 04 0 15 15
E 2016 04 34 0 34
A 2016 05 0 15 15
C 2016 08 0 89 89
B 2016 12 0 69 69
The y CTE (feel free to use more meaningful names!) generates all the distinct years from your original data, and also adds a ranking, so 2007 is 1, 2008 is 2, 2009 is 3, and 2016 is 4.
The r recursive CTE combines your actual data with dummy rows with zero sales, based on the name/month data from previous years.
From what that recursive CTE produces you can do your analytic cumulative sum to add the opening/closing balances. This is using windowing clauses to decide which sales values to include - essentially the opening and closing balances are the sum of all values up to this point, but opening doesn't include the current row.
This is the closest I can get to your result, although I realise it's not an exact match. For example, your opening balances don't look correct (where did the opening balance of 12 come from for the output row for id = 3?). Anyway, hopefully the following will enable you to amend as appropriate:
with sample_data as (select 1 id, 'A' name, '2007' year, '04' month, 5 sales from dual union all
select 2 id, 'A' name, '2007' year, '05' month, 2 sales from dual union all
select 3 id, 'B' name, '2008' year, '12' month, 3 sales from dual union all
select 4 id, 'B' name, '2009' year, '12' month, 56 sales from dual union all
select 5 id, 'C' name, '2009' year, '08' month, 89 sales from dual union all
select 13 id, 'B' name, '2016' year, '01' month, 10 sales from dual union all
select 14 id, 'A' name, '2016' year, '02' month, 8 sales from dual union all
select 15 id, 'D' name, '2016' year, '03' month, 12 sales from dual union all
select 16 id, 'E' name, '2016' year, '04' month, 34 sales from dual),
dts as (select distinct year
from sample_data),
res as (select sd.name,
dts.year,
sd.month,
nvl(sd.sales, 0) sales,
min(sd.year) over (partition by sd.name, sd.month) min_year_per_name_month,
sum(nvl(sd.sales, 0)) over (partition by name order by to_date(dts.year||'-'||sd.month, 'yyyy-mm')) - nvl(sd.sales, 0) as opening,
sum(nvl(sd.sales, 0)) over (partition by name order by to_date(dts.year||'-'||sd.month, 'yyyy-mm')) as closing
from dts
left outer join sample_data sd partition by (sd.name, sd.month) on (sd.year = dts.year))
select name,
year,
month,
sales,
opening,
closing
from res
where (opening != 0 or closing != 0)
and year >= min_year_per_name_month
order by to_date(year||'-'||month, 'yyyy-mm'),
name;
NAME YEAR MONTH SALES OPENING CLOSING
---- ---- ----- ---------- ---------- ----------
A 2007 04 5 0 5
A 2007 05 2 5 7
A 2008 04 0 7 7
A 2008 05 0 7 7
B 2008 12 3 0 3
A 2009 04 0 7 7
A 2009 05 0 7 7
C 2009 08 89 0 89
B 2009 12 56 3 59
B 2016 01 10 59 69
A 2016 02 8 7 15
D 2016 03 12 0 12
A 2016 04 0 15 15
E 2016 04 34 0 34
A 2016 05 0 15 15
C 2016 08 0 89 89
B 2016 12 0 69 69
I've used Partition Outer Join to link any month and name combination in the table (in my query, the sample_data subquery - you wouldn't need that subquery, you'd just use your table instead!) to any year in the same table, and then working out the opening / closing balances. I then discard any rows that have an opening and closing balance of 0.

SQL Aggregate function: Sum of some rows in another row

I have following scenario with sql server 2008
**** Original Result ****
================================================
Year month Category Count_days
================================================
2001 09 Leave 03
2001 09 Worked Below 8hrs 18
2001 09 Worked Above 8hrs 05
2001 09 Present 0 <----- current value
2001 10 Leave 01
2001 10 Worked Below 8hrs 10
2001 10 Worked Above 8hrs 09
2001 10 Present 0 <------ current value
Following is the criteria
criteria
===========
Present Count of 'x'th Month = SUM(Worked Below 8hrs count of 'x'th month) +
SUM(Worked Above 8hrs count of 'x'th month )
;where x is the month
I want following result with satisfying above criteria
**** Expected Result ****
===============================================
Year month Category Count_days
================================================
2001 09 Leave 03
2001 09 Worked Below 8hrs 18
2001 09 Worked Above 8hrs 05
2001 09 Present 23 <-----(expecting sum 18+05 =23)
2001 10 Leave 01
2001 10 Worked Below 8hrs 10
2001 10 Worked Above 8hrs 09
2001 10 Present 19 <-----(expecting sum 10+09 = 19)
Problem is the original result is generated by very complex query hence cant call same set again i.e.
Cannot use this (This will hamper the performance of my application.)
=================
select * from original (some join) select * from original
may be need to use the single query or It can be subquery, use of aggregate function etc.
Expecting any aggregation trick to generate my expected result????
Please help me out guys....
you can use sum as analytic function
SELECT
year, month, cat, count_days as count_days_orig,
case cat
when 'Present'
then
sum (
case
when cat in ('Worked Below 8hrs', 'Worked Above 8hrs')
then count_days
else 0
end
)
over (partition by year, month)
else count_days
end as count_days_calc
FROM
(
SELECT 2001 as year, 09 as month , 'Leave ' as cat , 03 as count_days FROM dual
UNION all
SELECT 2001 as year, 09 as month , 'Worked Below 8hrs' as cat , 18 as count_days FROM dual
UNION all
SELECT 2001 as year, 09 as month , 'Worked Above 8hrs' as cat , 05 as count_days FROM dual
UNION all
SELECT 2001 as year, 09 as month , 'Present' as cat , 0 as count_days FROM dual
UNION all
SELECT 2001 as year, 10 as month , 'Leave ' as cat , 01 as count_days FROM dual
UNION all
SELECT 2001 as year, 10 as month , 'Worked Below 8hrs' as cat , 10 as count_days FROM dual
UNION all
SELECT 2001 as year, 10 as month , 'Worked Above 8hrs' as cat , 09 as count_days FROM dual
UNION all
SELECT 2001 as year, 10 as month , 'Present' as cat , 0 as count_days FROM dual
)
;
year month cat count_days_orig count_days_calc
--------------------------------------------------------------------------
2001 9 Leave 3 3
2001 9 Worked Below 8hrs 18 18
2001 9 Worked Above 8hrs 5 5
2001 9 Present 0 23
2001 10 Leave 1 1
2001 10 Worked Below 8hrs 10 10
2001 10 Worked Above 8hrs 9 9
2001 10 Present 0 19
Something like this, don't know if column names are correct and stuff.
SELECT Year, month, category,
CASE Category
WHEN 'Present'
THEN (
SELECT Sum(T2.Count_days)
FROM table T2
WHERE T2.year = T.year
AND T2.month = T.month
AND T2.Category NOT IN ('Present', 'Leave')
)
ELSE Count_days
END
FROM table T
But this really feels like a wrong design...