Finding consecutive records - tsql

I have followed code which is supposed to give me consecutive records using PlanId and costcentreid. for example in the image the record number 7 has costcentreid of 14 and the costcentreid before that is 5, so then it will ignore all the records before that and return me StartDate of 2017-07-12. if the previous record was of the same cost centre, then it would keep going back till the cost centre is different and then return me the lowest date but it's not doing that. i have provided my sql. Could you please help?
Sample Data:
Scenario 1: Correct Answer should be '2017-07-12 11:56:52.560'
DECLARE #T TABLE (StartDate, PlanId, CostCentreId, PositionId, CostCentreFlavourID, CustomerPositionID)
INSERT #T(StartDate, PLanId, CostCentreID, PositionId, CostCentreFlavourID, CustomerPOsitionID)
VALUES('1998-10-23 00:00:00.000', 19130, 14, 129, 3, 1, 766 ),
('2010-06-22 00:00:00.000', 19130, 207, 25, 3, 1,16247),
('2012-05-01 16:27:04.460', 19130, 42, 14, 3, 1,23946),
('2013-04-30 18:57:57.617', 19130, 295, 14, 3, 1,29453),
('2015-03-03 09:31:28.133', 19130, 275, 5, 3,1, 39286),
('2015-06-26 15:48:35.637', 19130, 195, 5, 3,1,41985),
('2017-07-12 11:56:52.560', 19130, 1445, 14, 3, 1,57699)
Scenario 2: Correct Answer should be : '2015-06-26 15:48:35.637'
DECLARE #T TABLE (StartDate, PlanId, CostCentreId, PositionId, CostCentreFlavourID, CustomerPositionID)
INSERT #T(StartDate, PLanId, CostCentreID, PositionId, CostCentreFlavourID, CustomerPOsitionID)
VALUES('1998-10-23 00:00:00.000', 19130, 14, 129, 3,1,766 ),
('2010-06-22 00:00:00.000', 19130, 207, 25, 3, 1,16247),
('2012-05-01 16:27:04.460', 19130, 42, 14, 3, 1,23946),
('2013-04-30 18:57:57.617', 19130, 295, 14, 3, 1,29453),
('2015-03-03 09:31:28.133', 19130, 275, 5, 3, 1,39286),
('2015-06-26 15:48:35.637', 19130, 195, 14, 3,1, 41985),
('2017-07-12 11:56:52.560', 19130, 1445, 14, 3, 1,57699)
Scenario 3: Correct Answer should be: '2012-05-01 16:27:04.460'
DECLARE #T TABLE (StartDate, PlanId, CostCentreId, PositionId, CostCentreFlavourID, CustomerPositionID)
INSERT #T(StartDate, PLanId, CostCentreID, PositionId, CostCentreFlavourID, CustomerPOsitionID)
VALUES('1998-10-23 00:00:00.000', 19130, 14, 129, 3,1,766 ),
('2010-06-22 00:00:00.000', 19130, 207, 25, 3,1, 16247),
('2012-05-01 16:27:04.460', 19130, 42, 14, 3,1, 23946),
('2013-04-30 18:57:57.617', 19130, 295, 14, 3,1, 29453),
('2015-03-03 09:31:28.133', 19130, 275, 14, 3, 1,39286),
('2015-06-26 15:48:35.637', 19130, 195, 14, 3,1, 41985),
('2017-07-12 11:56:52.560', 19130, 1445, 14, 3,1, 57699)
WITH cte
AS (SELECT cp1.StartDate,
fp.PlanId,
p.CostCentreID,
p.PositionID,
fp.CostCentreFlavourId,
fp.CustomerWithNDISNumberOfPlans,
cp1.CustomerPositionID,
ROW_NUMBER() OVER (PARTITION BY cp1.CustomerID ORDER BY cp1.StartDate) AS CustomerRow,
ROW_NUMBER() OVER (PARTITION BY cp1.CustomerID, p.CostCentreID ORDER BY cp1.StartDate) AS CustomerCostCentreRow ,
ROW_NUMBER() OVER (PARTITION BY cp1.CustomerID ORDER BY cp1.StartDate)
- ROW_NUMBER() OVER (PARTITION BY cp1.CustomerID, p.CostCentreID ORDER BY cp1.StartDate) rn3
FROM #FlavouredPlans fp
INNER JOIN dbo.tblCustomerPositions cp1
ON cp1.CustomerID = fp.LADSCustomerID
INNER JOIN dbo.tblPositions p
ON p.PositionID = cp1.PositionID
--AND fp.CostCentreId = p.CostCentreID
WHERE fp.CostCentreFlavourId = 3
AND fp.OrderOfPlans = 1
),
ctePositionStartDate
AS (SELECT *,
MIN(cte.StartDate) OVER (PARTITION BY PlanId, CostCentreID, startdate, rn3) MinStartDate,
ROW_NUMBER() OVER (PARTITION BY PlanId ORDER BY cte.StartDate ASC) [Order]
FROM cte )
SELECT *
FROM ctePositionStartDate
WHERE ctePositionStartDate.planid = 19130

