Getting consecutive dates in SQL Server 2012 with CTE - sql-server

I have to assign color codes for one specific room alternating between two codes for the year in Microsoft SQL Server. The unique field is each individual date of the calendar.
So sample rows would look like as follows.
weekDay colorName RoomNum
2017-01-01 Blue 100
2017-01-02 Red 100
Color codes need assigned for each week alternating between the 2 colors from Monday (being the beginning of the week) through Sunday counted as the end of the week. So starting Monday January 2, 2017 I would need the following pattern to continue through the next calendar year so the end of 2018.
2017-01-02 Red 100
2017-01-03 Red 100
2017-01-04 Red 100
2017-01-05 Red 100
2017-01-06 Red 100
2017-01-07 Red 100
2017-01-08 Red 100
2017-01-09 Blue 100
2017-01-10 Blue 100
2017-01-11 Blue 100
2017-01-12 Blue 100
2017-01-13 Blue 100
2017-01-14 Blue 100
2017-01-15 Blue 100
2017-01-16 Red 100
.
.
.
I have the following CTE but it assigns 6 days the first week, then assigns 14 days every week after not seven day intervals starting each Monday through Sunday. What am I doing wrong? Thanks!
DECLARE #tableTest TABLE (weekDay datetime, colorName varchar(50), roomNum int);
DEClARE #begindate datetime = '01/02/17';
declare #enddate datetime = '12/31/18';
;with
N0 as (SELECT 1 as n UNION ALL SELECT 1)
,N1 as (SELECT 1 as n FROM N0 t1, N0 t2)
,N2 as (SELECT 1 as n FROM N1 t1, N1 t2)
,N3 as (SELECT 1 as n FROM N2 t1, N2 t2)
,N4 as (SELECT 1 as n FROM N3 t1, N3 t2)
,N5 as (SELECT 1 as n FROM N4 t1, N4 t2)
,N6 as (SELECT 1 as n FROM N5 t1, N5 t2)
,nums as (SELECT ROW_NUMBER() OVER (ORDER BY (SELECT 1)) as num FROM N6)
INSERT #tableTest (weekDay, roomNum)
SELECT DATEADD(day,num-1,#begindate) as thedate, 100
FROM nums
WHERE num <= DATEDIFF(day,#begindate,#enddate) + 1
;with weekNumber as (
SELECT weekDay, colorName, roomNum, (DATEPART(ww, weekDay) / 2) % 2 as schedule FROM #tableTest
)
UPDATE weekNumber
SET colorName = CASE WHEN weekNumber.schedule = 0 THEN 'Red' ELSE 'Blue' END
SELECT * FROM #tableTest

You're doing division AND modulo, which is causing you to double the "width" of the modulo, if that terminology makes sense to you. : )
In other words, instead of doing modulo against 1, 2, 3, 4 etc..
You're doing modulo against 1, 1.5, 2, 2.5, 3, etc. Get it?
Anyway, the solution is to change your final update to this:
;with weekNumber as (
SELECT weekDay, colorName, roomNum, (DATEPART(ww, weekDay)) % 2 as schedule FROM #tableTest
)
UPDATE weekNumber
SET colorName = CASE WHEN weekNumber.schedule = 0 THEN 'Red' ELSE 'Blue' END

Related

Find each employee Project Start Date and End Date (i.e., Start Date and End Date should be continuous without any break in Days/Months/Year)

ID
EmployeeId
ProjectId
StartDate
EndDate
1
1
100
01-04-2019
30-04-2019
2
1
100
01-05-2019
31-05-2019
3
1
100
01-12-2019
31-12-2019
4
1
100
01-01-2020
31-01-2020
5
2
200
01-01-2019
31-01-2019
6
2
200
01-02-2019
28-02-2019
7
2
200
01-04-2019
28-04-2019
8
2
200
01-05-2019
31-05-2019
9
2
200
01-06-2019
30-06-2019
10
3
100
01-08-2019
31-08-2019
11
3
100
01-09-2019
30-09-2019
12
3
200
01-10-2019
31-10-2019
13
3
200
01-11-2019
30-11-2019
14
3
300
01-12-2019
31-12-2019
15
3
300
01-01-2020
31-01-2020
16
3
300
01-02-2020
29-02-2020
expected Output
EmployeeId
ProjectId
StartDate
EndDate
1
100
01-04-2019
31-05-2019
1
100
01-12-2019
31-01-2020
2
200
01-01-2019
28-02-2019
2
200
01-04-2019
28-04-2019
2
200
01-05-2019
30-06-2019
3
100
01-08-2019
30-09-2019
3
200
01-10-2019
30-11-2019
3
300
01-12-2019
29-02-2020
I have tried to find the enddate of the currentrow is enddate+1 is startdate of the next row,if it is continious without any gaps then need to select startdate of the previous row and enddate of current row.
;with MyCTE as
(
select mt.EmployeeId, mt.StartDate, mt.EndDate, ROW_NUMBER() over (order by ID) as RowNum
from #Employees mt
)
select c1.employeeId, case when c2.employeeId is null then c1.StartDate else dateadd(dd,1, c2.EndDate) end as StartDate,
c1.EndDate
from MyCTE c1
left join MyCTE c2
on C1.employeeId=c2.employeeId and
--and dateadd(dd,1,c1.startdate)
c1.RowNum = c2.RowNum +1
This is a classic gaps-and-islands problem.
There are many solutions. A typical simple (if not very efficient) solution, is as follows:
Use LAG to identify rows which start a group/island (partitioning as necessary)
Use a windowed COUNT to assign a group ID to each of those
Group by that ID, and take the MIN/MAX of the values
WITH PrevValues AS (
SELECT *,
IsStart = CASE WHEN DATEADD(day, -1, StartDate) <=
LAG(EndDate) OVER (PARTITION BY EmployeeId, ProjectId ORDER BY StartDate)
THEN NULL ELSE 1 END
FROM Employees e
),
Groups AS (
SELECT *,
GroupId = COUNT(IsStart) OVER (PARTITION BY EmployeeId, ProjectId ORDER BY StartDate ROWS UNBOUNDED PRECEDING)
FROM PrevValues pv
)
SELECT
g.EmployeeId,
g.ProjectId,
StartDate = MIN(StartDate),
EndDate = MAX(EndDate)
FROM Groups g
GROUP BY
g.EmployeeId,
g.ProjectId,
g.GroupId;
db<>fiddle

Finding the Datediff between Records in same Table

IP QID ScanDate Rank
101.110.32.80 6 2016-09-28 18:33:21.000 3
101.110.32.80 6 2016-08-28 18:33:21.000 2
101.110.32.80 6 2016-05-30 00:30:33.000 1
I have a Table with certain records, grouped by Ipaddress and QID.. My requirement is to find out which record missed the sequence in the date column or other words the date difference is more than 30 days. In the above table date diff between rank 1 and rank 2 is more than 30 days.So, i should flag the rank 2 record.
You can use LAG in Sql 2012+
declare #Tbl Table (Ip VARCHAR(50), QID INT, ScanDate DATETIME,[Rank] INT)
INSERT INTO #Tbl
VALUES
('101.110.32.80', 6, '2016-09-28 18:33:21.000', 3),
('101.110.32.80', 6, '2016-08-28 18:33:21.000', 2),
('101.110.32.80', 6, '2016-05-30 00:30:33.000', 1)
;WITH Result
AS
(
SELECT
T.Ip ,
T.QID ,
T.ScanDate ,
T.[Rank],
LAG(T.[Rank]) OVER (ORDER BY T.[Rank]) PrivSRank,
LAG(T.ScanDate) OVER (ORDER BY T.[Rank]) PrivScanDate
FROM
#Tbl T
)
SELECT
R.Ip ,
R.QID ,
R.ScanDate ,
R.Rank ,
R.PrivScanDate,
IIF(DATEDIFF(DAY, R.PrivScanDate, R.ScanDate) > 30, 'This is greater than 30 day. Rank ' + CAST(R.PrivSRank AS VARCHAR(10)), '') CFlag
FROM
Result R
Result:
Ip QID ScanDate Rank CFlag
------------------------ ----------- ----------------------- ----------- --------------------------------------------
101.110.32.80 6 2016-05-30 00:30:33.000 1
101.110.32.80 6 2016-08-28 18:33:21.000 2 This is greater than 30 day. Rank 1
101.110.32.80 6 2016-09-28 18:33:21.000 3 This is greater than 30 day. Rank 2
While Window Functions could be used here, I think a self join might be more straight forward and easier to understand:
SELECT
t1.IP,
t1.QID,
t1.Rank,
t1.ScanDate as endScanDate,
t2.ScanDate as beginScanDate,
datediff(day, t2.scandate, t1.scandate) as scanDateDays
FROM
table as t1
INNER JOIN table as t2 ON
t1.ip = t2.ip
t1.rank - 1 = t2.rank --get the record from t2 and is one less in rank
WHERE datediff(day, t2.scandate, t1.scandate) > 30 --only records greater than 30 days
It's pretty self-explanatory. We are joining the table to itself and joining the ranks together where rank 2 gets joined to rank 1, rank 3 gets joined to rank 2, and so on. Then we just test for records that are greater than 30 days using the datediff function.
I would use windowed function to avoid self join which in many case will perform better.
WITH cte
AS (
SELECT
t.IP
, t.QID
, LAG(t.ScanDate) OVER (PARTITION BY t.IP ORDER BY T.ScanDate) AS beginScanDate
, t.ScanDate AS endScanDate
, DATEDIFF(DAY,
LAG(t.ScanDate) OVER (PARTITION BY t.IP ORDER BY t.ScanDate),
t.ScanDate) AS Diff
FROM
MyTable AS t
)
SELECT
*
FROM
cte c
WHERE
Diff > 30;

Calculating price based on changing hourly rates

Imagine I have a business that rents widgets by the minute, but the rental rates vary depending on the day of the week and hour of the day. I describe this information in a "Rates" table as follows:
CREATE TABLE Rates (
DayNumber int,
HourNumber int,
HourlyRate decimal(19,4),
PRIMARY KEY (DayNumber, HourNumber)
)
DayNumber HourNumber HourlyRate
--------- ---------- ----------
1 1 3.75
1 2 4.50
1 3 4.25
1 4 3.75
In the above table, the day number is retrieved from datepart(dw, Start), the hour number from datepart(hour, Start). It has 168 records (the number of hours in a standard week).
I have the rental information as follows in a "Rentals" table:
CREATE TABLE Rentals (
RentalId int,
CustomerId int,
Start datetimeoffset,
Finish datetimeoffset,
Cost decimal(19,4),
PRIMARY KEY (RentalId)
)
RentalId CustomerId Start Finish Cost
-------- ---------- --------------- --------------- ----
1 1 1/1/2016 6:11am 1/1/2016 2:34pm
2 1 1/2/2016 7:23am 1/3/2016 8:12am
Using T-SQL (SQL Server 2014 or better), I'd like to update the Rentals table to calculate the Cost column that considers the rate of each day-hour, summed up for the total rental period. Bonus points for efficiency.
Tested in SSMS, it updates the table now. It solved the issues below:
1) it works no matter whether or not START and FINISH are on the same day;
2) it works no matter whether or not START and FINISH are on the same week or month.
update rentals
set cost = (select sum(hourlyrate) from rates
where (daynumber > datepart(dw,start) and daynumber < datepart(dw,finish)) or
(daynumber = datepart(dw,start) and hournumber > datepart(hour,start)) or
(daynumber = datepart(dw,finish) and hournumber < datepart(hour,finish))
) +
(select hourlyrate from rates
where daynumber = datepart(dw,start) and hournumber = datepart(hour,start)
) * 1.00 * (60-datepart(minute, start))/60 +
(select hourlyrate from rates
where daynumber = datepart(dw,finish) and hournumber = datepart(hour,finish)
) * 1.00 * datepart(minute, finish)/60 -
(
Case when datediff(day,start,finish)%7 = 0 then 230 -- deal with same day case
when datediff(day,start,finish)%7 <> 0 then 0
end
) +
(select datediff(day,start,finish)/7 * sum(hourlyrate) from rates) -- deal with multiple weeks case
You can use a tally table to split record into one record per hour.
For example, the following rental
RentalId CustomerId Start Finish Cost
-------- ---------- --------------- --------------- ----
1 1 1/1/2016 1:30pm 1/1/2016 4:45pm
is processed using tally into
RentalId Start Finish Cost
-------- --------------- --------------- ----
1 1/1/2016 1:30pm 1/1/2016 2:00pm 1
1 1/1/2016 2:00pm 1/1/2016 3:00pm 2
1 1/1/2016 3:00pm 1/1/2016 4:00pm 3
1 1/1/2016 4:00pm 1/1/2016 4:45pm 4
With this, you can calculate the cost of each preprocessed records. You have to use rate per minute since not all records lasted a full hour.
Then, just sum these costs grouped by rental and you have got the cost of each rentals.
Here is the complete solution.
I used CTE for the tally table and preprocessed records.
;WITH
N0(_) AS (SELECT NULL UNION ALL SELECT NULL),
N1(_) AS (SELECT NULL FROM N0 AS L CROSS JOIN N0 AS R),
N2(_) AS (SELECT NULL FROM N1 AS L CROSS JOIN N1 AS R),
N3(_) AS (SELECT NULL FROM N2 AS L CROSS JOIN N2 AS R),
Tally AS (SELECT N = ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) FROM N3 AS L CROSS JOIN N3 AS R),
PreprocessedData AS (SELECT Rent.RentalId,
BillingStart =( CASE WHEN Tally.N = 1 THEN
Rent.Start
ELSE
DATEADD(HOUR, DATEDIFF(HOUR, 0, DATEADD(HOUR, Tally.N - 1, Rent.Start)), 0)--Trim exceeding minutes
END),
BillingEnd = ( CASE WHEN DATEDIFF(HOUR, Rent.Start, Rent.Finish) < Tally.N THEN
Rent.Finish
ELSE
DATEADD(HOUR, DATEDIFF(HOUR, 0, DATEADD(HOUR, Tally.N, Rent.Start)), 0)--Trim exceeding minutes
END),
Rate.HourlyRate
FROM Rentals AS Rent
INNER JOIN Tally ON DATEDIFF(HOUR, Rent.Start, Rent.Finish) >= Tally.N - 1 -- DATEADD(HOUR, Tally.N, Rent.Start) < Rent.Finish
LEFT JOIN Rates AS Rate ON DATEPART(DW, DATEADD(HOUR, Tally.N - 1, Rent.Start)) = Rate.DayNumber
AND DATEPART(HOUR, DATEADD(HOUR, Tally.N - 1, Rent.Start)) = Rate.HourNumber
)
UPDATE Rentals
SET Cost = CalculateCostPerRental.CalculateCost
FROM Rentals
INNER JOIN (SELECT RentalId,
CalculateCost = SUM(HourlyRate * DATEDIFF(MINUTE, BillingStart, BillingEnd) /60)
FROM PreprocessedData
GROUP BY RentalId
HAVING SUM(CASE WHEN HourlyRate IS NOT NULL THEN 0 ELSE 1 END) = 0 /*Update only if all the rates where found*/) AS CalculateCostPerRental ON Rentals.RentalId = CalculateCostPerRental.RentalId
/*cost is null when there is a rate missing in rate table*/
As for performance, they are poor, but it is due to your database design. Without, changing the design, it's going to be really hard to do better than what this solution does. However, I would challenge if if you really need rocking performance for this task.
Disclaimer : You should do some testing before using this in production because I haven't tested every edge case there is. Also, you might have missing rates in your rate table.

