how to split the date period in sql? - sql

Info:
Date 1 nov 2016 to 5 nov 2016 = 10 members;
then people joined
Date 3 nov 2016 to 7 Nov 2016 =12 members.
Now I want the data for date 1 nov 2016 to 10 nov 2016.
Output will be
From date To date No.of members
1 nov 2016 2 nov 2016 10
3 nov 2016 5 nov 2016 22
6 nov 2016 7 nov 2016 12
Kindly Give me the suggestion to make query for that

In standard SQL, you would do this with a conditional sum:
with dates as (
select startdate as dte, cnt
from t
union all
select startdate + interval '1 day', - cnt
from t
)
select dte, sum(sum(cnt)) over (order by dte)
from dates
group by dte
order by dte;

Related

how can I generate this list of data with a SQL query?

year
month
2021
1
2021
2
2021
3
2021
4
2021
5
2021
6
2021
7
2021
8
2021
9
2021
10
2021
11
2021
12
2022
1
2022
2
2022
3
2022
4
2022
5
2022
6
2022
7
2022
8
2022
9
2022
10
2022
11
2022
12
I can get one column fine, for example
SELECT * FROM generate_series(1,12) as month
but I can't find how to get another column next to it that is generated rather than joined to an actual table.
You may use a cross join:
SELECT y.year, m.month
FROM (SELECT 2020 + generate_series(1, 2) AS year) y
CROSS JOIN (SELECT generate_series(1, 12) AS month) m
ORDER BY y.year, m.month;
Demo
Create a list of dates one for each month:
select extract(year from dt) as "year",
extract(month from dt) as "month"
from generate_series(date '2021-01-01',
date '2022-12-31',
interval '1 month') as g(dt)
order by g.dt;
select
extract(year from d) "year",
extract(month from d) "month"
from generate_series ('2021-01-01', '2022-12-01', interval '1 month') as d;
You can use a CASE statement
Select year.*,
CASE
WHEN year = 2021 THEN generate_series(1,12)
WHEN year = 2022 THEN generate_series(1,12)
END as month
from generate_series(2021,2022) as year
Using integer arithmetic
select 2021 + m / 12 "year", m % 12 + 1 "month"
from generate_series(0, 23) as m

Daily status using prior value as backfill

This is the input table (tbl_statuslog):
User_id
isactive
date
1
1
1 Feb 2021
2
1
1 Feb 2021
3
1
2 Feb 2021
2
0
5 Feb 2021
4
1
10 Feb 2021
4
0
10 Feb 2021
3
0
12 Feb 2021
create table tbl_statuslog
(
[user_id] int,
[isactive] bit,
[date] datetime
);
insert into tbl_statuslog (user_id, isactive, date) values
(1, 1, ' 1 Feb 2021'),
(2, 1, ' 1 Feb 2021'),
(3, 1, ' 2 Feb 2021'),
(2, 0, ' 5 Feb 2021'),
(4, 1, '10 Feb 2021'),
(4, 0, '10 Feb 2021'),
(3, 0, '12 Feb 2021');
I want to return output, given today is 16 Feb:
User_id
isactive
date
1
1
1 Feb 2021
.
.
.
1
1
16 Feb 2021
2
1
1 Feb 2021
.
.
2
1
4 Feb 2021
2
0
5 Feb 2021
.
.
2
0
16 Feb 2021
3
1
2 Feb 2021
.
.
3
1
11 Feb 2021
3
0
12 Feb 2021
.
.
3
0
16 Feb 2021
4
0
10 Feb 2021
.
.
4
0
16 Feb 2021
I have used following SQL to get the list of all dates.
DECLARE #StartDateTime DATETIME
DECLARE #EndDateTime DATETIME
SET #StartDateTime = '2021-02-01'
SET #EndDateTime = GETDATE();
WITH DateRange(DateData) AS
(
SELECT #StartDateTime as Date
UNION ALL
SELECT DATEADD(d,1,DateData)
FROM DateRange
WHERE DateData < #EndDateTime
)
SELECT DateData
FROM DateRange
OPTION (MAXRECURSION 0)
GO
Now I am thinking of doing a left join of this table with tbl_statuslog table. Thus I have a date irrespective of whether date exists in tbl_statuslog or not.
Then I want to backfill the isactive value for the date and user_id based on the previous value.
Can I use window function- example partition by user_id, order by date to achieve the result?
I'm blocked here because when evaluating the isactive value for a date and userid, how can I get access to prior 1 day value, or value of 2 days prior (and so on) when the previous day doesn't have value?
This answers the original version of the question.
I would suggest using recursion but only for each row:
with ts as (
select ts.*,
lead(date) over (partition by user_id order by date) as next_date
from tbl_statuslog ts
),
cte as (
select user_id, date, isactive,
coalesce(dateadd(day, -1, next_date), convert(date, getdate())) as end_date
from ts
union all
select user_id, dateadd(day, 1, date), isactive, end_date
from cte
where date < end_date
)
select user_id, date, isactive
from cte;
Here is a db<>fiddle.

