Related
I have a table with attendance dates in SQL Workbench/J. I need to define the attendance periods of the people, where any attendance period with gaps less or equal than 90 days are merged into a single attendance period and any gaps larger than that are considered a different attendance period. For example for a single person this is the table I have
id
year
start_date
end_date
prev_att_month
diff
1
2012
2012-08-01
2012-08-31
2012-07-01
31
1
2012
2012-07-01
2012-07-31
2012-04-01
91
1
2012
2012-04-01
2012-04-30
2012-03-01
31
1
2012
2012-03-01
2012-03-31
2012-02-01
29
1
2012
2012-02-01
2012-02-29
2012-01-01
31
1
2012
2012-01-01
2012-01-31
2011-12-01
31
1
2011
2011-12-01
2011-12-31
2011-11-01
30
1
2011
2011-11-01
2011-11-30
2011-10-01
31
1
2011
2011-10-01
2011-10-31
2011-09-01
30
1
2011
2011-09-01
2011-09-30
2011-08-01
31
1
2011
2011-08-01
2011-08-31
2011-07-01
31
1
2011
2011-07-01
2011-07-31
2011-05-01
61
1
2011
2011-05-01
2011-05-31
2011-04-01
30
1
2011
2011-04-01
2011-04-30
2011-03-01
31
1
2011
2011-03-01
2011-03-31
2011-02-01
28
1
2011
2011-02-01
2011-02-28
2010-08-01
184
1
2010
2010-08-01
2010-08-31
2010-07-01
31
1
2010
2010-07-01
2010-07-31
2010-06-01
30
1
2010
2010-06-01
2010-06-30
2010-05-01
31
1
2010
2010-05-01
2010-05-31
2010-04-01
30
1
2010
2010-04-01
2010-04-30
where I defined the previous attendance month column with a lag function and then found the difference between that column and the the start_date in the diff column. This way I can check the gaps between the attendance months
I want this output of the attendance periods with the 90 day rule explained above:
id
start_date
end_date
1
1/04/2010
31/08/2010
1
1/02/2011
30/04/2012
1
1/07/2012
31/08/2012
Does any one have an idea of how to do this?
So far I was just able to define the difference between the attendance months but since this is a large data set I have not been able to find a solution to define the attendance periods without making a row to row analysis.
with [table] as (
select id, year, start_date, end_date,
lag(start_date) over (partition by id order by id, year, start_date) as prev_att_month,
start_date-prev_att_month as diff
from source
)
select *
from [table]
where id = 1
One method would be to use a windowed COUNT to count how many times a value greater than 90 has appeared in the diff column, which provides a unique group number. Then you can just group your data into those groups and get the MIN and MAX values:
WITH Grps AS(
SELECT V.id,
V.year,
V.start_date,
V.end_date,
V.prev_att_month,
V.diff,
COUNT(CASE WHEN diff > 90 THEN 1 END) OVER (PARTITION BY ID ORDER BY V.start_date ASC) AS Grp
FROM (VALUES(1,2012,CONVERT(date,'20120801'),CONVERT(date,'20120831'),CONVERT(date,'2012-07-01'),31),
(1,2012,CONVERT(date,'20120701'),CONVERT(date,'20120731'),CONVERT(date,'2012-04-01'),91),
(1,2012,CONVERT(date,'20120401'),CONVERT(date,'20120430'),CONVERT(date,'2012-03-01'),31),
(1,2012,CONVERT(date,'20120301'),CONVERT(date,'20120331'),CONVERT(date,'2012-02-01'),29),
(1,2012,CONVERT(date,'20120201'),CONVERT(date,'20120229'),CONVERT(date,'2012-01-01'),31),
(1,2012,CONVERT(date,'20120101'),CONVERT(date,'20120131'),CONVERT(date,'2011-12-01'),31),
(1,2011,CONVERT(date,'20111201'),CONVERT(date,'20111231'),CONVERT(date,'2011-11-01'),30),
(1,2011,CONVERT(date,'20111101'),CONVERT(date,'20111130'),CONVERT(date,'2011-10-01'),31),
(1,2011,CONVERT(date,'20111001'),CONVERT(date,'20111031'),CONVERT(date,'2011-09-01'),30),
(1,2011,CONVERT(date,'20110901'),CONVERT(date,'20110930'),CONVERT(date,'2011-08-01'),31),
(1,2011,CONVERT(date,'20110801'),CONVERT(date,'20110831'),CONVERT(date,'2011-07-01'),31),
(1,2011,CONVERT(date,'20110701'),CONVERT(date,'20110731'),CONVERT(date,'2011-05-01'),61),
(1,2011,CONVERT(date,'20110501'),CONVERT(date,'20110531'),CONVERT(date,'2011-04-01'),30),
(1,2011,CONVERT(date,'20110401'),CONVERT(date,'20110430'),CONVERT(date,'2011-03-01'),31),
(1,2011,CONVERT(date,'20110301'),CONVERT(date,'20110331'),CONVERT(date,'2011-02-01'),28),
(1,2011,CONVERT(date,'20110201'),CONVERT(date,'20110228'),CONVERT(date,'2010-08-01'),184),
(1,2010,CONVERT(date,'20100801'),CONVERT(date,'20100831'),CONVERT(date,'2010-07-01'),31),
(1,2010,CONVERT(date,'20100701'),CONVERT(date,'20100731'),CONVERT(date,'2010-06-01'),30),
(1,2010,CONVERT(date,'20100601'),CONVERT(date,'20100630'),CONVERT(date,'2010-05-01'),31),
(1,2010,CONVERT(date,'20100501'),CONVERT(date,'20100531'),CONVERT(date,'2010-04-01'),30),
(1,2010,CONVERT(date,'20100401'),CONVERT(date,'20100430'),NULL,NULL))V(id,year,start_date,end_date,prev_att_month,diff))
SELECT id,
MIN(Start_date) AS Start_date,
MAX(End_Date) AS End_Date
FROM Grps
GROUP BY Id,
Grp
ORDER BY id,
Start_date;
I asked a question a couple weeks ago and I thought I asked the correct question, but I needed to modify it. I am posting a new question, however the previous post can be found here Thanks to #Serg for the quick reply to my previous question.
I've copied and pasted the question below. The modification to my question is, I assumed that the end date was sysdate, whereas I actually have an END_DATE column to work from. So, instead of count all files that were active each year until sysdate, I want to count all files there were active each year until END_DATE.
My previous question edited for my new question:
I have an Oracle table containing a number of active files. Each row is a file and has unique file #, issue date and end date
File #
ISSUE_DATE
END_DATE
1254
15-OCT-1997
11-NOV-2005
5245
22-MAY-2005
15-OCT-2008
7852
02-APR-2015
05-DEC-2023
9852
11-MAR-2021
22-OCT-2028
etc
I want to query a count of how many files were active each year between the year in which it was issued and the year of its end date. So, if it was issued in 2010, then I want to include that file in the count for each year since it was issued (2010, 2011, 2012, etc), up to its end date.
I'd like my end table to loo like:
Year
COUNT_OF_FILES
1997
20
1998
32
1999
55
2000
42
...
...
2019
130
2020
155
2021
151
2022
101
2023
98
2024
61
Just use simple generator:
Step 1: get all years for each record:
with
t(File#, ISSUE_DATE, END_DATE) as (
select 1254, to_date('15-OCT-1997','dd-mon-yyyy'),to_date('11-NOV-2005','dd-mon-yyyy') from dual union all
select 5245, to_date('22-MAY-2005','dd-mon-yyyy'),to_date('15-OCT-2008','dd-mon-yyyy') from dual union all
select 7852, to_date('02-APR-2015','dd-mon-yyyy'),to_date('05-DEC-2023','dd-mon-yyyy') from dual union all
select 9852, to_date('11-MAR-2021','dd-mon-yyyy'),to_date('22-OCT-2028','dd-mon-yyyy') from dual
)
select
t.*, yyyy
from t
cross apply(
select
extract(year from ISSUE_DATE)+level-1 as yyyy
from dual
connect by extract(year from ISSUE_DATE)+level-1<=extract(year from END_DATE)
) gen_years
this will return:
FILE# ISSUE_DATE END_DATE YYYY
---------- ------------------- ------------------- ----------
1254 1997-10-15 00:00:00 2005-11-11 00:00:00 1997
1254 1997-10-15 00:00:00 2005-11-11 00:00:00 1998
1254 1997-10-15 00:00:00 2005-11-11 00:00:00 1999
1254 1997-10-15 00:00:00 2005-11-11 00:00:00 2000
1254 1997-10-15 00:00:00 2005-11-11 00:00:00 2001
1254 1997-10-15 00:00:00 2005-11-11 00:00:00 2002
1254 1997-10-15 00:00:00 2005-11-11 00:00:00 2003
1254 1997-10-15 00:00:00 2005-11-11 00:00:00 2004
1254 1997-10-15 00:00:00 2005-11-11 00:00:00 2005
5245 2005-05-22 00:00:00 2008-10-15 00:00:00 2005
5245 2005-05-22 00:00:00 2008-10-15 00:00:00 2006
5245 2005-05-22 00:00:00 2008-10-15 00:00:00 2007
5245 2005-05-22 00:00:00 2008-10-15 00:00:00 2008
7852 2015-04-02 00:00:00 2023-12-05 00:00:00 2015
7852 2015-04-02 00:00:00 2023-12-05 00:00:00 2016
7852 2015-04-02 00:00:00 2023-12-05 00:00:00 2017
7852 2015-04-02 00:00:00 2023-12-05 00:00:00 2018
7852 2015-04-02 00:00:00 2023-12-05 00:00:00 2019
7852 2015-04-02 00:00:00 2023-12-05 00:00:00 2020
7852 2015-04-02 00:00:00 2023-12-05 00:00:00 2021
7852 2015-04-02 00:00:00 2023-12-05 00:00:00 2022
7852 2015-04-02 00:00:00 2023-12-05 00:00:00 2023
9852 2021-03-11 00:00:00 2028-10-22 00:00:00 2021
9852 2021-03-11 00:00:00 2028-10-22 00:00:00 2022
9852 2021-03-11 00:00:00 2028-10-22 00:00:00 2023
9852 2021-03-11 00:00:00 2028-10-22 00:00:00 2024
9852 2021-03-11 00:00:00 2028-10-22 00:00:00 2025
9852 2021-03-11 00:00:00 2028-10-22 00:00:00 2026
9852 2021-03-11 00:00:00 2028-10-22 00:00:00 2027
9852 2021-03-11 00:00:00 2028-10-22 00:00:00 2028
Then just aggregate them:
select
yyyy, count(*) cnt
from t
cross apply(
select
extract(year from ISSUE_DATE)+level-1 as yyyy
from dual
connect by extract(year from ISSUE_DATE)+level-1<=extract(year from END_DATE)
) gen_years
group by yyyy;
Full test case with test data:
with
t(File#, ISSUE_DATE, END_DATE) as (
select 1254, to_date('15-OCT-1997','dd-mon-yyyy'),to_date('11-NOV-2005','dd-mon-yyyy') from dual union all
select 5245, to_date('22-MAY-2005','dd-mon-yyyy'),to_date('15-OCT-2008','dd-mon-yyyy') from dual union all
select 7852, to_date('02-APR-2015','dd-mon-yyyy'),to_date('05-DEC-2023','dd-mon-yyyy') from dual union all
select 9852, to_date('11-MAR-2021','dd-mon-yyyy'),to_date('22-OCT-2028','dd-mon-yyyy') from dual
)
select
yyyy, count(*) cnt
from t
cross apply(
select
extract(year from ISSUE_DATE)+level-1 as yyyy
from dual
connect by extract(year from ISSUE_DATE)+level-1<=extract(year from END_DATE)
) gen_years
group by yyyy
order by yyyy;
Output:
YYYY CNT
---------- ----------
1997 1
1998 1
1999 1
2000 1
2001 1
2002 1
2003 1
2004 1
2005 2
2006 1
2007 1
2008 1
2015 1
2016 1
2017 1
2018 1
2019 1
2020 1
2021 2
2022 2
2023 2
2024 1
2025 1
2026 1
2027 1
2028 1
I have an ASP.NET Core application, through controller endpoint I pass #by and #period string values to the SQL query.
#by takes one of the following values: day, week
#period takes one of the following values: week, month, year
When the #period is month or year, then #by is a week, else it's a day.
I have the following working query when the #period is a month or a year:
SELECT
l.region_id AS region_id,
'Region ' + r.region_desc AS region_name,
MIN(DATEADD(D, -(DATEPART(WEEKDAY, s.pos_date) - 1), s.pos_date)) AS date_pos,
CONVERT(VARCHAR(20), MIN(DATEADD(D, -(DATEPART(WEEKDAY, s.pos_date) - 1), s.pos_date)), 107) AS display_date_pos
FROM
incent_summary s
INNER JOIN
location l ON s.store_num = l.store_num
INNER JOIN
region r ON l.region_id = r.region_id
WHERE
s.pos_date >= DATEADD(day, #period , CONVERT(date, GETDATE()))
AND s.pos_date <= GETDATE()
GROUP BY
DATEPART (#by, s.pos_date),
l.region_id, r.region_desc
ORDER BY
DATEPART (#by, pos_date),
l.region_id, r.region_desc
The issue is when the #period is a week, #by is day, and the statement
MIN(DATEADD(D, -(DATEPART(WEEKDAY, s.pos_date) - 1), s.pos_date)) AS date_pos
returns the same day for all the 7 days.
Sample output when #period = year and #by = week:
region_id region_name date_pos display_date_pos
---------------------------------------------------------------------
34 Region 43 2019-12-29 00:00:00.000 Dec 29, 2019
50 Region 22 2019-12-29 00:00:00.000 Dec 29, 2019
34 Region 43 2020-01-05 00:00:00.000 Jan 05, 2020
50 Region 22 2020-01-05 00:00:00.000 Jan 05, 2020
34 Region 43 2020-01-12 00:00:00.000 Jan 12, 2020
50 Region 22 2020-01-12 00:00:00.000 Jan 12, 2020
34 Region 43 2020-01-19 00:00:00.000 Jan 19, 2020
50 Region 22 2020-01-19 00:00:00.000 Jan 19, 2020
34 Region 43 2020-01-26 00:00:00.000 Jan 26, 2020
50 Region 22 2020-01-26 00:00:00.000 Jan 26, 2020
Sample output when #period = week and #by = day:
region_id region_name date_pos display_date_pos
--------------------------------------------------------------------
34 Region 43 2020-07-12 00:00:00.000 Jul 12, 2020
50 Region 22 2020-07-12 00:00:00.000 Jul 12, 2020
34 Region 43 2020-07-12 00:00:00.000 Jul 12, 2020
50 Region 22 2020-07-12 00:00:00.000 Jul 12, 2020
34 Region 43 2020-07-19 00:00:00.000 Jul 19, 2020
50 Region 22 2020-07-19 00:00:00.000 Jul 19, 2020
34 Region 43 2020-07-19 00:00:00.000 Jul 19, 2020
50 Region 22 2020-07-19 00:00:00.000 Jul 19, 2020
34 Region 43 2020-07-19 00:00:00.000 Jul 19, 2020
50 Region 22 2020-07-19 00:00:00.000 Jul 19, 2020
How can I fix this?
SELECT
DATEADD(D, -(DATEPART(WEEKDAY, s.pos_date) - 1), s.pos_date)
Will always return the first day of the week because the logic is: "subtract from my date the number of days from sunday and add 1."
Sunday: 1 - 1 + 1 = 1 = Sunday
Monday: 2 - 2 + 1 = 1 = Sunday
.
.
.
Saturday: 7 - 7 + 1 = Sunday
That's fine when you want the first Sunday of the year/month/whatever. But the first sunday of every week is always... sunday. But in this case you really just need to take the MIN(s.pos_date) if #period is week.
There's probably some crazy way to do this in a single statement using quaternions or something else super mathy, but it's easiest to just use a case statement:
MIN
(
CASE
WHEN '#by' = 'day' THEN s.pos_date
ELSE DATEADD(D, -(DATEPART(WEEKDAY, s.pos_date) - 1), s.pos_date)
END
)
I'm not a C# programmer so I can't tell you the exact way to make sure the string DAY is passed to the query as "DAY" but I'm sure you can handle that part.
ALSO IMPORTANT The datepart "day" is day of month, so if you're going to possibly have a span greater than one month (but under a year), use dayofyear.
I have data in two tables ORDERS and training.
Different kinds of training are provided to various customers. I would like to see what was the affect of these training on revenue for the customers involved. For achieving this, I would like to look at the revenue for the past 90 days and the future 90 days for each customer from the date of receiving the training. In other words, if a customer received a training on March 30 2014, I would like to look at the revenue from Jan 1 2014 till June 30 2014. I have come up with the following query which pretty much does the job:
select
o.custno,
sum(ISNULL(a.revenue,0)) as Revenue,
o.Date as TrainingDate,
DATEADD(mm, DATEDIFF(mm, 0, a.created), 0) as RevenueMonth
from ORDERS a,
(select distinct custno, max(Date) as Date from Training group by custno) o
where a.custnum = o.custno
and a.created between DATEADD(day, -90, o.Date) and DATEADD(day, 90, o.Date)
group by o.custno, o.Date, DATEADD(mm, DATEDIFF(mm, 0, a.created), 0)
order by o.custno
The sample output of this query looks something like this:
custno Revenue TrainingDate RevenueMonth
0000100 159.20 2014-06-02 00:00:00.000 2014-03-01 00:00:00.000
0000100 199.00 2014-06-02 00:00:00.000 2014-04-01 00:00:00.000
0000100 79.60 2014-06-02 00:00:00.000 2014-05-01 00:00:00.000
0000100 29.85 2014-06-02 00:00:00.000 2014-06-01 00:00:00.000
0000100 79.60 2014-06-02 00:00:00.000 2014-07-01 00:00:00.000
0000100 99.50 2014-06-02 00:00:00.000 2014-08-01 00:00:00.000
0000250 437.65 2013-02-26 00:00:00.000 2012-11-01 00:00:00.000
0000250 4181.65 2013-02-26 00:00:00.000 2012-12-01 00:00:00.000
0000250 4146.80 2013-02-26 00:00:00.000 2013-01-01 00:00:00.000
0000250 6211.93 2013-02-26 00:00:00.000 2013-02-01 00:00:00.000
0000250 2199.72 2013-02-26 00:00:00.000 2013-03-01 00:00:00.000
0000250 4452.65 2013-02-26 00:00:00.000 2013-04-01 00:00:00.000
Desired output example:
If the training was provided on March 15 2014, for customer number 100, I’d want revenue data in the following format:
CustNo Revenue TrainingDate RevenueMonth
100 <Some revenue figure> March 15 2014 Dec 15 2013 – Jan 14 2014 (Past month 1)
100 <Some revenue figure> March 15 2014 Jan 15 2014 – Feb 14 2014 (Past month 2)
100 <Some revenue figure> March 15 2014 Feb 15 2014 – Mar 14 2014 (Past month 3)
100 <Some revenue figure> March 15 2014 Mar 15 2014 – Apr 14 2014 (Future month 1)
100 <Some revenue figure> March 15 2014 Apr 15 2014 – May 14 2014 (Future month 2)
100 <Some revenue figure> March 15 2014 May 15 2014 – Jun 14 2014 (Future month 3)
Here, the RevenueMonth column doesn’t need to be in this format as long as it has the data relative to the training date. The ‘past’ and ‘future’ month references in the braces are only to explain the output, they need not be present in the output.
My query gets the data and groups the data by month. I would like the months to be grouped relative to the training date. For example - If the training was given on March 15, I would like the past month to be from Feb 15 till March 14, my query doesn't do that. I believe a little tweak in this query might just achieve what I'm after.
Any help with the query would be highly appreciated.
Something along these lines may do what you want:
select
t.custno,
sum(ISNULL(o.revenue,0)) as Revenue,
t.maxDate as TrainingDate,
((DATEDIFF(day, TrainingDate, o.created) + 89) / 30) - 3 as DeltaMonth
from ORDERS o
join (select custno, max([Date]) as maxDate from Training group by custno) t
on o.custnum = t.custno
where o.created
between DATEADD(day, -89, t.maxDate) and DATEADD(day, 90, t.maxDate)
group by t.custno, t.maxDate, DeltaMonth
order by t.custno
The general strategy is to compute a difference in months (or 30-day periods, really) from the training date, and group by that. This version uses from 89 days before to 90 days after the training, because if you run from -90 to +90 then you have one more day (the training day itself) than divides evenly into 30-day periods.
The query follows the general structure of the original, but there are several changes:
it computes DeltaMonth (from -3 to 2) as an index of 30-day periods relative to the training date, with the training date being the last day of DeltaMonth number -1.
it uses the DeltaMonth alias in the GROUP BY clause instead of repeating its formula. That provides better clarity, simplicity, and maintainability.
I changed the table aliases. I simply could not handle there being a table named "ORDERS" and an alias "o", with the latter not being associated with the former.
the query uses modern join syntax, for better clarity
the GROUP BY clause updated appropriately
My output from a procedure is like
Jan 1 1900 10:30PM
Jan 1 1900 10:45PM
Jan 1 1900 11:00PM
Jan 1 1900 11:30PM
Jan 1 1900 11:45PM
Jan 2 1900 12:00AM
Jan 2 1900 12:15AM
Jan 2 1900 12:30AM
Jan 2 1900 12:45AM
Jan 2 1900 1:00AM
I want add current date with time and change date after 12:00AM
like this:
Friday,MAY,18 10:30PM
Friday,MAY,18 10:45PM
Friday,MAY,18 11:00PM
Friday,MAY,18 11:30PM
Friday,MAY,18 11:45PM
Friday,MAY,19 12:00AM
Friday,MAY,19 12:15AM
Friday,MAY,19 12:30AM
Friday,MAY,19 12:45AM
Friday,MAY,19 1:00AM
How to do this??
thanks in advance
From SQL Server 2008:
select YourTimeCol+cast(getdate() as date)
from YourTable
Pre SQL Server 2008:
select YourTimeCol+dateadd(day, datediff(day, 0, getdate()), 0)
from YourTable
SE-Data
I think you need this
DECLARE #tt TABLE (Sday VARCHAR(50))
INSERT INTO #tt VALUES('Jan 1 1900 10:30PM'),('Jan 1 1900 10:45PM'),('Jan 1 1900 11:00PM'),('Jan 1 1900 11:30PM'),('Jan 1 1900 11:45PM'),('Jan 2 1900 12:00AM'),('Jan 2 1900 12:15AM'),('Jan 2 1900 12:30AM'),('Jan 2 1900 12:45AM'),('Jan 2 1900 1:00AM')
SELECT Sday,DATEADD(DAY,(DATEDIFF(DAY,'1900-01-01',GETDATE())),Sday) AS resultAsDatetime,
CONVERT(VARCHAR(50),DATEADD(DAY,(DATEDIFF(DAY,'1900-01-01',GETDATE())),Sday),109) AS result
FROM #tt
which is returning
Jan 1 1900 10:30PM 2012-05-18 22:30:00.000 May 18 2012 10:30:00:000PM
Jan 1 1900 10:45PM 2012-05-18 22:45:00.000 May 18 2012 10:45:00:000PM
Jan 1 1900 11:00PM 2012-05-18 23:00:00.000 May 18 2012 11:00:00:000PM
Jan 1 1900 11:30PM 2012-05-18 23:30:00.000 May 18 2012 11:30:00:000PM
Jan 1 1900 11:45PM 2012-05-18 23:45:00.000 May 18 2012 11:45:00:000PM
Jan 2 1900 12:00AM 2012-05-19 00:00:00.000 May 19 2012 12:00:00:000AM
Jan 2 1900 12:15AM 2012-05-19 00:15:00.000 May 19 2012 12:15:00:000AM
Jan 2 1900 12:30AM 2012-05-19 00:30:00.000 May 19 2012 12:30:00:000AM
Jan 2 1900 12:45AM 2012-05-19 00:45:00.000 May 19 2012 12:45:00:000AM
Jan 2 1900 1:00AM 2012-05-19 01:00:00.000 May 19 2012 1:00:00:000AM
obviouly you can choose the right format for the conversion of the DATETIME to a VARCHAR as documented in the CONVERT function, but I think this does not need help.
Hope this helps.