SQL, using Group by until specific trend (increment, decrement, same)

I would like to know how can i modify my code for considering all the same values of suppose 10 as UP till the time it is incrementing and then down for decrement and SAME if there is no change till the time there is no variation in the value (increment, decrement, same).
Here is my code :
;with etape1 as
(
select ROW_NUMBER() OVER(ORDER BY mnth) AS id,* from [InsideTSQL2008].[alioune].[Sales]
)
,
etape2 as
(
select
a.id, b.mnth AS START , a.mnth AS FINISH ,
a.qty - b.qty AS TREND
FROM
etape1 a
LEFT JOIN etape1 b
on a.id = b.id+1
)
select * from etape2;
My Result is :
id START FINISH TREND
1 NULL 2007-12-01 NULL
2 2007-12-01 2008-01-01 10
3 2008-01-01 2008-02-01 10
4 2008-02-01 2008-03-01 10
5 2008-03-01 2008-04-01 10
6 2008-04-01 2008-05-01 0
7 2008-05-01 2008-06-01 -10
8 2008-06-01 2008-07-01 -10
9 2008-07-01 2008-08-01 -10
10 2008-08-01 2008-09-01 -10
11 2008-09-01 2008-10-01 10
12 2008-10-01 2008-11-01 -10
13 2008-11-01 2008-12-01 20
14 2008-12-01 2009-01-01 10
15 2009-01-01 2009-02-01 10
16 2009-02-01 2009-03-01 -40
My final result as required should be like :
Start End Trend
200712 200712 unknown
200801 200804 UP
200805 200805 SAME
200806 200809 DOWN
200810 200810 UP
200811 200811 DOWN
200812 200812 UP
200903 200903 DOWN
200904 200905 SAME
200906 200907 UP
Any help would be really helpful; Thanks
Took me a few goes (and a few hours), but I think I have what you want:
DECLARE #Sales AS TABLE (mnth datetime, qty int)
INSERT INTO #Sales
SELECT '2016-01-01', 10 UNION ALL
SELECT '2016-02-01', 20 UNION ALL
SELECT '2016-03-01', 30 UNION ALL
SELECT '2016-04-01', 40 UNION ALL
SELECT '2016-05-01', 40 UNION ALL
SELECT '2016-06-01', 30 UNION ALL
SELECT '2016-07-01', 20 UNION ALL
SELECT '2016-08-01', 30 UNION ALL
SELECT '2016-09-01', 40 UNION ALL
SELECT '2016-10-01', 45 UNION ALL
SELECT '2016-11-01', 50
;WITH etape1 AS (
SELECT ROW_NUMBER() OVER(ORDER BY mnth) AS id, * FROM #Sales
)
, etape2 AS (
SELECT id, lag(mnth) OVER (ORDER BY id) AS START, mnth AS FINISH, CASE WHEN qty - LAG(qty) OVER (ORDER BY id) < 0 THEN -1 WHEN qty - LAG(qty) OVER (ORDER BY id) > 0 THEN 1 ELSE 0 END AS TREND
FROM etape1
)
, etape3 AS (
SELECT id, START, FINISH, TREND, lag(TREND) OVER (ORDER BY id) AS PrevTrend
FROM etape2
)
, etape4 AS (
SELECT id, START, FINISH, TREND, SUM(CASE WHEN TREND = PREVTREND THEN 0 ELSE 1 END) OVER (ORDER BY id ROWS UNBOUNDED PRECEDING) AS Change
FROM etape3
)
SELECT MIN(START) AS START, MAX(FINISH) AS FINISH, CASE WHEN MIN(TREND) IS NULL THEN 'Unknown' WHEN MIN(TREND) < 0 THEN 'Down' WHEN MIN(TREND) > 0 THEN 'Up' WHEN MIN(Start) is NULL THEN 'Unknown' ELSE 'Same' END AS TREND
FROM etape4
GROUP BY Change
ORDER BY START
Results are:
START FINISH TREND
NULL 2016-01-01 Unknown
2016-01-01 2016-04-01 Up
2016-04-01 2016-05-01 Same
2016-05-01 2016-07-01 Down
2016-07-01 2016-11-01 Up