How to grab values years dynamically in Oracle/SQL depending on whether they are available (not Null) or not (Null)

Let's say that I have a table that has a person with their weight by year.
NAME 2012 2013 2014 2015 2016 2017 2018 2019
JOHN 180 185 192 205 199 198 193 null
MIKE 190 191 191 195 195 195 195 195
What I am trying to do is grab an min/max of the last NON NULL years. (as shown in the result set below)
NAME MIN MAX
JOHN 192 205
MIKE 195 195
Below is what I've tried, is there a better way to do this?
SELECT
CASE WHEN 2019 IS NULL
AND 2018 > 2017 > 2016 > 2015 > 2014
THEN 2018
WHEN 2019 IS NULL
AND 2017 > 2016 > 2015 > 2014 > 2018
THEN 2017
WHEN 2019 IS null
AND 2016 > 2017 > 2018 > 2015 > 2014
THEN 2016
WHEN 2019 IS NULL
AND 2015 > 2018 > 2017 > 2016 > 2014
THEN 2015
WHEN 2019 IS NULL
AND 2014 > 2018 > 2017 > 2016 > 2015
THEN 2014
WHEN 2019 IS NOT NULL
AND 2019 > 2018 > 2017 > 2016 > 2015 > 2014
THEN 2019
WHEN 2019 IS NOT NULL
AND 2018 > 2017 > 2016 > 2015 > 2014 > 2018
THEN 2018
WHEN 2019 IS NOT null
AND 2017 > 2017 > 2018 > 2015 > 2014 > 2019
THEN 2017
WHEN 2019 IS NOT NULL
AND 2016> 2015 > 2018 > 2017 > 2019 > 2014
THEN 2016
WHEN 2019 IS NOT NULL
AND 2015 > 2014 > 2018 > 2017 > 2016 > 2019
THEN 2015
WHEN 2019 IS NOT NULL
AND 2014 > 2015 > 2015 > 2018 > 2017 > 2016 > 2019
THEN 2014
END as MAX
Thanks in advance
FROM Table
WHERE
Your table design is not optimal, and it would be best to normalize it get each weight per year on a separate record, instead of column. That being said, if you must continue, one trick you can do is to use COALESCE to replace a NULL column weight with -1. This will effectively take it out of the picture. Then, use the scalar GREATEST function to find the legitimate maximum:
SELECT
GREATEST(COALESCE("2019", -1),
COALESCE("2018", -1),
COALESCE("2017", -1),
COALESCE("2016", -1),
COALESCE("2015", -1),
COALESCE("2014", -1),
COALESCE("2013", -1),
COALESCE("2012", -1)) AS max_weight
FROM yourTable;
Note: In the off chance that every column might be NULL, then the above call to GREATEST would return -1. In this case, you can either leave that value there as a market for no valid data, or you could wrap in a CASE expression and replace with some other value.
Greatest() and Least() functions would help in this case
Greatest() function compares the columns and provide maximum value from those columns and Least() function provide minimum value from those columns.
select name,
LEAST(2012,2013,2014,2015,2016,2017,2018) as MIN_WEIGHT
, GREATEST(2012,2013,2014,2015,2016,2017,2018) as MAX_WEIGHT
from your_table
/
Regarding your remark on Tim's answer
if 2019 is null, we only want to calculate the max from 2018-2013. if
2019 is available only 2019-2014
you want min/max over 6 years.
The easiest way is to normalize the data first using UNPIVOT, by default this removes NULLs:
SELECT NAME, weight, yr,
Row_Number() Over (PARTITION BY NAME ORDER BY yr DESC) AS rn
FROM mytable
UNPIVOT(weight
FOR yr
IN (
y2012 as 2012
,y2013 as 2013
,y2014 as 2014
,y2015 as 2015
,y2016 as 2016
,y2017 as 2017
,y2018 as 2018
,y2019 as 2019
)
)
Then you apply your logic using Windowed Aggregates to return the last 6 years only. Using row_number you get the last 6 rows
WITH cte AS
(
SELECT NAME, weight, yr,
Row_Number() Over (PARTITION BY NAME ORDER BY yr DESC) AS rn
FROM mytable
UNPIVOT(weight
FOR yr
IN (
y2012 as 2012
,y2013 as 2013
,y2014 as 2014
,y2015 as 2015
,y2016 as 2016
,y2017 as 2017
,y2018 as 2018
,y2019 as 2019
)
)
)
SELECT NAME, Min(weight), Max(weight), Min(yr), Max(yr)
FROM cte
WHERE rn BETWEEN 1 AND 6
GROUP BY NAME
Using max you get the rows from the last 6 years (which will return a different result if there are additional NULLs):
WITH cte AS
(
SELECT NAME, weight, yr,
Max(yr) Over (PARTITION BY NAME) AS maxyr
FROM mytable
UNPIVOT(weight
FOR yr
IN (
y2012 as 2012
,y2013 as 2013
,y2014 as 2014
,y2015 as 2015
,y2016 as 2016
,y2017 as 2017
,y2018 as 2018
,y2019 as 2019
)
)
)
SELECT NAME, Min(weight), Max(weight), Min(yr), Max(yr)
FROM cte
WHERE yr BETWEEN maxyr -5 AND maxyr
GROUP BY NAME
See dbfiddle

