SQL Server iterate in select query and update upon condition - sql

I have the following result from query
empId totalPoints addPointsDate incidentDate
-------------------------------------------------
1 11 2015-06-04 2015-07-11
2 12 2015-07-04 2015-08-16
3 10 2015-08-04 2015-06-14
4 9 2015-06-14 2015-09-11
I have to update the total points if the addPointsDate was 5 weeks ago and I didn't have any incidentDate during this period.
Can I do that with stored procedure or I have to use sql function

A simple update is sufficient:
update mytable
set totalPoints = totalPoints + 1
, addPointsDate = getdate()
where
addPointsDate <= dateadd(week, -5, getdate()) -- add points was before 5 weeks ago
and incidentDate < addPointsDate -- last incident was before add points

I would use plain SQL (this is just a simple UPDATE statement) or a procedure -- functions expect return values which you don't need.
UPDATE
pointsTable
SET
totalPoints = totalPoints + 1
WHERE
-- More than 5 weeks ago
DATEDIFF(DAY, addPointsDate, GETDATE()) > 35 -- 5 weeks * 7 days
AND
-- No incidents, or incident was before the add points date
(incidentDate IS NULL OR incidentDate < addPointsDate)

Related

SQL query to find out number of days in a week a user visited

I'd like to find out how many days in a week users have visited my site. For example, 1 day in a week, 2 days in a week, every day of the week (7).
I imagine the easiest way of doing this would be to set the date range and find out the number of days within that range (option 1). However, ideally I'd like the code to understand a week so I can run a number of weeks in one query (option 2). I'd like the users to be unique for each number of days (ie those who have visited 2 days have also visited 1 day but would only be counted in the 2 days row)
In my database (using SQLWorkbench64) I have user ids (id) and date (dt)
I'm relatively new to SQL so any help would be very much appreciated!!
Expected results (based on total users = 5540):
Option 1:
Number of Days Users
1 2000
2 1400
3 1000
4 700
5 300
6 100
7 40
Option 2:
Week Commencing Number of Days Users
06/05/2019 1 2000
06/05/2019 2 1400
06/05/2019 3 1000
06/05/2019 4 700
06/05/2019 5 300
06/05/2019 6 100
06/05/2019 7 40
You can find visitor count between a date range with below script. Its also consider if a visitor visits multi days in the given date range, s/he will be counted for the latest date only from the range-
Note: Dates are used as sample in the query.
SELECT date,COUNT(id)
FROM
(
SELECT id,max(date) date
FROM your_table
WHERE date BETWEEN '04/21/2019' AND '04/22/2019'
GROUP BY ID
)A
GROUP BY date
You can find the Monday of the week of a date and then group by that. After you have the week day there is a series of group by. Here is how I did this:
DECLARE #table TABLE
(
id INT,
date DATETIME,
MondayOfWeek DATETIME
)
DECLARE #info TABLE
(
CommencingWeek DATETIME,
NumberOfDays INT,
Users INT
)
INSERT INTO #table (id,date) VALUES
(1,'04/15/2019'), (2,'07/21/2018'), (3,'04/16/2019'), (4,'04/16/2018'), (1,'04/16/2019'), (2,'04/17/2019')
UPDATE #table
SET MondayOfWeek = CONVERT(varchar(50), (DATEADD(dd, ##DATEFIRST - DATEPART(dw, date) - 6, date)), 101)
INSERT INTO #info (CommencingWeek,NumberOfDays)
SELECT MondayOfWeek, NumberDaysInWeek FROM
(
SELECT id,MondayOfWeek,COUNT(*) AS NumberDaysInWeek FROM #table
GROUP BY id,MondayOfWeek
) T1
SELECT CommencingWeek,NumberOfDays,COUNT(*) AS Users FROM #info
GROUP BY CommencingWeek,NumberOfDays
ORDER BY CommencingWeek DESC
Here is the output from my query:
CommencingWeek NumberOfDays Users
2019-04-14 00:00:00.000 1 2
2019-04-14 00:00:00.000 2 1
2018-07-15 00:00:00.000 1 1
2018-04-15 00:00:00.000 1 1

Add N business days to a given date skipping holidays, exceptions and weekends in SQL DB2

I'm facing a challenging task here, spent a day on it and I was only able to solve it through a procedure but it is taking too long to run for all projects.
I would like to solve it in a single query if possible (no functions or procedures).
There is already some questions here doing it in programming languages OR sql functions/procedures (Wich I also solved min). So I'm asking if it is possible to solve it with just SQL
The background info is:
A project table
A phase table
A holiday table
A dayexception table which cancel a holiday or a weekend day (make that date as a working day) and it is associated with a project
A project may have 0-N phases
A phase have a start date, a duration and a draworder (needed by the system)
Working days is all days that is not weekend days and not a holiday (exception is if that date is in dayexception table)
Consider this following scenario:
project | phase(s) | Dayexception | Holiday
id | id pid start duration draworder | pid date | date
1 | 1 1 2014-01-20 10 0 | 1 2014-01-25 | 2014-01-25
| 2 1 2014-02-17 14 2 | |
The ENDDATE for the project id 1 and phase id 1 is actually 2014-01-31 see the generated data below:
The date on the below data (and now on) is formatted as dd/mm/yyyy (Brazil format) and the value N is null
proj pha start day weekday dayexcp holiday workday
1 1 20/01/2014 20/01/2014 2 N N 1
1 1 20/01/2014 21/01/2014 3 N N 1
1 1 20/01/2014 22/01/2014 4 N N 1
1 1 20/01/2014 23/01/2014 5 N N 1
1 1 20/01/2014 24/01/2014 6 N N 1
1 1 20/01/2014 25/01/2014 7 25/01/2014 25/01/2014 1
1 1 20/01/2014 26/01/2014 1 N N 0
1 1 20/01/2014 27/01/2014 2 N 27/01/2014 0
1 1 20/01/2014 28/01/2014 3 N N 1
1 1 20/01/2014 29/01/2014 4 N N 1
To generate the above data I created a view daysOfYear with all days from 2014 and 2015 (it can be bigger or smaller, created it with two years for the year turn cases) with a CTE query if you guys want to see it let me know and I will add it here. And the following select statement:
select ph.project_id proj,
ph.id phase_id pha,
ph.start,
dy.curday day,
dy.weekday, /*weekday here is a calling to the weekday function of db2*/
doe.exceptiondate dayexcp,
h.date holiday,
case when exceptiondate is not null or (weekday not in (1,7) and h.date is null)
then 1 else 0 end as workday
from phase ph
inner join daysofyear dy
on (year(ph.start) = dy.year)
left join dayexception doe
on (ph.project_id = doe.project_id
and dy.curday = truncate(doe.exceptiondate))
left join holiday h
on (dy.curday = truncate(h.date))
where ph.project_id = 1
and ph.id = 1
and dy.year in (year(ph.start),year(ph.start)+1)
and dy.curday>=ph.start
and dy.curday<=ph.start + ((duration - 1) days)
order by ph.project_id, start, dy.curday, draworder
To solve this scenario I created the following query:
select project_id,
min(start),
max(day) + sum(case when workday=0 then 1 else 0 end) days as enddate
from project_phase_days /*(view to the above select)*/
This will return correctly:
proj start enddate
1 20/01/2014 31/01/2014
The problem I couldn't solve is if the days I'm adding (non workdays sum(case when workday=0 then 1 else 0 end) days ) to the last enddate (max(day)) is weekend days or holidays or exceptions.
See the following scenario (The duration for the below phase is 7):
proj pha start day weekday dayexcp holiday workday
81 578 14/04/2014 14/04/2014 2 N N 1
81 578 14/04/2014 15/04/2014 3 N N 1
81 578 14/04/2014 16/04/2014 4 N N 1
81 578 14/04/2014 17/04/2014 5 N N 1
81 578 14/04/2014 18/04/2014 6 N 18/04/2014 0
81 578 14/04/2014 19/04/2014 7 N 0
81 578 14/04/2014 20/04/2014 1 N 20/04/2014 0
/*the below data I added to show the problem*/
81 578 14/04/2014 21/04/2014 2 N 21/04/2014 0
81 578 14/04/2014 22/04/2014 3 N 1
81 578 14/04/2014 23/04/2014 4 N 1
81 578 14/04/2014 24/04/2014 5 N 1
With the above data my query will return
proj start enddate
81 14/04/2014 23/04/2014
But the correct result would be the enddate as 24/04/2014 that's because my query doesn't take into account if the days after the last day is weekend days or holidays (or exceptions for that matter) as you can see in the dataset above the day 21/04/2014 which is outside my duration is also a Holiday.
I also tried to create a CTE on phase table to add a day for each iteration until the duration is over but I couldn't add the exceptions nor the holidays because the DB2 won't let me add a left join on the CTE recursion. Like this:
with CTE (projectid, start, enddate, duration, level) as (
select projectid, start, start as enddate, duration, 1
from phase
where project_id=1
and phase_id=1
UNION ALL
select projectid, start, enddate + (level days), duration,
case when isWorkDay(enddate + (level days)) then level+1 else level end as level
from CTE left join dayexception on ...
left join holiday on ...
where level < duration
) select * from CTE
PS: the above query doesn't work because of the DB2 limitations and isWorkDay is just as example (it would be a case on the dayexception and holiday table values).
If you have any doubts, please just ask in the comments.
Any help would be greatly appreciated. Thanks.
How to count business days forward and backwards.
Background last Century I worked at this company that used this technique. So this is a pseudo code answer. It worked great for their purposes.
What you need is a table that contains a date column and and id column that increments by one. Fill the table with only business dates... That's the tricky part because of the observing date on another date. Like 2017-01-02 was a holiday where I work but its not really a recognized holiday AFAIK.
How to get 200 business days in the future.
Select the min(id) where date >= to current date.
Select the date where id=id+200.
How to get 200 business days in the past.
Select the min(id) from table with a date >= to current date.
Select the date with id=id-200.
Business days between.
select count(*) from myBusinessDays where "date" between startdate and enddate
Good Luck as this is pseudo code.
So, using the idea of #danny117 answer I was able to create a query to solve my problem. Not exactly his idea but it gave me directions to solve it, so I will mark it as the correct answer and this answer is to share the actual code to solve it.
First let me share the view I created to the periods. As I said I created a view daysofyear with the data of 2014 and 2015 (in my final solution I added a considerable bigger interval without impacting in the end result). Ps: the date format here is in Brazil format dd/mm/yyyy
create or replace view daysofyear as
with CTE (curday, year, weekday) as (
select a1.firstday, year(a1.firstday), dayofweek(a1.firstday)
from (select to_date('01/01/1990', 'dd/mm/yyyy') firstday
from sysibm.sysdummy1) as a1
union all
select a.curday + 1 day as sumday,
year(a.curday + 1 day),
dayofweek(a.curday + 1 day)
from CTE a
where a.curday < to_date('31/12/2050', 'dd/mm/yyyy')
)
select * from cte;
With that View I then created another view with the query on my question adding an amount of days based on my historical data (bigger phase + a considerable margin) here it is:
create or replace view project_phase_days as
select ph.project_id proj,
ph.id phase_id pha,
ph.start,
dy.curday day,
dy.weekday, /*weekday here is a calling to the weekday function of db2*/
doe.exceptiondate dayexcp,
h.date holiday,
ph.duration,
case when exceptiondate is not null or (weekday not in (1,7) and h.date is null)
then 1 else 0 end as workday
from phase ph
inner join daysofyear dy
on (year(ph.start) = dy.year)
left join dayexception doe
on (ph.project_id = doe.project_id
and dy.curday = truncate(doe.exceptiondate))
left join holiday h
on (dy.curday = truncate(h.date))
where dy.year in (year(ph.start),year(ph.start)+1)
and dy.curday>=ph.start
and dy.curday<=ph.start + ((duration - 1) days) + 200 days
/*max duration in database is 110*/
After that I then created this query:
select p.id,
a.start,
a.curday as enddate
from project p left join
(
select p1.project_id,
p1.duration,
p1.start,
p1.curday,
row_number() over (partition by p1.project_id
order by p1.project_id, p1.start, p1.curday) rorder
from project_phase_days p1
where p1.validday=1
) as a
on (p.id = a.project_id
and a.rorder = a.duration)
order by p.id, a.start
What it does is select all workdays from my view (joined with my other days view) rownumber based on the project_id ordered by project_id, start date and current day (curday) I then join with the project table to get the trick part that solved the problem which is a.rorder = a.duration
If you guys need more explanation I will be glad to provide.

Determine a specific fortnight based on anchor dates

I have 2 x bi-weekly periods that were defined by 2 starting dates 1 week apart. For example, Group 1 started on 2016-01-15 and Group 2 started on 2016-01-22.
By bi-weekly, I mean a rolling period lasting 2 weeks.
How can I determine if the current date is in week 1 of Group 1 or is in week 1 of Group 2?
By way of example, today's date is 2016-04-04 so this would be day 1 of Group 2 and day 8 of Group 1, therefore I would like to a query to return 'Group 2'.
DATEDIFF calculates the difference between two dates. Divide it by 14 days and take the remainder (%).
If remainder is less than 7, then it is closer to that starting date.
Since you know that your starting dates are 1 week apart you really need to check only one starting date.
DECLARE #VarStartGroup1 date = '2016-01-15';
DECLARE #VarStartGroup2 date = '2016-01-22';
DECLARE #VarCurrentDate date = '2016-04-04';
SELECT
DATEDIFF(day, #VarStartGroup1, #VarCurrentDate) AS TotalDays1,
DATEDIFF(day, #VarStartGroup2, #VarCurrentDate) AS TotalDays2,
DATEDIFF(day, #VarStartGroup1, #VarCurrentDate) % 14 AS DayNumberInGroup1,
DATEDIFF(day, #VarStartGroup2, #VarCurrentDate) % 14 AS DayNumberInGroup2,
CASE WHEN DATEDIFF(day, #VarStartGroup1, #VarCurrentDate) % 14 < 7
THEN 'Group1' ELSE 'Group2' END AS Result
;
Result
+------------+------------+-------------------+-------------------+--------+
| TotalDays1 | TotalDays2 | DayNumberInGroup1 | DayNumberInGroup2 | Result |
+------------+------------+-------------------+-------------------+--------+
| 80 | 73 | 10 | 3 | Group2 |
+------------+------------+-------------------+-------------------+--------+
I included intermediate calculations in the result to help understand what is going on.

SQL Query to provide a count of jobs that were open grouped by week, month, year for the last 12 months

Given the following data:
Job CreatedDate ClosedDate
ID6 2014-06-04 01:51:47.060 NULL
ID7 2014-06-05 00:25:35.187 NULL
ID43 2014-06-16 05:17:18.803 2014-06-26 15:00:15.190
ID72 2014-06-20 04:00:07.733 2014-06-20 04:12:18.770
ID84 2014-06-27 16:01:18.953 NULL
ID74 2014-06-20 04:05:42.843 NULL
ID68 2014-06-20 03:46:52.653 2014-06-20 03:52:47.540
ID88 2014-07-03 03:47:55.407 NULL
ID64 2014-06-19 07:29:37.060 NULL
ID104 2014-07-08 02:59:58.337 2014-07-15 15:00:15.543
ID106 2014-07-08 03:02:29.710 2014-07-16 11:04:19.230
ID130 2014-07-10 04:30:20.900 NULL
ID132 2014-07-10 04:32:20.243 NULL
ID150 2014-07-15 20:59:06.077 2014-07-15 21:10:19.490
.
.
.
Etc.
I need to write a query that will group records by week, month, and year. A count of the number of records that were in an Open state at the end of each week (Sunday) needs to be returned.
The difficulty arises in determining if a job was in an Open state at the end of a particular week. I imagine the logic goes something like this: A job was open if the CreatedDate is less than or equal to the end of the week and the ClosedDate is after the end of the week or ClosedDate is NULL. A job can remain open over many weeks.
The output should look as follows.
Week Month Year Count
23 6 2014 80
24 6 2014 36
25 6 2014 71
26 6 2014 0
27 7 2014 25
28 7 2014 180
So, at the end of week 23 there were 80 jobs that were still open. Where a week has no records the count should return 0. Only records that were created 12 months from the time the query is executed should be returned. The query is executed every week and used to build a rolling 12 month report. I am using Microsoft SQL Server 2012.
I’ll give an example:
At the beginning of week 1 there are no jobs in the table. During week 1, 50 jobs are opened and 30 are closed which means that 20 are in an Open state at the end of the week.
During week 2, a further 50 jobs are opened and 40 are closed which means that 30 are in an Open state (when you add the 20 from last week and the 10 from this week) at the end of the week. Some of the jobs closed in week 2 were created in week 1 and some of the open jobs are still from week 1.
During week 3, a further 50 jobs are opened and 40 are closed which means that 40 are in an Open state at the end of the week. Some of the jobs closed in week 3 were created in weeks 1 and 2 and some of the open jobs are still from weeks 1 and 2.
And so on for a 12 month period.
When I run the query at the end of week 3 I want to know that at the end of week 1 there were 20 jobs open and at the end of week 2 there were 30 and at the end of week 3 there were 40. You can’t rely on the ClosedDate being NULL because even thought it was at the end of week 1 it may have been closed during week 3 and now has a ClosedDate. As I mentioned above, I think the logic that needs to be used is: A job was in the Open state at the end of a week if the CreatedDate is less than or equal to the end of the week and the ClosedDate is after the end of the week or ClosedDate is NULL.
So, at the end of week 3, when the query is run, the data that I would like to receive would look like:
Week Month Year Count
1 1 2014 20
2 1 2014 30
3 1 2014 40
A manual way of achieving what I need is to run a simple query at the end of each week that counts the number of jobs that have a ClosedDate of NULL. I then manually enter the week, month, year, and count into a table and use that table as a source for reporting. I was hoping that a more automated process could be achieved.
Thanks in advance
Hope this is what you are looking for..
DECLARE #S INT = (SELECT CAST(CAST(MIN([CREATEDDATE]) AS DATETIME) AS INT) FROM [DBO].[YOURTABLE] WHERE [CLOSEDDATE] IS NULL)
DECLARE #E INT = (SELECT CAST(CAST(MAX([CREATEDDATE]) AS DATETIME) AS INT) FROM [DBO].[YOURTABLE] WHERE [CLOSEDDATE] IS NULL)
DECLARE #TAB TABLE (ID INT PRIMARY KEY IDENTITY(1,1), DT DATETIME)
WHILE #S <= #E
BEGIN
INSERT INTO #TAB
SELECT CAST(#S AS DATETIME)
SET #S = #S + 1
END
SELECT MAIN.WEEK,Main.MONTH,Main.YEAR,
LU.COUNT
FROM (
SELECT DATEPART(YEAR,DT) YEAR,
DATEPART(MONTH,DT) MONTH,
DATEPART(WEEK,DT) WEEK
FROM #TAB
GROUP BY DATEPART(YEAR,DT),
DATEPART(MONTH,DT),
DATEPART(WEEK,DT)) MAIN
LEFT JOIN
(
SELECT DATEPART(YEAR,[CREATEDDATE]) YEAR,
DATEPART(MONTH,[CREATEDDATE]) MONTH,
DATEPART(WEEK,[CREATEDDATE]) WEEK,
COUNT(*) COUNT
FROM [DBO].[YOURTABLE]
WHERE [CLOSEDDATE] IS NULL
GROUP BY DATEPART(YEAR,[CREATEDDATE]),
DATEPART(MONTH,[CREATEDDATE]) ,
DATEPART(WEEK,[CREATEDDATE])) LU
ON MAIN.YEAR = LU.YEAR
AND MAIN.MONTH = LU.MONTH
AND MAIN.WEEK = LU.WEEK
ORDER BY MAIN.YEAR,
MAIN.MONTH,
MAIN.WEEK

How to aggregate 7 days in SQL

I was trying to aggregate a 7 days data for FY13 (starts on 10/1/2012 and ends on 9/30/2013) in SQL Server but so far no luck yet. Could someone please take a look. Below is my example data.
DATE BREAD MILK
10/1/12 1 3
10/2/12 2 4
10/3/12 2 3
10/4/12 0 4
10/5/12 4 0
10/6/12 2 1
10/7/12 1 3
10/8/12 2 4
10/9/12 2 3
10/10/12 0 4
10/11/12 4 0
10/12/12 2 1
10/13/12 2 1
So, my desired output would be like:
DATE BREAD MILK
10/1/12 1 3
10/2/12 2 4
10/3/12 2 3
10/4/12 0 4
10/5/12 4 0
10/6/12 2 1
Total 11 15
10/7/12 1 3
10/8/12 2 4
10/9/12 2 3
10/10/12 0 4
10/11/12 4 0
10/12/12 2 1
10/13/12 2 1
Total 13 16
--------through 9/30/2013
Please note, since FY13 starts on 10/1/2012 and ends on 9/30/2012, the first week of FY13 is 6 days instead of 7 days.
I am using SQL server 2008.
You could add a new computed column for the date values to group them by week and sum the other columns, something like this:
SELECT DATEPART(ww, DATEADD(d,-2,[DATE])) AS WEEK_NO,
SUM(Bread) AS Bread_Total, SUM(Milk) as Milk_Total
FROM YOUR_TABLE
GROUP BY DATEPART(ww, DATEADD(d,-2,[DATE]))
Note: I used DATEADD and subtracted 2 days to set the first day of the week to Monday based on your dates. You can modify this if required.
Use option with GROUP BY ROLLUP operator
SELECT CASE WHEN DATE IS NULL THEN 'Total' ELSE CONVERT(nvarchar(10), DATE, 101) END AS DATE,
SUM(BREAD) AS BREAD, SUM(MILK) AS MILK
FROM dbo.test54
GROUP BY ROLLUP(DATE),(DATENAME(week, DATE))
Demo on SQLFiddle
Result:
DATE BREAD MILK
10/01/2012 1 3
10/02/2012 2 4
10/03/2012 2 3
10/04/2012 0 4
10/05/2012 4 0
10/06/2012 2 1
Total 11 15
10/07/2012 1 3
10/08/2012 4 7
10/10/2012 0 4
10/11/2012 4 0
10/12/2012 2 1
10/13/2012 2 1
Total 13 16
You are looking for a rollup. In this case, you will need at least one more column to group by to do your rollup on, the easiest way to do that is to add a computed column that groups them into weeks by date.
Take a lookg at: Summarizing Data Using ROLLUP
Here is the general idea of how it could be done:
You need a derived column for each row to determine which fiscal week that record belongs to. In general you could subtract that record's date from 10/1, get the number of days that have elapsed, divide by 7, and floor the result.
Then you can GROUP BY that derived column and use the SUM aggregate function.
The biggest wrinkle is that 6 day week you start with. You may have to add some logic to make sure that the weeks start on Sunday or whatever day you use but this should get you started.
The WITH ROLLUP suggestions above can help; you'll need to save the data and transform it as you need.
The biggest thing you'll need to be able to do is identify your weeks properly. If you don't have those loaded into tables already so you can identify them, you can build them on the fly. Here's one way to do that:
CREATE TABLE #fy (fyear int, fstart datetime, fend datetime);
CREATE TABLE #fylist(fyyear int, fydate DATETIME, fyweek int);
INSERT INTO #fy
SELECT 2012, '2011-10-01', '2012-09-30'
UNION ALL
SELECT 2013, '2012-10-01', '2013-09-30';
INSERT INTO #fylist
( fyyear, fydate )
SELECT fyear, DATEADD(DAY, Number, DATEADD(DAY, -1, fy.fstart)) AS fydate
FROM Common.NUMBERS
CROSS APPLY (SELECT * FROM #fy WHERE fyear = 2013) fy
WHERE fy.fend >= DATEADD(DAY, Number, DATEADD(DAY, -1, fy.fstart));
WITH weekcalc AS
(
SELECT DISTINCT DATEPART(YEAR, fydate) yr, DATEPART(week, fydate) dt
FROM #fylist
),
ridcalc AS
(
SELECT
ROW_NUMBER() OVER (ORDER BY yr, dt) AS rid, yr, dt
FROM weekcalc
)
UPDATE #fylist
SET fyweek = rid
FROM #fylist
JOIN ridcalc
ON DATEPART(YEAR, fydate) = yr
AND DATEPART(week, fydate) = dt;
SELECT list.fyyear, list.fyweek, p.[date], COUNT(bread) AS Bread, COUNT(Milk) AS Milk
FROM products p
JOIN #fylist list
ON p.[date] = list.fydate
GROUP BY list.fyyear, list.fyweek, p.[date] WITH ROLLUP;
The Common.Numbers reference above is a simple numbers table that I use for this sort of thing (goes from 1 to 1M). You could also build that on the fly as needed.