You can get your result by using window functions. Check this query
DECLARE #T TABLE (StartDate datetime, PlanId int, CostCentreId int, PositionId int, CostCentreFlavourID int, CustomerPositionID int)
INSERT #T(StartDate, PLanId, PositionId, CostCentreID, CostCentreFlavourID, CustomerPOsitionID)
VALUES
('19981023 00:00:00.000', 19130, 14, 129, 3,1),
('20100622 00:00:00.000', 19130, 207, 25, 3,1),
('20120501 16:27:04.460', 19130, 42, 14, 3,1),
('20130430 18:57:57.617', 19130, 295, 12, 3,1),
('20150303 09:31:28.133', 19130, 275, 14, 3, 1),
('20150626 15:48:35.637', 19130, 195, 14, 3,1),
('20170712 11:56:52.560', 19130, 1445, 14, 3,1)
select
*
from (
select
*, row_number() over (partition by PlanId, CostCentreID, rn1 - rn2 order by StartDate) r3
, max(StartDate) over (partition by PlanId, CostCentreID, rn1 - rn2) mx1
, max(StartDate) over (partition by PlanId) mx2
from (
select
*, row_number()over(partition by PlanId order by StartDate) rn1
, row_number()over(partition by PlanId, CostCentreID order by StartDate) rn2
from
#T
) t
) t
where
mx1 = mx2
and r3 = 1

Related

Sort Date Names Accurately by Partition

I have the following data:
with cte (id,[months]) as (
select 1, 'July 2019' union all
select 2, 'July 2019' union all
select 3, 'July 2019' union all
select 4, 'July 2019' union all
select 5, 'August 2019' union all
select 6, 'August 2019' union all
select 7, 'September 2019' union all
select 8, 'October 2019' union all
select 9, 'November 2019' union all
select 10, 'December 2019' union all
select 11, 'January 2020' union all
select 12, 'January 2020' union all
select 13, 'January 2020' union all
select 14, 'January 2020' union all
select 15, 'February 2020' union all
select 16, 'March 2020' union all
select 17, 'March 2020' union all
select 18, 'April 2020' union all
select 19, 'May 2020' union all
select 20, 'June 2020'
)
I require to create a Sort Column that ranks all the same months with the same number.
The problem I experienced with the following code is, it does not sort correctly and also does not provide me with the expected results:
select
*
, dense_rank() over (partition by months order by id) Sort
from cte
Current Results:
My expected results:
How should I change my script to achieve this?
After a lot of struggle, I managed to resolve this with this script:
select
*
, convert(date,'01 '+Months) MonthsConvertedToDate
, dense_rank() over (order by convert(date,'01 '+Months)) Sort
from cte
order by sort,id
Results:
See demo here
I am however open to better suggestions :-)

How to increment counter based on date selection