How do I build ISO Week Number table programatically in T-SQL query?

Anyone knows how to built temp table of week using T-SQL query?
I heard there has a lot of type of calculations for that, Gregorian or etc... My needs are ISO Week No and bind to temp table depends on week no.
The temp table has 2 columns : ISOWeekNo and WeekName
ISOWeekNo WeekName
1 01 Jan 2013 To 07 Jan 2013
2 08 Jan 2013 To 14 Jan 2013
How do I build programmatically in T-SQL Query based on ISO Week No?
Updated : I want to pass the parameter year only. e.g : 2013
EDIT: Added WHERE clause to terminate for sought year only.
This seems to match the Wikipedia description and I am sure there is room for optimisation.
Mikael, I copied your formatting code for the friendly column, thank you.
This code will work on SQL Server 2008 onwards because of the use of the ISOWEEK datepart.
use tempdb
go
DECLARE #Year SMALLINT = 2013
,#FirstISOWKDay DATETIME
;WITH FindISOWEEKFirstDay AS
(
SELECT DT = DATEADD(DAY, -7, DATEFROMPARTS(#Year, 1, 1))
UNION ALL
SELECT DATEADD(DAY, 1, DT)
FROM FindISOWEEKFirstDay
WHERE DATEADD(DAY, 1, DT) < DATEADD(DAY, 14, DATEFROMPARTS(#Year, 1, 1))
)
SELECT TOP 1 #FirstISOWKDay = DT
FROM FindISOWEEKFirstDay
WHERE DATEPART(ISO_WEEK, DT) = 1
ORDER BY DT ASC -- Eliminate probability of arb sorting (Thanks Mikael)
;WITH Base10 (n) AS
(
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1
)
,Base1000 (n) AS
(
SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL))-1
FROM Base10 T1, Base10 T2, Base10 T3
)
SELECT Start = DATEADD(DAY, n*7, #FirstISOWKDay)
,[End] = DATEADD(DAY, n*7 + 6, #FirstISOWKDay)
,Friendly = CONVERT(VARCHAR(101), DATEADD(DAY, n*7, #FirstISOWKDay), 106)+' To '+CONVERT(VARCHAR(101), DATEADD(DAY, n*7 + 6, #FirstISOWKDay), 106)
,ISOWEEK = DATEPART(ISO_WEEK, DATEADD(DAY, n*7, #FirstISOWKDay))
FROM Base1000
-- Filter to terminate, resulting only in sought year's calendar
WHERE DATEPART(YEAR, DATEADD(DAY, n*7 + 6, #FirstISOWKDay)) = #Year
declare #Year int;
set #Year = 2016;
with C as
(
select datefromparts(#Year, 1, 1) as D
union all
select dateadd(day, 1, C.D)
from C
where C.D < datefromparts(#Year, 12, 31)
)
select datepart(iso_week, C.D) as ISOWeekNo,
convert(varchar(101), min(C.D), 106)+' To '+convert(varchar(101), max(C.D), 106) as WeekName
from C
group by datepart(iso_week, C.D),
case when datepart(month, C.D) = 12 and
datepart(iso_week, C.D) > 50
then 1
else 0
end
order by min(C.D)
option (maxrecursion 0);
Result:
ISOWeekNo WeekName
----------- --------------------------
53 01 Jan 2016 To 03 Jan 2016
1 04 Jan 2016 To 10 Jan 2016
2 11 Jan 2016 To 17 Jan 2016
3 18 Jan 2016 To 24 Jan 2016
4 25 Jan 2016 To 31 Jan 2016
5 01 Feb 2016 To 07 Feb 2016
6 08 Feb 2016 To 14 Feb 2016
7 15 Feb 2016 To 21 Feb 2016
.
.
.
47 21 Nov 2016 To 27 Nov 2016
48 28 Nov 2016 To 04 Dec 2016
49 05 Dec 2016 To 11 Dec 2016
50 12 Dec 2016 To 18 Dec 2016
51 19 Dec 2016 To 25 Dec 2016
52 26 Dec 2016 To 31 Dec 2016
This may help:
Select date '2012-12-31' + level*7 WK_STARTS_DT
, to_char(date '2012-12-31' + level*7, 'IW') ISO_WEEK
, to_char(date '2012-12-31' + level*7, 'WW') WEEK
From dual
Connect By Level <=
(
Select Round( (ADD_MONTHS(TRUNC(SYSDATE,'Y'),12)-TRUNC(SYSDATE,'Y') )/7, 0) From dual
) --365/7
/
WK_STARTS_DT ISO_WEEK WEEK
------------------------------------
1/7/2013 02 01
1/14/2013 03 02
1/21/2013 04 03
......
2/4/2013 06 05
2/11/2013 07 06
......
3/4/2013 10 09
To confirm week numbers:
http://www.epochconverter.com/date-and-time/weeknumbers-by-year.php?year=2013

Calculate substraction by a specific quarter of year

I have some records after SQL generating:
YEARS MONTHS SUMMONTH SUMQUARTER QTR
----- ------ -------- ---------- ---
2009 Jan 363639 855922 1
2009 Feb 128305 855922 1
2009 Mar 363978 855922 1
2009 Apr 376633 1058871 2
2009 May 299140 1058871 2
2009 Jun 383098 1058871 2
2009 Jul 435000 1063577 3
2009 Aug 266227 1063577 3
2009 Sep 362350 1063577 3
2009 Oct 449366 1017906 4
2009 Nov 280943 1017906 4
2009 Dec 287597 1017906 4
2010 Jan 418277 661083 1
2010 Feb 129895 661083 1
2010 Mar 112911 661083 1
2010 Apr 163593 685625 2
2010 May 228505 685625 2
2010 Jun 293527 685625 2
2010 Jul 451608 1044364 3
2010 Aug 356683 1044364 3
2010 Sep 236073 1044364 3
2010 Oct 247365 798925 4
2010 Nov 414100 798925 4
2010 Dec 137460 798925 4
24 rows selected
The SUMQUARTER column sum up each quarter of a year...
The qtr specify it belongs to which quarter.
The problem is how to have a subtraction of sumquarter between 2 different years to get the specific query result?
The difference is not the: max value-min value.
It is the user-defined value that he want to input...
Let say...
For example, the user want to see the substraction(sumquarter) between 2009 qtr=2 and 2010 qtr=2,
user may change the parameter(years,qtr) of sql to view the record.
This mean the result should be: (1058871 - 685625)
Here is the SQL that I am currently using:
select years,months,summonth,sumquarter,qtr
from(
select years,months,summonth,sumhour,hours,to_char(ym, 'Q') qtr,
sum(sumhour) over(partition by years || to_char(ym, 'Q') order by years || to_char(ym, 'Q')) sumquarter,ym,
count(days) over(partition by years,months,hours) days_month
from(
select years, months, days, hours, mins, sumHour,
SUM (sumHour) OVER (PARTITION BY years,months,days) sumDay,
SUM (sumHour) OVER (PARTITION BY years,months) sumMonth,
SUM (sumHour) OVER (PARTITION BY years) sumyear,
to_date(years || months, 'YYYYMon', 'NLS_DATE_LANGUAGE=American') ym
from (
SELECT x.years, x.months, x.days, x.hours, x.mins, sum(x.value) as sumHour
FROM xmltest,
XMLTABLE ('$d/cdata/name' passing doc as "d"
COLUMNS
years integer path 'year',
months varchar(3) path 'month',
days varchar(2) path 'day',
hours varchar(2) path 'hour',
mins varchar(2) path 'minute',
value float path 'value'
) as X
group by x.years, x.months, x.days, x.hours, x.mins
order by x.years, x.months, x.days
)
)
)
group by years,months,summonth,sumquarter,qtr,ym
order by ym
The sql pattern maybe something like this:...??
select ((select sumquarter from table where years=2009 and qtr=2) - (select sumquarter from table where years=2010 and qtr=2)) from table
Actually, it doesn't work...
The result maybe look in this view:
SUBTRACT
----------
373246
Thanks everyone helps!!:)
I'd use the following:
select
((select sumquarter from table where years=2009 and qtr=2 and rownum=1) -
(select sumquarter from table where years=2010 and qtr=2 and rownum=1)) as substract
from dual
try this query:
select (case when years=2009 and qtr=2 then sumquater end) - (case when years=2010 and qtr=2 then sumquater end) from table