ORACLE: Splitting a string into multiple rows - sql

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 )

Related

Counts and divide from two different selects with dates

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

Sum Non Null Values Block in SQL

How to add Non Null Values block by block without any grouping criteria :
Example input :
Machine Value DateTime
a null 1 Dec 2021 8:34AM
a 2 1 Dec 2021 8:35AM
a 1 1 Dec 2021 9:34AM
a 3 1 Dec 2021 10:11AM
a null 1 Dec 2021 11:14AM
a null 1 Dec 2021 11:16AM
a 5 1 Dec 2021 11:58AM
a 6 1 Dec 2021 11:59AM
Example output :
Machine Value DateTime SumValue
a null 1 Dec 2021 8:34AM
a 2 1 Dec 2021 8:35AM
a 1 1 Dec 2021 9:34AM
a 3 1 Dec 2021 10:11AM 6
a null 1 Dec 2021 11:14AM
a null 1 Dec 2021 11:16AM
a 5 1 Dec 2021 11:58AM
a 6 1 Dec 2021 11:59AM 11
I don't have any other grouping criteria other than device column , but I want sum block wise
You need to define the groups and use windowed SUM():
Table:
SELECT *
INTO Data
FROM (VALUES
('2021-12-12T09:00:01', 'a', null),
('2021-12-12T09:00:02', 'a', 2),
('2021-12-12T09:00:03', 'a', 1),
('2021-12-12T09:00:04', 'a', 3),
('2021-12-12T09:00:05', 'a', null),
('2021-12-12T09:00:06', 'a', null),
('2021-12-12T09:00:07', 'a', 5),
('2021-12-12T09:00:08', 'a', 6)
) v (Date, Machine, Value)
Statement:
SELECT
Date, Machine, Value,
CASE
WHEN ROW_NUMBER() OVER (PARTITION BY Machine, GroupNumber ORDER BY Date DESC) = 1
THEN SUM(Value) OVER (PARTITION BY Machine, GroupNumber ORDER BY (SELECT NULL))
END AS SumValue
FROM (
SELECT
*,
SUM(CASE WHEN Value IS NULL THEN 1 ELSE 0 END) OVER (PARTITION BY Machine ORDER BY Date) AS GroupNumber
FROM Data
) t
ORDER BY Machine, Date
Result:
Date Machine Value SumValue
2021-12-12T09:00:01 a
2021-12-12T09:00:02 a 2
2021-12-12T09:00:03 a 1
2021-12-12T09:00:04 a 3 6
2021-12-12T09:00:05 a
2021-12-12T09:00:06 a
2021-12-12T09:00:07 a 5
2021-12-12T09:00:08 a 6 11

Pivot a Date Range into multiple rows

I have a table T in this format:
ClientName
StartMonth
EndingMonth
X
Dec 2018
Jan 2021
I want the output of my query to be:
ClientName
MonthRange
Year #
X
Dec 2018-Nov 2019
1
X
Dec 2019-Nov 2020
2
X
Dec 2020-Nov 2021
3
Can someone help me what is the best way to tackle this problem?
Try this:
WITH
indata(clientname,startmonth,endmonth) AS(
SELECT 'x',DATE '2018-12-01', DATE '2021-01-01'
)
,
-- a series of at least 3 integers - no other way ...
y(y) AS (
SELECT 1
UNION ALL SELECT 2
UNION ALL SELECT 3
UNION ALL SELECT 4
)
SELECT
clientname
, TO_CHAR(ADD_MONTHS(startmonth,(y-1)*12),'Mon-YYYY')
||'-'
||TO_CHAR(ADD_MONTHS(startmonth,(y-1)*12+11),'Mon-YYYY') AS monthrange
, y AS "year#"
FROM indata CROSS JOIN y
WHERE ADD_MONTHS(startmonth,(y-1)*12) <= endmonth
ORDER BY y;
clientname|monthrange |year#
x |Dec-2018-Nov-2019| 1
x |Dec-2019-Nov-2020| 2
x |Dec-2020-Nov-2021| 3

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

Sliding window aggregate for year-week in bigquery