I have a table chequeBounce with columns
Accountid ,Accountname,Chdate,chno,chamount
Now, I am creating a query which results the count of Accountid in a month. I need to increment the count of current Accountid month with previous month value based on date select by user.
Anybody can guide?
It's hard to know exactly what it is that you want from your question, but I made a few assumptions myself and came up with a stored procedure that might do what you want. The reason I choose a stored procedure is beacuse you imply that there should be a user defined variable.
So first of all, create this Stored Procedure:
CREATE PROCEDURE dbo.Get_Count
#month1 int,
#month2 int
AS
SELECT [source].AccountName, sum([source].chamount) as [sum of amount], count([source].AccountID) as [no of AccountID in specified months] FROM
(SELECT
AccountID, AccountName, chno, chamount
FROM chequeBounce
WHERE
MONTH(Chdate) = #month1
UNION ALL
SELECT
AccountID, AccountName, chno, chamount
FROM chequeBounce
WHERE
MONTH(Chdate) = #month2) as [source]
GROUP BY [source].AccountID, [source].AccountName, [source].chamount
RETURN 0
This Stored Procedure takes two parameters, #month1, and #month2. Then it runs two queries. One with a where statement that matches #month1 and another that does the exact same, but on #month2. It then unions them both together into one query and counts how many entries there is on each AccountID for the specified months. It allso sums the amount up the same way. You run the procedure by typing exec Get_Count followed by the two months you want to count on. Like this, if you want February and March:
exec Get_Count 2, 3
The results from above query could look something like this:
AccountName sum of amount no of AccountID in specified months
-------------------------------------------------- --------------------- ---------------
NameOne 200.00 2
NameTwo 100.00 1
NameThree 100.00 1
NameFour 200.00 2
NameFive 100.00 1
In this example no of AccountID in specified months is the count of how many occurences there is on each accountID for the specified months (2, 3).
This is the data I've used:
create table chequeBounce (
AccountID int,
AccountName varchar(50),
Chdate date,
chno int,
chamount money,
)
insert into chequeBounce VALUES
(1, 'NameOne', GETDATE(), 123, 100)
,(2, 'NameTwo', GETDATE(), 123, 100)
,(3, 'NameThree', GETDATE(), 123, 100)
,(4, 'NameFour', GETDATE(), 123, 100)
,(5, 'NameFive', GETDATE(), 123, 100)
,(1, 'NameOne', DATEADD(mm, -1, GETDATE()), 123, 100)
,(2, 'NameTwo', DATEADD(mm, -2, GETDATE()), 123, 100)
,(4, 'NameFour', DATEADD(mm, -4, GETDATE()), 123, 100)
,(5, 'NameFive', DATEADD(mm, -5, GETDATE()), 123, 100)
,(1, 'NameOne', DATEADD(mm, -2, GETDATE()), 123, 100)
,(2, 'NameTwo', DATEADD(mm, -3, GETDATE()), 123, 100)
,(4, 'NameFour', DATEADD(mm, -5, GETDATE()), 123, 100)
,(4, 'NameFour', DATEADD(mm, -3, GETDATE()), 123, 100)
,(4, 'NameFour', DATEADD(mm, -1, GETDATE()), 123, 100)
,(5, 'NameFive', DATEADD(mm, -1, GETDATE()), 123, 100)
,(1, 'NameOne', DATEADD(mm, -5, GETDATE()), 123, 100)
,(3, 'NameThree', DATEADD(mm, -1, GETDATE()), 123, 100)
,(4, 'NameFour', DATEADD(mm, -1, GETDATE()), 123, 100)
,(5, 'NameFive', DATEADD(mm, -3, GETDATE()), 123, 100)
Feel free to ask any questions if I did not make this very clear :)

Summing arrays in conjunction with GROUP BY