grouping in sql server with for xml

I have a table in the following format
reporting_date interest_payment balance
200401 10 10
200402 20 15
200403 30 20
200404 40 30
200405 50 40
200406 60 50
200407 70 60
i wanted to generate an OUTPUT in the following format :
The output of the query should look like this :
reporting_date interest_payment balance
Q1 -2004 60 10
Q2 -2004 150 30
Q3 -2004 70 60
Q4 -2004 0 0
i.e i wanted to represent data by quarter and year and group by quarter and year for interest_payment column but for balance i need to pick up the value from the first reporting date in that quarter ,so as you can see q1-2004 has 10,15 and 20 but only 10 is accounted as that was the first reporting date in that quarter
I have my query working for interest payment but i am not sure how do i pickup the first reporting value for balance in a quarter
SELECT report_year as "#date",'Q'+CAST(report_quarter+1 as varchar(1)) as "#quarter", SUM(a.balance) as "#balance", SUM(a.interest_payment) as "#interest_payment"
FROM (SELECT *,
(reporting_date%100 - 1)/3 as report_quarter,
reporting_date/100 as report_year
FROM employee) a
GROUP by report_year, report_quarter
order by report_year, report_quarter
First, create all combination of years and quarters. Then use window functions such AS SUM OVER() and ROW_NUMBER() for the interest_payment and balance.
CREATE TABLE #temp(
reporting_date INT,
interest_payment INT,
balance INT
)
INSERT INTO #temp VALUES
(200401, 10, 10), (200402, 20, 15), (200403, 30, 20),
(200404, 40, 30), (200405, 50, 40), (200406, 60, 50),
(200407, 70, 60);
;WITH quarters(yr, qtr) AS(
SELECT
y.N, q.N
FROM(
SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4
) q(N)
CROSS JOIN(
SELECT DISTINCT reporting_date / 100 FROM #temp
) y(N)
)
,GroupedByQtr AS(
SELECT
*,
qtr = (reporting_date % 100 -1) / 3 + 1,
yr = reporting_date / 100,
ss = SUM(interest_payment) OVER(PARTITION BY (reporting_date / 100), ((reporting_date % 100 -1) / 3 + 1)),
rn = ROW_NUMBER() OVER(PARTITION BY (reporting_date / 100), ((reporting_date % 100 -1) / 3 + 1) ORDER BY reporting_date)
FROM #temp
)
SELECT
reporting_date = 'Q' + CONVERT(VARCHAR(1), q.qtr) + ' - ' + CONVERT(VARCHAR(4), q.yr),
interest_payment = ISNULL(g.ss, 0),
balance = ISNULL(g.balance, 0)
FROM quarters q
LEFT JOIN GroupedByQtr g
ON g.qtr = q.qtr
AND g.yr = q.yr
AND g.rn = 1
RESULT
reporting_date interest_payment balance
-------------- ---------------- -----------
Q1 - 2004 60 10
Q2 - 2004 150 30
Q3 - 2004 70 60
Q4 - 2004 0 0

Resources