My question is about sliding window sum up in bigquery.
I have a table like the following
run_id year_week value
001 201451 5
001 201452 8
001 201501 1
001 201505 5
003 201352 8
003 201401 1
003 201405 5
Here for each year the week can range from 01 to 53. For example year 2014 has last week which is 201452 but year 2015 has last week which is 201553. If it makes life easier I only have 5 years, 2013, 2014, 2015, 2016 and 2017 and only year 2015 has weeks those go upto 53.
Now for each run I am trying to get a sliding window sum of the values. Each year_week would assume the sum of the values next 5 year_week (including itself) for the current run_id (e.g. 001). For example the following could be a an output from the current table
run_id year_week aggregate_sum
001 201451 5+8+1+0+0
001 201452 8+1+0+0+0
001 201501 1+0+0+0+5
001 201502 0+0+0+5+0
001 201503 0+0+5+0+0
001 201504 0+5+0+0+0
001 201505 5+0+0+0+0
003 201352 8+1+0+0+0
003 201401 1+0+0+0+5
003 201402 0+0+0+5+0
003 201403 0+0+5+0+0
003 201404 0+5+0+0+0
003 201405 5+0+0+0+0
To explain what is happening, the next 5 weeks for 201451 including itself would be 201451,201452,201501,201502,201503 . If there is a value for those weeks in the table for current run_id we just sum them up which would be, 5+8+1+0+0, because the corresponding value for a year_week is 0 if it is not in the table.
Is it possible to do it using sliding window operation in bigquery?
Below is for BigQuery Standard SQL
#standardSQL
WITH weeks AS (
SELECT 100* year + week year_week
FROM UNNEST([2013, 2014, 2015, 2016, 2017]) year,
UNNEST(GENERATE_ARRAY(1, IF(EXTRACT(ISOWEEK FROM DATE(1+year,1,1)) = 1, 52, 53))) week
), temp AS (
SELECT i.run_id, w.year_week, d.year_week week2, value
FROM weeks w
CROSS JOIN (SELECT DISTINCT run_id FROM `project.dataset.table`) i
LEFT JOIN `project.dataset.table` d
USING(year_week, run_id)
)
SELECT * FROM (
SELECT run_id, year_week,
SUM(value) OVER(win) aggregate_sum
FROM temp
WINDOW win AS (
PARTITION BY run_id ORDER BY year_week ROWS BETWEEN CURRENT row AND 4 FOLLOWING
)
)
WHERE NOT aggregate_sum IS NULL
You can test / play with above using dummy data from your question as below
#standardSQL
WITH `project.dataset.table` AS (
SELECT '001' run_id, 201451 year_week, 5 value UNION ALL
SELECT '001', 201452, 8 UNION ALL
SELECT '001', 201501, 1 UNION ALL
SELECT '001', 201505, 5
), weeks AS (
SELECT 100* year + week year_week
FROM UNNEST([2013, 2014, 2015, 2016, 2017]) year,
UNNEST(GENERATE_ARRAY(1, IF(EXTRACT(ISOWEEK FROM DATE(1+year,1,1)) = 1, 52, 53))) week
), temp AS (
SELECT i.run_id, w.year_week, d.year_week week2, value
FROM weeks w
CROSS JOIN (SELECT DISTINCT run_id FROM `project.dataset.table`) i
LEFT JOIN `project.dataset.table` d
USING(year_week, run_id)
)
SELECT * FROM (
SELECT run_id, year_week,
SUM(value) OVER(win) aggregate_sum
FROM temp
WINDOW win AS (
PARTITION BY run_id ORDER BY year_week ROWS BETWEEN CURRENT row AND 4 FOLLOWING
)
)
WHERE NOT aggregate_sum IS NULL
-- ORDER BY run_id, year_week
with result as
Row run_id year_week aggregate_sum
1 001 201447 5
2 001 201448 13
3 001 201449 14
4 001 201450 14
5 001 201451 14
6 001 201452 9
7 001 201501 6
8 001 201502 5
9 001 201503 5
10 001 201504 5
11 001 201505 5
12 003 201348 8
13 003 201349 9
14 003 201350 9
15 003 201351 9
16 003 201352 9
17 003 201401 6
18 003 201402 5
19 003 201403 5
20 003 201404 5
21 003 201405 5
note; this is for - I only have 5 years, 2013, 2014, 2015, 2016 and 2017 but can easily be extended in weeks CTE