I've got some periodic counter data (like once a second) from different objects that I wish to combine into an hourly total.
If I do it with separate column names, it's pretty straightforward:
CREATE TABLE ts1 (
id INTEGER,
ts TIMESTAMP,
count0 integer,
count1 integer,
count2 integer
);
INSERT INTO ts1 VALUES
(1, '2017-12-07 10:37:48', 10, 20, 50),
(2, '2017-12-07 10:37:48', 13, 7, 88),
(1, '2017-12-07 10:37:49', 12, 23, 34),
(2, '2017-12-07 10:37:49', 11, 13, 46),
(1, '2017-12-07 10:37:50', 8, 33, 80),
(2, '2017-12-07 10:37:50', 9, 3, 47),
(1, '2017-12-07 10:37:51', 17, 99, 7),
(2, '2017-12-07 10:37:51', 9, 23, 96);
SELECT id, date_trunc('hour', ts + '1 hour') nts,
sum(count0), sum(count1), sum(count2)
FROM ts1 GROUP BY id, nts;
id | nts | sum | sum | sum
----+---------------------+-----+-----+-----
1 | 2017-12-07 11:00:00 | 47 | 175 | 171
2 | 2017-12-07 11:00:00 | 42 | 46 | 277
(2 rows)
The problem is that different objects have different numbers of counts (though each particular object's rows -- ones sharing the same ID -- all have the same number of counts). Hence I want to use an array.
The corresponding table looks like this:
CREATE TABLE ts2 (
id INTEGER,
ts TIMESTAMP,
counts INTEGER[]
);
INSERT INTO ts2 VALUES
(1, '2017-12-07 10:37:48', ARRAY[10, 20, 50]),
(2, '2017-12-07 10:37:48', ARRAY[13, 7, 88]),
(1, '2017-12-07 10:37:49', ARRAY[12, 23, 34]),
(2, '2017-12-07 10:37:49', ARRAY[11, 13, 46]),
(1, '2017-12-07 10:37:50', ARRAY[8, 33, 80]),
(2, '2017-12-07 10:37:50', ARRAY[9, 3, 47]),
(1, '2017-12-07 10:37:51', ARRAY[17, 99, 7]),
(2, '2017-12-07 10:37:51', ARRAY[9, 23, 96]);
I have looked at this answer https://stackoverflow.com/a/24997565/1076479 and I get the general gist of it, but I cannot figure out how to get the correct rows summed together when I try to combine it with the grouping by id and timestamp.
For example, with this I get all the rows, not just the ones with matching id and timestamp:
SELECT id, date_trunc('hour', ts + '1 hour') nts, ARRAY(
SELECT sum(elem) FROM ts2 t, unnest(t.counts)
WITH ORDINALITY x(elem, rn) GROUP BY rn ORDER BY rn
) FROM ts2 GROUP BY id, nts;
id | nts | array
----+---------------------+--------------
1 | 2017-12-07 11:00:00 | {89,221,448}
2 | 2017-12-07 11:00:00 | {89,221,448}
(2 rows)
FWIW, I'm using postgresql 9.6
The problem with you original query is that you're summing all elements, because GROUP BY id, nts is executed in outer query. Combining a CTE with LATERAL JOIN would do the trick:
WITH tmp AS (
SELECT
id,
date_trunc('hour', ts + '1 hour') nts,
sum(elem) AS counts
FROM
ts2
LEFT JOIN LATERAL unnest(counts) WITH ORDINALITY x(elem, rn) ON TRUE
GROUP BY
id, nts, rn
)
SELECT id, nts, array_agg(counts) FROM tmp GROUP BY id, nts

List Hours worked with multiple shifts and across dates

I have a few questions on how to handle a few employee timekeeping queries in SQL 2012… I had asked another quesiton last week about determining the time between shifts, which I as able to get a great response to.
We are fed the data from our Point Of Sale Software Provider, so we cannot change the format of the data.
Work days are based on DateOfBusiness which are from 5:00 am until 4:59am the next calendar day, so it crosses midnight. Locations End Of Day process is offset a little to control balance loads, some are 5:05am instead of 5:00am. When the End Of Day process runs, it clocks everyone out then clocks them back in when the process completes.
- Employees clock out for the 30 minute required meal breaks, so there are 2 or more records for one day for these employees. Not sure why, but several employees have more than 3 records in one day (EmployeeShiftNumber).
- Shifts occasionally cross DateOfBusiness. In at 11:00PM - Out at 7:00am
I need to report list of initial InTime and Final OutTime and the number of minutes worked. I have will have to compare these against the table which holds the employee schedules. This reporting is not for Payroll purposes, but for comparisons against scheduled shifts.
I have included some sample data grouped by employee and by day and the values I would expect to see commented to the right. I also need to see the number of minutes worked in the shift.
--drop table #Shift
CREATE TABLE #Shift(
FKEmployeeNumber int,
DateOfBusiness datetime,
FKStoreId int,
EmployeeShiftNumber int,
FKJobCodeId int,
InHour int,
InMinute int,
OutHour int,
OutMinute int)
insert into #Shift ( FKEmployeeNumber, DateOfBusiness, FKStoreId, EmployeeShiftNumber, FKJobCodeId, InHour, InMinute,OutHour,OutMinute)
values
(23761, '11/30/2017', 3013, 0, 1, 17, 39, 21, 30),
(23761, '11/30/2017', 3013, 1, 1, 21, 30, 2, 39), -- 5:39PM 2:39AM
(23770, '11/30/2017', 3013, 0, 200, 7, 19, 16, 25), -- 7:19AM 4:25PM
(23938, '11/30/2017', 3013, 0, 1, 16, 4, 1, 26), -- 4:04AM 1:26AM
(24006, '11/30/2017', 3013, 0, 1, 7, 30, 18, 36),
(24006, '11/30/2017', 3013, 1, 1, 18, 36, 18, 40), -- 7:30AM 6:40PM
(24018, '11/30/2017', 3013, 0, 2, 8, 52, 17, 0), -- 8:52M 4:00PM
(25176, '11/30/2017', 3013, 0, 200, 15, 59, 20, 1), -- 3:59PM 8:01PM
(25176, '11/30/2017', 3013, 1, 200, 20, 30, 0, 05), -- 8:30PM 12:05AM
(25180, '11/30/2017', 3013, 0, 1, 21, 0, 5, 0), -- 9:00PM 5:00AM
(25187, '11/30/2017', 3013, 0, 1, 10, 0, 16, 6), -- 10:00AM 4:06PM
(35189, '11/30/2017', 3013, 0, 1, 16, 58, 2, 4), -- 4:58PM 2:04AM
(25147, '12/04/2017', 3106, 0, 1, 6, 58, 15, 2),
(25147, '12/04/2017', 3106, 1, 1, 15, 3, 15, 3), -- 6:58AM 3:03PM
(26291, '12/01/2017', 3118, 1, 200, 23, 15, 5, 5),
(26291, '12/02/2017', 3118, 0, 200, 5, 6, 7, 22), -- 11:15PM 7:22AM
(26291, '12/03/2017', 3118, 0, 200, 7, 30, 15, 38), -- 7:30AM 3:38PM
(26291, '12/04/2017', 3118, 0, 200, 23, 15, 5, 5),
(26291, '12/05/2017', 3118, 0, 200, 5, 6, 7, 12), -- 11:15PM 7:12AM
(26291, '12/05/2017', 3118, 1, 200, 23, 15, 5, 5),
(26291, '12/06/2017', 3118, 0, 200, 15, 14, 7, 5) -- 11:15PM 7:05AM
--Select * from #Shift
SELECT fkstoreid, FKEmployeeNumber AS EmpID, DateOfBusiness AS Date,
RIGHT(CONVERT(varchar(30), DATEADD(MINUTE, InTime_Mins, 0), 100), 7) AS InTime,
RIGHT(CONVERT(varchar(30), DATEADD(MINUTE, OutTime_Mins, 0), 100), 7) AS OutTime,
MinsWorked
FROM (
select sh.FKStoreId, sh.FKEmployeeNumber, sh.DateOfBusiness,
MIN(sh.InHour*60+InMinute) AS InTime_Mins,
MAX(sh.OutHour*60+OutMinute) AS OutTime_Mins,
SUM(((sh.outhour+case when sh.OutHour < sh.InHour then 24 else 0 end)*60 + sh.outminute) -
(sh.inhour*60 + sh.inminute)) AS MinsWorked
from #shift sh
group by sh.FKStoreId,sh.FKEmployeeNumber, sh.DateOfBusiness
) AS derived
order by FKEmployeeNumber, DateOfBusiness
I've written a sample of how you could do it.
Briefly summarized I perform these steps:
Mark records that should be grouped with either the next or the previous record, because they are a part of the same shift.
Find the start and end of each shift
Join all records for an employee that fall within each shift
Group the resultset per employee per shift, and calculate the minutes worked by summing up the minutes between checkin and checkout for each record within a shift.
Sample query:
WITH ShiftParts AS (
SELECT
FKEmployeeNumber,
DATEADD(MINUTE, InMinute, DATEADD(HOUR, InHour, DateOfBusiness)) CheckIn, --Datetime of the checkin
DATEADD(DAY, CASE WHEN OutHour < InHour THEN 1 ELSE 0 END,
DATEADD(MINUTE, OutMinute, DATEADD(HOUR, OutHour, DateOfBusiness))) CheckOut --Datetime of the checkout (add one day if we crossed midnight).
FROM #Shift
),
GroupInfo AS (
SELECT
*,
CASE
WHEN DATEDIFF(MINUTE, LAG(CheckOut, 1, NULL) OVER (PARTITION BY FKEmployeeNumber ORDER BY CheckOut), CheckIn) <= 120
THEN 1 ELSE 0 END AS GroupWithPrevious, --Determine whether we want to group this record with the previous
CASE
WHEN DATEDIFF(MINUTE, CheckOut, LEAD(CheckIn, 1, NULL) OVER (PARTITION BY FKEmployeeNumber ORDER BY CheckOut)) <= 120
THEN 1 ELSE 0 END AS GroupWithNext --Determine whether we want to group this record with the next
FROM ShiftParts
), ShiftStartAndEnd AS (
SELECT
*,
CASE WHEN GroupWithNext = 1 THEN LEAD(CheckOut, 1, NULL) OVER (PARTITION BY FKEmployeeNumber ORDER BY CheckOut) ELSE CheckOut END AS FinalCheckOut
FROM GroupInfo
WHERE GroupWithPrevious = 0 OR GroupWithNext = 0 --Only pick beginning and end of a shift
)
SELECT
sse.FKEmployeeNumber,
sse.CheckIn,
sse.FinalCheckOut,
SUM(DATEDIFF(MINUTE,sp.CheckIn,sp.CheckOut)) AS MinutesWorked
FROM ShiftStartAndEnd sse
LEFT OUTER JOIN ShiftParts sp ON sp.FKEmployeeNumber = sse.FKEmployeeNumber AND sp.CheckIn >= sse.CheckIn AND sp.CheckOut <= sse.FinalCheckOut
WHERE sse.GroupWithPrevious = 0
GROUP BY sse.FKEmployeeNumber, sse.CheckIn, sse.FinalCheckOut
Note: I think the last two records in your sample data should not be grouped
(26291, '12/05/2017', 3118, 1, 200, 23, 15, 5, 5),
(26291, '12/06/2017', 3118, 0, 200, 15, 14, 7, 5) -- 11:15PM 7:05AM
should be:
(26291, '12/05/2017', 3118, 1, 200, 23, 15, 5, 5), -- 11:19PM 5:05AM
(26291, '12/06/2017', 3118, 0, 200, 15, 14, 7, 5) -- 3:14PM 7:05AM

TSQL combine multiple records

I have a table that contains:
Code ID1 ID2
Blue 1 2
Blue 3
Blue 4
Green 1 5
Green 10
Green 12
I need the result set:
Code ID1 ID2
Blue 1 2
Blue 1 3
Blue 1 4
Blue 1 5
Green 1 5
Green 1 10
Green 1 12
The number in ID1 does not always start with a 1.
Any ideas?? I am stumped!
Drop Table T1
Create table T1( Code varchar(10), Id1 int, Id2 int )
Insert T1 Values
('Blue', 11, 2),
('Blue', 12, 3),
('Blue', NULL, 4),
('Blue', 15, 5),
('Blue', NULL, 6),
('Green', 23, 21),
('Green', 24, 24),
('Green',NULL, 25),
('Green',NULL, 28),
('Green', 27, 29),
('Red', 35, 33),
('Red', NULL, 36),
('Red', NULL, 38)
SELECT
A.Code, ISNULL(A. Id1,B.LastId) Id1 , A.Id2
FROM T1 A
CROSS APPLY(
Select Top 1 Id1 as LastId
From T1
where A.Code = T1.Code and Id2 <= A.Id2
order by id1 desc
) B