I have a query that calculates duration of an incident. However, it doesn't include the current time for the still "open" incidents. I am trying to figure out a way to add that to the below. This is running on Azure SQL 12.0.2000.8. Per the example, Incident 18 and 19 are closed (last record has StatusID<>1), so my current calculation is correct. However, Incident 20 is ongoing (last record has StatusId=1) and needs to calculate the time between the last update and now.
Structure:
CREATE TABLE [dbo].[IncidentActions](
[Id] [INT] IDENTITY(1,1) NOT NULL,
[IncidentId] [INT] NOT NULL,
[ActionDate] [DATETIMEOFFSET](7) NOT NULL,
[Description] [NVARCHAR](MAX) NOT NULL,
[StatusId] [INT] NOT NULL
) ON [PRIMARY]
GO
INSERT INTO [dbo].[IncidentActions] VALUES
( 51, 18, N'2020-03-10T13:39:27.8621563+00:00', N'This is a demo of the app ops incident management portal', 1 ),
( 52, 18, N'2020-03-10T13:41:42.4306254+00:00', N'Superfast update we''re on it', 1 ),
( 53, 18, N'2020-03-10T13:42:19.0766735+00:00', N'Found a workaround', 1 ),
( 55, 18, N'2020-03-10T13:44:05.7958553+00:00', N'Suspending for now', 2 ),
( 56, 18, N'2020-03-10T13:44:49.732564+00:00', N'No longer suspended', 1 ),
( 57, 18, N'2020-03-10T13:45:09.8056202+00:00', N'All sorted', 3 ),
( 58, 19, N'2020-03-11T14:47:05.6968653+00:00', N'This is just a test', 1 ),
( 59, 19, N'2020-03-11T14:51:20.4522014+00:00', N'Found workaround and root cause, not yet fixed', 1 ),
( 60, 19, N'2020-03-11T14:52:34.857061+00:00', N'Networking issues, updates suspended', 2 ),
( 61, 19, N'2020-03-11T14:54:48.2262037+00:00', N'Network issue resolved, full functionality restored', 3 ),
( 62, 20, N'2020-03-12T10:49:11.5595048+00:00', N'There is an ongoing issue', 1 ),
( 63, 20, N'2020-03-12T11:29:37.9376805+00:00', N'This incident is ongoing....', 1 )
GO
CREATE TABLE [dbo].[IncidentStatuses](
[Id] [int] IDENTITY(1,1) NOT NULL,
[Description] [nvarchar](500) NOT NULL
) ON [PRIMARY]
GO
INSERT INTO [dbo].[IncidentStatuses] VALUES
( 1, N'OPEN' ), ( 2, N'SUSPENDED' ), ( 3, N'CLOSED' )
GO
Query:
WITH allActions
AS (SELECT IncidentId,
ActionDate,
IncidentStatuses.Description,
ROW_NUMBER() OVER (PARTITION BY IncidentId ORDER BY ActionDate) AS rowNum
FROM IncidentActions
INNER JOIN dbo.IncidentStatuses ON IncidentStatuses.Id = IncidentActions.StatusId
)
,actionPeriods
AS (SELECT firstAction.IncidentId,
firstAction.Description StartStatus,
secondAction.Description EndStatus,
DATEDIFF(SECOND, firstAction.ActionDate, secondAction.ActionDate) SecondsElapsed
FROM allActions firstAction
INNER JOIN allActions secondAction ON firstAction.rowNum +1 = secondAction.rowNum --the next action
AND firstAction.IncidentId = secondAction.IncidentId --for the same incident
)
SELECT
actionPeriods.IncidentId,
SUM(CASE WHEN actionPeriods.StartStatus = 'OPEN' THEN actionPeriods.SecondsElapsed ELSE 0 END) SecondsActive,
SUM(CASE WHEN actionPeriods.StartStatus <> 'OPEN' THEN actionPeriods.SecondsElapsed ELSE 0 END) SecondsInactive,
SUM(actionPeriods.SecondsElapsed) SecondsElapsed
FROM actionPeriods
GROUP BY actionPeriods.IncidentId
GO
Using a CTE is quit overkill. You could use tsql windowing functions. In this case the lag and or lead functions. I have added a sample code based on your tables.
select
IncidentId,
StatusId,actiondate ,
lag(actiondate) over (partition by incidentid order by incidentid, actiondate) as previousrow,
coalesce(
lead(actiondate) over (partition by incidentid order by incidentid, actiondate),
case
when (max(actiondate) over (partition by incidentid order by incidentid) = actiondate) and (statusid = 3) then actiondate
when (max(actiondate) over (partition by incidentid order by incidentid) = actiondate) and (statusid = 1) then convert([DATETIMEOFFSET](7),getdate())
end
) as nextrow
from
dbo.IncidentActions
order by
IncidentId, ActionDate
Applying LEAD instead of a self-join based on Row_number:
WITH allPeriods AS
(
SELECT IncidentId,
Lead(ActionDate) Over (PARTITION BY IncidentId ORDER BY ActionDate DESC) AS ActionDate,
Lead(st.Description) Over (PARTITION BY IncidentId ORDER BY ActionDate DESC) AS StartStatus,
st.Description AS EndStatus,
CASE -- return NOW if the last row is "open"
WHEN Row_Number() Over (PARTITION BY IncidentId ORDER BY ActionDate DESC) = 1
AND StatusId = 1
THEN getdate()
ELSE ActionDate
END AS nextDate
FROM IncidentActions AS act
JOIN dbo.IncidentStatuses AS st
ON st.Id = act.StatusId
),
elapsed AS
( SELECT *,
DATEDIFF(SECOND, ActionDate, nextDate) AS SecondsElapsed
FROM allPeriods
)
SELECT
IncidentId,
Sum(CASE WHEN StartStatus = 'OPEN' THEN SecondsElapsed ELSE 0 END) SecondsActive,
Sum(CASE WHEN StartStatus <> 'OPEN' THEN SecondsElapsed ELSE 0 END) SecondsInactive,
Sum(SecondsElapsed) SecondsElapsed
FROM elapsed
WHERE ActionDate IS NOT NULL
GROUP BY IncidentId
See fiddle
For the curious, the final version, courtesy of Remko
WITH actionPeriods /* This one determines the elapsed time between actions */
AS (SELECT IncidentId,
IncidentStatuses.Description StatusDesc,
/* LAG(ActionDate) OVER (PARTITION BY IncidentId ORDER BY IncidentId, ActionDate) AS previousrow, */
DATEDIFF(SECOND,ActionDate,
COALESCE(LEAD(ActionDate) OVER (PARTITION BY IncidentId ORDER BY IncidentId, ActionDate), /* Lead gets the next action */
CASE /* If the next aciton is NULL, then get either the current time for active, or the original time for closed */
WHEN (MAX(ActionDate) OVER (PARTITION BY IncidentId ORDER BY IncidentId) = ActionDate) AND (StatusId = (SELECT Id FROM dbo.IncidentStatuses WHERE Description = 'CLOSED')) THEN ActionDate
WHEN (MAX(ActionDate) OVER (PARTITION BY IncidentId ORDER BY IncidentId) = ActionDate) AND (StatusId <> (SELECT Id FROM dbo.IncidentStatuses WHERE Description = 'CLOSED')) THEN GETUTCDATE()
END
)
) SecondsElapsed
FROM dbo.IncidentActions
INNER JOIN dbo.IncidentStatuses ON IncidentStatuses.Id = IncidentActions.StatusId
)
SELECT
actionPeriods.IncidentId,
SUM(CASE WHEN actionPeriods.StatusDesc = 'OPEN' THEN actionPeriods.SecondsElapsed ELSE 0 END) SecondsActive, /* This counts periods that are active */
SUM(CASE WHEN actionPeriods.StatusDesc = 'SUSPENDED' THEN actionPeriods.SecondsElapsed ELSE 0 END) SecondsInactive, /* This count periods that are inactive */
SUM(CASE WHEN actionPeriods.StatusDesc IN ('OPEN','SUSPENDED') THEN actionPeriods.SecondsElapsed ELSE 0 END) SecondsElapsed /* We don't want periods that were CLOSED, as the ticket was not elapsing during that time */
FROM actionPeriods
GROUP BY actionPeriods.IncidentId
Related
I have a SQL Server 2016 database representing patients (pID), with visits (vID) to a healthcare facility. When patients move facilities, new visits are created.
I would like to piece together visits where the admit/discharge dates (represented by example ints vdStart and vdEnd) are close to one another (fuzzy joining), and display these as extra columns, thus having 1 row representing a patients healthcare journey. Future visits that aren't close to previous visits are separate journeys.
Here's some sample data:
CREATE TABLE t
(
[pID] varchar(7),
[vID] int,
[vdStart] int,
[vdEnd] int
);
INSERT INTO t ([pID], [vID], [vdStart], [vdEnd])
VALUES
('Jenkins', 1, 100, 102),
('Jenkins', 3, 102, 110),
('Jenkins', 7, 111, 130),
('Barnaby', 2, 90, 114),
('Barnaby', 5, 114, 140),
('Barnaby', 9, 153, 158),
('Forster', 4, 100, 130),
('Smith', 6, 120, 131),
('Smith', 8, 140, 160),
('Everett', 10, 158, 165),
('Everett', 12, 165, 175),
('Everett', 15, 186, 190),
('Everett', 17, 190, 195),
('Everett', 18, 195, 199),
('Everett', 19, 199, 210)
;
Here's an example of what I want:
Visits that all correspond to the same "healthcare journey" are joined. New row for each.
I wasn't able to get the PIVOT function to do what I wanted based on a fuzzy joining logic (which is supposed to represent datetimes)). My approach was using LEAD, however this quickly becomes silly when trying to connect beyond 2 visits, and it was showing incorrect values with gaps in between, which I don't want.
SELECT
pID,
vdStart,
vdEnd,
vID,
(
CASE WHEN ((
LEAD (vdStart, 1) OVER (PARTITION BY pID ORDER BY vdStart ASC)
) - vdEnd < 2) THEN (
LEAD (vID, 1) OVER (PARTITION BY pID ORDER BY vdStart ASC)
) ELSE NULL END
) AS vID2,
(
CASE WHEN ((
LEAD (vdStart, 2) OVER (PARTITION BY pID ORDER BY vdStart ASC)
) - (
LEAD (vdEnd, 1) OVER (PARTITION BY pID ORDER BY vdStart ASC)
) < 2) THEN (
LEAD (vID, 2) OVER (PARTITION BY pID ORDER BY vdStart ASC)
) ELSE NULL END
) AS vID3,
(
CASE WHEN ((
LEAD (vdStart, 3) OVER (PARTITION BY pID ORDER BY vdStart ASC)
) - (
LEAD (vdEnd, 2) OVER (PARTITION BY pID ORDER BY vdStart ASC)
) < 2) THEN (
LEAD (vID, 3) OVER (PARTITION BY pID ORDER BY vdStart ASC)
) ELSE NULL END
) AS vID4
FROM t
;
I'm unsure how else to approach this based on the fuzzy pivot logic I'm after. This only needs to be run occasionally, and should run in less than 10 minutes.
This is a classic gaps-and-islands problem.
One solution uses a conditional count
Get the each row's previous using LAG
Use a conditional count to number the groups of rows.
Use ROW_NUMBER to number each row within the group
Group up and pivot by pID and group ID.
WITH cte1 AS (
SELECT *,
PrevEnd = LAG(t.vdEnd) OVER (PARTITION BY t.pID ORDER BY t.vdStart)
FROM t
),
cte2 AS (
SELECT *,
GroupId = COUNT(CASE WHEN cte1.PrevEnd >= cte1.vdStart - 1 THEN NULL ELSE 1 END)
OVER (PARTITION BY cte1.pID ORDER BY cte1.vdStart ROWS UNBOUNDED PRECEDING)
FROM cte1
),
Numbered AS (
SELECT *,
rn = ROW_NUMBER() OVER (PARTITION BY cte2.pID, cte2.GroupID ORDER BY cte2.vdStart)
FROM cte2
)
SELECT
n.pID,
vdStart = MIN(n.vdStart),
vdEnd = MIN(n.vdEnd),
vID = MIN(CASE WHEN n.rn = 1 THEN n.vID END),
vID1 = MIN(CASE WHEN n.rn = 2 THEN n.vID END),
vID2 = MIN(CASE WHEN n.rn = 3 THEN n.vID END),
vID3 = MIN(CASE WHEN n.rn = 4 THEN n.vID END)
FROM Numbered n
GROUP BY
n.pID,
n.GroupID
ORDER BY
n.pID,
n.GroupID;
Another option you can use is a recursive algorithm
Get all rows which are starting rows (no previous rows in the sequence for this pID)
Recursively get the next row in the sequence, keeping track of the first row's vdStart.
Number the sequence results.
Group up and pivot by pID and sequence number.
WITH cte AS (
SELECT pID, vID, vdStart, vdEnd, GroupID = vdStart
FROM t
WHERE NOT EXISTS (SELECT 1
FROM t Other
WHERE Other.pID = t.pID
AND t.vdStart BETWEEN Other.vdEnd AND Other.vdEnd + 1)
UNION ALL
SELECT t.pID, t.vID, t.vdStart, t.vdEnd, cte.GroupID
FROM cte
JOIN t ON t.pID = cte.pID AND t.vdStart BETWEEN cte.vdEnd AND cte.vdEnd + 1
),
Numbered AS (
SELECT *,
rn = ROW_NUMBER() OVER (PARTITION BY cte.pID, cte.GroupID ORDER BY cte.vdStart)
FROM cte
)
SELECT
n.pID,
vdStart = MIN(n.vdStart),
vdEnd = MIN(n.vdEnd),
vID = MIN(CASE WHEN n.rn = 1 THEN n.vID END),
vID1 = MIN(CASE WHEN n.rn = 2 THEN n.vID END),
vID2 = MIN(CASE WHEN n.rn = 3 THEN n.vID END),
vID3 = MIN(CASE WHEN n.rn = 4 THEN n.vID END)
FROM Numbered n
GROUP BY
n.pID,
n.GroupID
ORDER BY
n.pID,
n.GroupID;
db<>fiddle
Splitting time from same column and storing into 2 column and finding difference between those 2 columns in sql server.
select SourceName,Active,EventTimeStamp,
case when Active=1
then EventTimeStamp END as ActiveTime,
case when Active=0
then EventTimeStamp END as InactiveTime
from dbo.AllEvent
where SourceName='R2_20'
order by SourceName
;
sname active EventTimeStamp Active Inavtive
R2_20 1 14:51.9 14:51.9 NULL
R2_20 0 14:53.9 NULL 14:53.9
R2_20 1 15:05.9 15:05.9 NULL
R2_20 0 15:07.9 NULL 15:07.9
R2_20 1 15:15.9 15:15.9 NULL
R2_20 0 15:17.9 NULL 15:17.9
R2_20 1 15:26.0 15:26.0 NULL
R2_20 0 15:28.0 NULL 15:28.0
R2_20 1 15:36.0 15:36.0 NULL
R2_20 0 15:38.0 NULL 15:38.0
If the version of SQL Server you are using supports the LEAD function, use it. But this is only my guess based on the data you provided.
SELECT
SourceName sname,
Active active,
EventTimeStamp start,
[end],
DATEDIFF(millisecond, EventTimeStamp, [end]) difference
FROM (
SELECT
*,
LEAD(EventTimeStamp)
OVER (PARTITION BY SourceName
ORDER BY EventTimeStamp, Active DESC) [end]
FROM AllEvents
) t
WHERE Active = 1;
But for a more complex data set, the following query may be preferable.
WITH
AllEvents AS (
SELECT
sn SourceName,
CAST(a as INT) Active,
CAST(ts as TIME) EventTimeStamp
FROM (VALUES
('R2_20', 1, '00:14:51.9'),
('R2_20', 1, '00:14:52.9'),
('R2_20', 0, '00:14:53.9'),
('R2_20', 1, '00:15:05.9'),
('R2_20', 0, '00:15:07.9'),
('R2_20', 1, '00:15:15.9'),
('R2_20', 1, '00:15:16.9'),
('R2_20', 0, '00:15:17.5'),
('R2_20', 0, '00:15:17.9'),
('R2_20', 1, '00:15:26.0'),
('R2_20', 0, '00:15:28.0'),
('R2_20', 1, '00:15:36.0'),
('R2_20', 0, '00:15:37.0'),
('R2_20', 0, '00:15:38.0')
) t(sn, a, ts)
),
a AS (
SELECT
*,
ROW_NUMBER() OVER (PARTITION BY SourceName ORDER BY EventTimeStamp) -
ROW_NUMBER() OVER (PARTITION BY SourceName, Active ORDER BY EventTimeStamp) g
FROM AllEvents
),
b AS (
SELECT
SourceName,
Active,
IIF(Active = 0, MAX(EventTimeStamp), MIN(EventTimeStamp)) start
FROM a
GROUP BY SourceName, Active, g
),
c AS (
SELECT
*,
LEAD(start) OVER (PARTITION BY SourceName ORDER BY start, Active DESC) [end],
DATEDIFF(millisecond,
start,
LEAD(start) OVER (PARTITION BY SourceName
ORDER BY start, Active DESC)) difference
FROM b
)
SELECT * FROM c WHERE Active = 1 ORDER BY start;
So I have a Visitor table, and a Visitor_activity table. Say:
Visitor
Visitor_ID Int
Visitor_name varchar(20)
Visitor_Activity
ID Int
Visitor_ID Int
Activity_Type char(3) -- values IN or OUT
Activity_Time datetime
Visitors might sign in and out multiple times in a day.
I'd like a nice query to tell me all visitors who are in: i.e. the last activity for today (on activity_time) was an "IN" not an "OUT". Any advice much appreciated.
It's T-SQL by the way, but I think it's more of an in-principle question.
One way to solve this is to use a correlated not exists predicate:
select Activity_Time, Visitor_ID
from Visitor_Activity t1
where Activity_Type = 'IN'
and not exists (
select 1
from Visitor_Activity
where Activity_Type = 'OUT'
and Visitor_ID = t1.Visitor_ID
and Activity_Time > t1.Activity_Time
and cast(Activity_Time as date) = cast(t1.Activity_Time as date)
)
This basically says get all visitor_id that have type = IN for which there doesn't exists any type = OUT record with a later time (on the same date).
Sample SQL Fiddle
SELECT
v.*
FROM
Visitors v
JOIN Visitor_Activity va ON va.Visitor_ID = v.Visitor_ID
WHERE
va.Activity_Type = 'IN'
AND NOT EXISTS ( SELECT
*
FROM
Visitor_Activity va_out
WHERE
va_out.Visitor_ID = va.Visitor_ID
AND va_out.Activity_Type = 'OUT'
AND va_out.Activity_Time > va.Activity_Time )
with visitorsInOut as (
select Visitor_id,
max(case when Activity_Type = 'in' then Activity_Time else null end) inTime,
max(case when Activity_Type = 'out' then Activity_Time else null end) outTime
from Visitor_Activity
where datediff(dd, Activity_Time, getdate()) = 0
group by Visitor_id)
select Visitor_id
from visitorsInOut
where inTime > outTime or outTime is null
This uses a CTE to find the activity record with the greatest Activity_Time where the Activity_Type = 'IN' and assigns it RowNum 1. Then you can INNER JOIN the CTE to the Visitor table, filtering by the CTE results where RowNum = 1.
; WITH VisAct AS(
SELECT act.Visitor_ID
, ROW_NUMBER() OVER(PARTITION BY Visitor_ID ORDER BY Activity_Time DESC) AS RowNum
FROM Visitor_Activity act
WHERE act.Activity_Type = 'IN'
AND act.Activity_Time >= CAST(GETDATE() AS DATE)
)
SELECT vis.Visitor_ID, vis.Visitor_name
FROM Visitor vis
INNER JOIN VisAct act
ON act.Visitor_ID = vis.Visitor_ID
WHERE act.Row_Num = 1
You can pull the most recent action for each visitor, and then only return those where the last action for today was to check in.
SELECT v.Visitor_ID, v.Visitor_Name, va.Activity_Type, va.Activity_Time
FROM Visitor AS v
INNER JOIN (SELECT Visitor_ID, Activity_Type, Activity_Time, RANK() OVER (PARTITION BY Visitor_ID ORDER BY Activity_Time DESC) AS LastAction
FROM Visitor_Activity
-- checks for today, can be omitted if you still want
-- to see someone checked in from yesterday
WHERE DATEDIFF(d, 0, Activity_Time) = DATEDIFF(d, 0, getdate())
) AS va ON va.Visitor_ID = v.Visitor_ID
WHERE LastAction = 1
AND Activity_Type = 'IN'
With CROSS APPLY:
DECLARE #d DATE = '20150320'
DECLARE #v TABLE
(
visitor_id INT ,
visitor_name NVARCHAR(MAX)
)
DECLARE #a TABLE
(
visitor_id INT ,
type CHAR(3) ,
time DATETIME
)
INSERT INTO #v
VALUES ( 1, 'A' ),
( 2, 'B' ),
( 3, 'C' )
INSERT INTO #a
VALUES ( 1, 'in', '2015-03-20 19:32:27.513' ),
( 1, 'out', '2015-03-20 19:32:27.514' ),
( 1, 'in', '2015-03-20 19:32:27.515' ),
( 2, 'in', '2015-03-20 19:32:27.516' ),
( 2, 'out', '2015-03-20 19:32:27.517' ),
( 3, 'in', '2015-03-20 19:32:27.518' ),
( 3, 'out', '2015-03-20 19:32:27.519' ),
( 3, 'in', '2015-03-20 19:32:27.523' )
SELECT *
FROM #v v
CROSS APPLY ( SELECT *
FROM ( SELECT TOP 1
type
FROM #a a
WHERE a.visitor_id = v.visitor_id
AND a.time >= #d
AND a.time < DATEADD(dd, 1, #d)
ORDER BY time DESC
) i
WHERE type = 'in'
) c
Output:
visitor_id visitor_name type
1 A in
3 C in
The principle:
First you are selecting all visitors.
Then you are applying to visitor last activity
SELECT TOP 1
type
FROM #a a
WHERE a.visitor_id = v.visitor_id
AND a.time >= #d
AND a.time < DATEADD(dd, 1, #d)
ORDER BY time DESC
Then you are selecting from previous step in order to get empty set which will filter out visitors whose last activity was not 'in'. If last activity was 'in' you get one row in result and thus applying works. If last activity is 'out' then outer query will result in empty set, and by design CROSS APPLY will eliminate such visitor.
I am trying to get the balance quantity at the end of every month i.e. running total at every month end with FIFO only but its not showing any result. Pls help
Here is my query.
declare #Stock table (Item char(3) not null,Date date not null,TxnType varchar(3) not null,Qty int not null,Price decimal(10,2) null)
insert into #Stock(Item , [Date] , TxnType, Qty, Price) values
('ABC','20120401','IN', 200, 750.00),
('ABC','20120402','OUT', 100 ,null ),
('ABC','20120403','IN', 50, 700.00),
('ABC','20120404','IN', 75, 800.00),
('ABC','20120405','OUT', 175, null ),
('XYZ','20120406','IN', 150, 350.00),
('XYZ','20120407','OUT', 120 ,null ),
('XYZ','20120408','OUT', 10 ,null ),
('XYZ','20120409','IN', 90, 340.00),
('ABC','20120510','IN', 200, 750.00),
('ABC','20120511','OUT', 100 ,null ),
('ABC','20120512','IN', 50, 700.00),
('ABC','20120513','IN', 75, 800.00),
('ABC','20120514','OUT', 175, null ),
('XYZ','20120515','IN', 150, 350.00),
('XYZ','20120516','OUT', 120 ,null ),
('XYZ','20120517','OUT', 10 ,null ),
('XYZ','20120518','IN', 90, 340.00);
;WITH OrderedIn as (
select *,ROW_NUMBER() OVER (PARTITION BY month(date) ORDER BY DATE) as rn
from #Stock
where TxnType = 'IN'
), RunningTotals as (
select Item,Qty,Price,Qty as Total,0 as PrevTotal,rn from OrderedIn where rn = 1
union all
select rt.Item,oi.Qty,oi.Price,rt.Total + oi.Qty,rt.Total,oi.rn
from
RunningTotals rt
inner join
OrderedIn oi
on
rt.Item = oi.Item and
rt.rn = oi.rn - 1
), TotalOut as (
select Item,SUM(Qty) as Qty from #Stock where TxnType='OUT' group by Item
)
select
rt.Item,SUM(CASE WHEN PrevTotal > out.Qty THEN rt.Qty ELSE rt.Total - out.Qty END * Price)
from
RunningTotals rt
inner join
TotalOut out
on
rt.Item = out.Item
where
rt.Total > out.Qty
group by rt.Item
Output
Month Item (No column name(qty*price))
4 ABC 40000
4 XYZ 37600
5 ABC 77500
5 XYZ 76100
With you sample data.Are you looking for this,else clearly sketch your desired output .
;WITH CTE AS
(
select Item ,[Date] ,TxnType ,s.Qty
,Price ,ROW_NUMBER() OVER (PARTITION BY month(date),TxnType ORDER BY DATE) as rn
from #Stock S
)
,
CTE1 AS
(
SELECT ITEM ,[DATE] ,TXNTYPE ,QTY,MONTH(DATE) MONTHDT,RN FROM CTE WHERE RN=1
UNION ALL
SELECT S.ITEM ,S.[DATE] ,S.TXNTYPE ,S.QTY+A.QTY,A.MONTHDT ,S.RN
FROM CTE S INNER JOIN CTE1 A
ON S.TXNTYPE=A.TXNTYPE AND MONTH(S.[DATE])=A.MONTHDT AND S.RN-A.RN=1
)
,CTE2 AS
(
SELECT *
,ROW_NUMBER() OVER (PARTITION BY month(date),TxnType ORDER BY QTY DESC )RN1
FROM CTE1 S
)
SELECT A.DATE,A.QTY-(SELECT B.Qty from CTE2 B
where a.MONTHDT=b.MONTHDT and b.RN1=1 AND b.TxnType='OUT')
from CTE2 A
WHERE A.RN1=1 AND a.TxnType='IN'
Try this.
;WITH cte
AS (SELECT Row_number()OVER(partition BY item, Datepart(mm, date) ORDER BY date DESC) rn,
item,
Month(date) AS [month],
TxnType,
Price,
date
FROM #Stock
WHERE TxnType = 'IN'),
cte1
AS (SELECT Row_number() OVER(partition BY item, Datepart(mm, date) ORDER BY date DESC) rn,
item,
Month(date) [month],
(SELECT Sum(CASE
WHEN TxnType = 'out' THEN -1 * qty
ELSE qty
END) AS qty
FROM #Stock b
WHERE a.item = b.item
AND a.Date >= b.Date) qty
FROM #Stock a)
SELECT a.[month],
a.Item,
( a.Price * b.qty ) running_tot
FROM cte a
JOIN cte1 b
ON a.Item = b.Item
AND a.[month] = b.[month]
WHERE a.rn = 1
AND b.rn = 1
I have a table that's structured like the one below. I'm wondering if it's possible to compress each transaction by the TransactionID, pull values from the first and last row, then finally calculate a duration for the transaction.
For instance below, I would like to pull RecordID and CustomerID. Then alias a new column called startingStatus which would be 'Submitted' from the first row. I would alias a second column called endingStatus that would be 'Success' from the last row. I then need to grab the timestamps in the same manner, creating startingTime and endingTime. The final column would be the difference between starting and ending in seconds.
RecordID TransactionID CustomerID Status TimeStamp
1 12 10 Submitted 04/07/2014 14:32:23
2 12 10 Queued 04/07/2014 14:32:24
3 12 10 Processing 04/08/2014 14:32:26
4 12 10 Error 04/09/2014 14:32:27
5 12 10 Resubmitted 04/10/2014 15:12:29
6 12 10 Queued 04/11/2014 15:12:31
7 12 10 Processing 04/12/2014 15:12:34
8 12 10 Success 04/13/2014 15:12:47
I've been trying to group by TransactionID and using MIN and MAX, but I haven't gotten it working yet.
How would I go about doing something like this?
Give this a try:
SELECT
CustomerID,
TransactionID,
Min([TimeStamp]) as StartTime,
Max([TimeStamp]) as EndTime,
DATEDIFF(MINUTE,Min([TimeStamp]),Max([TimeStamp])) as TransactionTime
FROM YourTable
GROUP BY CustomerID, TransactionID
ORDER BY CustomerID, TransactionID
I would use window functions to retrieve the first and last records for a group.
This will work for SQL Server 2005 and later by using the ROW_NUMBER() window function and then pivoting the results with the MAX/CASE WHEN method:
SELECT
[TransactionID],
[CustomerID],
MAX(CASE WHEN [rn_asc] = 1 THEN [Status] ELSE NULL END) [startingStatus],
MAX(CASE WHEN [rn_desc] = 1 THEN [Status] ELSE NULL END) [endingStatus],
MAX(CASE WHEN [rn_asc] = 1 THEN [TimeStamp] ELSE NULL END) [startingTimeStamp],
MAX(CASE WHEN [rn_desc] = 1 THEN [TimeStamp] ELSE NULL END) [endingTimeStamp],
DATEDIFF(
SECOND,
MAX(CASE WHEN [rn_asc] = 1 THEN [TimeStamp] ELSE NULL END),
MAX(CASE WHEN [rn_desc] = 1 THEN [TimeStamp] ELSE NULL END)
) [duration]
FROM
(
SELECT
[TransactionID],
[CustomerID],
ROW_NUMBER() OVER (PARTITION BY [TransactionID], [CustomerID] ORDER BY [RecordID] ASC) [rn_asc],
ROW_NUMBER() OVER (PARTITION BY [TransactionID], [CustomerID] ORDER BY [RecordID] DESC) [rn_desc],
[Status],
[Timestamp]
FROM [tbl]
) A
GROUP BY
[TransactionID],
[CustomerID]
SQL Server 2012 introduced the FIRST_VALUE function, so if you're running that version you can consider this query
SELECT DISTINCT
[TransactionID],
[CustomerID],
FIRST_VALUE([Status]) OVER (PARTITION BY [TransactionID], [CustomerID] ORDER BY [RecordID] ASC) [startingStatus],
FIRST_VALUE([Status]) OVER (PARTITION BY [TransactionID], [CustomerID] ORDER BY [RecordID] DESC) [endingStatus],
FIRST_VALUE([TimeStamp]) OVER (PARTITION BY [TransactionID], [CustomerID] ORDER BY [RecordID] ASC) [startingTimeStamp],
FIRST_VALUE([TimeStamp]) OVER (PARTITION BY [TransactionID], [CustomerID] ORDER BY [RecordID] DESC) [endingTimeStamp],
DATEDIFF(
SECOND,
FIRST_VALUE([TimeStamp]) OVER (PARTITION BY [TransactionID], [CustomerID] ORDER BY [RecordID] ASC),
FIRST_VALUE([TimeStamp]) OVER (PARTITION BY [TransactionID], [CustomerID] ORDER BY [RecordID] DESC)
) [duration]
FROM [tbl]