I have this table where I am storing the Sale Orders. The scenario is that once any sale order is punched it is not finalized, and requires editing later on so if any more items are added and saved again the sale order is updated with transaction number more than the previous one to keep the track of the changes. Here is a sample data that a sale order was punched and then 2 times more items were added and amount was changed and in the last row as shown items were cancelled and amount was changed.
I want to calculate the amount of the additions made in the sale order every time new items were added and the cancellations as well that how much worth of items were cancelled.
CREATE TABLE SaleOrder
(
TransactionNo Int,
SaleOrderDate DATE,
Code VARCHAR(25),
Quantity INT,
TotalAmount Numeric(18,2),
Remarks VARCHAR(25)
)
INSERT INTO SaleOrder VALUES (NULL, '2018-10-01', 'SO-001-OCT-18', 6, '2500', 'Hello');
INSERT INTO SaleOrder VALUES (1, '2018-10-01', 'SO-001-OCT-18', 8, '2600', 'Hello');
INSERT INTO SaleOrder VALUES (2, '2018-10-01', 'SO-001-OCT-18', 12, '3400', 'Hello');
INSERT INTO SaleOrder VALUES (3, '2018-10-01', 'SO-001-OCT-18', 9, '2900', 'Hello');
This will be the result that I am expected.
Code SaleOrderDate Quantity InitialAmount Addition Cancellation
SO-001-OCT-18 2018-10-01 9 2500.00 900.00 500.00
I have written this query but it's not helping that much.
;WITH CTE AS (
SELECT
[TransactionNo], [Code], [SaleOrderDate], [Quantity], [TotalAmount],
CAST('Oct 1 2018 10:16AM' AS DATE) AS [DateFrom], CAST('Oct 4 2018 10:16AM' AS DATE) AS [DateTo]
FROM [SaleOrder]
GROUP BY
[TransactionNo], [Code], [SaleOrderDate], [TotalAmount], Quantity
)
SELECT
[D].[TransactionNo], [D].[Code], [D].[SaleOrderDate], [D].[Quantity], [D].TotalAmount,
--CAST('Oct 4 2018 4:06PM' AS DATE) AS [DateFrom],
--CAST('Oct 4 2018 4:06PM' AS DATE) AS [DateTo],
[D].[Balance], [D].[Balance]-ISNULL(NULLIF([D].TotalAmount, 0),0) [Opening]
FROM(
SELECT *,
SUM(TotalAmount) OVER (PARTITION BY [Code] ORDER BY [TransactionNo], [SaleOrderDate]) AS [Balance]
FROM CTE
)D
WHERE [SaleOrderDate] BETWEEN CAST('Oct 1 2018 10:16AM' AS DATE) AND CAST('Oct 4 2018 10:16AM' AS DATE)
ORDER BY [SaleOrderDate]
use the LAG() window function to get previous value and compare to determine it is an addition or cancellation.
; WITH cte as
(
SELECT *,
row_no = ROW_NUMBER() OVER (PARTITION BY Code ORDER BY TransactionNo DESC),
Addition = CASE WHEN TotalAmount > LAG(TotalAmount) OVER (PARTITION BY Code ORDER BY TransactionNo)
THEN TotalAmount - LAG(TotalAmount) OVER (PARTITION BY Code ORDER BY TransactionNo)
ELSE 0
END,
Cancellation = CASE WHEN TotalAmount < LAG(TotalAmount) OVER (PARTITION BY Code ORDER BY TransactionNo)
THEN LAG(TotalAmount) OVER (PARTITION BY Code ORDER BY TransactionNo) - TotalAmount
ELSE 0
END
FROM SaleOrder
)
SELECT Code,
SaleOrderDate,
Quantity = MAX (CASE WHEN row_no = 1 then Quantity END),
InitialAmount = MAX (CASE WHEN TransactionNo IS NULL THEN TotalAmount END),
Addition = SUM (Addition),
Cancellation = SUM (Cancellation)
FROM cte
GROUP BY Code, SaleOrderDate
Are you trying to do this? :
SELECT
Code
, MAX(SaleOrderDate) SaleOrderDate
, MAX(Quantity) Quantity
, MAX(InitialAmount) InitialAmount
, SUM(Addition) Addition
, ABS(SUM(Cancellation)) Cancellation
FROM (
SELECT
Code
, CASE WHEN rn = cnt THEN SaleOrderDate END SaleOrderDate
, CASE WHEN rn = cnt THEN Quantity END Quantity
, InitialAmount
, CASE WHEN Diff > 0 THEN Diff ELSE 0 END Addition
, CASE WHEN Diff < 0 THEN Diff ELSE 0 END Cancellation
FROM (
SELECT *
, CASE WHEN TransactionNo IS NULL THEN TotalAmount END InitialAmount
, LEAD(TotalAmount) OVER(PARTITION BY Code ORDER BY TransactionNo) nxtPrice
, LEAD(TotalAmount) OVER(PARTITION BY Code ORDER BY TransactionNo) - TotalAmount Diff
, COUNT(*) OVER(PARTITION BY Code) cnt
, ROW_NUMBER() OVER(PARTITION BY Code ORDER BY SaleOrderDate) rn
FROM SaleOrder
) D
) C
GROUP BY
Code
Related
Can someone please help me to find the average time between first and second purchase on a product level.
This is what I have written -
Select A.CustomerId,A.ProductId , A.OrderSequence, (Case WHEN OrderSequence = 1 THEN OrderDate END) AS First_Order_Date,
MAX(Case WHEN OrderSequence = 2 THEN OrderDate END) AS Second_Order_Date
From
(
Select t.CustomerId, t.ProductId, t.OrderDate,
Dense_RANK() OVER (PARTITION BY t.CustomerId, t.ProductId ORDER BY OrderDate Asc) as OrderSequence
From Transactions t (NOLOCK)
Where t.SiteKey = 01
Group by t.CustomerId, t.ProductId, t.OrderDate)
A
Where A.OrderSequence IN (1,2)
Group By A.Customer_Id, A.ProductId, A.OrderSequence, A.OrderDate
Sample Data:
It looks like row-numbering and LEAD should do the trick for you here.
Don't use NOLOCK unless you really know what you're doing
It's unclear if you want the results to be partitioned by CustomerId also. If not, you can remove it everywhere in the query
SELECT
A.CustomerId,
A.ProductId,
AVG(DATEDIFF(day, OrderDate, NextOrderDate))
FROM
(
SELECT
t.CustomerId,
t.ProductId,
t.OrderDate,
ROW_NUMBER() OVER (PARTITION BY t.CustomerId, t.ProductId ORDER BY OrderDate) AS rn,
LEAD(OrderDate) OVER (PARTITION BY t.CustomerId, t.ProductId ORDER BY OrderDate) AS NextOrderDate
FROM Transactions t
WHERE t.SiteKey = '01'
) t
WHERE t.rn = 1
GROUP BY
t.Customer_Id,
t.ProductId;
I am trying to create Start and End dates (Effective dates) based on a table that has a primary key ContactID, ModificationDate, and StateCode. The ModificationDate represents when the record was entered or updated, the StateCode represents if the record is enabled or disabled.
I am trying to figure out the Start (StateCode = 0) and End (StateCode=1) dates of each Contact record but can't quite get the query down.
I've tried using combinations of Windows Functions like Row_Number, Rank, Lead, etc but can't figure out how to increment the grouping number when StateCode = 1.
CREATE TABLE Contact(
ContactID INTEGER,
StateCode INTEGER,
ModifiedOn Datetime)
INSERT INTO Contact
SELECT 1, 0, '7/1/2019' UNION
SELECT 1, 0, '7/2/2019' UNION
SELECT 1, 1, '7/3/2019' UNION
SELECT 1, 0, '7/4/2019' UNION
SELECT 1, 0, '7/5/2019' UNION
SELECT 1, 1, '7/6/2019' UNION
SELECT 1, 0, '7/7/2019' UNION
SELECT 1, 0, '7/8/2019'
Sample SQL Fiddle:
http://sqlfiddle.com/#!18/e8aca/45
Based on the changes of StateCode in my example, I expect to see 3 records.
ContactID, StartDate, EndDate, ActiveFlag
1, 7/1/2019, 7/3/2019, 0
1, 7/4/2019, 7/6/2019, 0
1, 7/7/2019, NULL, 1
Potential Solution I am validating
WITH CTE AS (
SELECT
LAG(StateCode,1,1) OVER (PARTITION BY ContactID ORDER BY ModifiedOn) AS IsStart
, StateCode AS IsEnd
, ContactID
, StateCode
, ModifiedOn
FROM Contact
), CTE2 AS(
SELECT
ContactID
, IsStart
, IsEnd
, ModifiedOn
, DENSE_RANK() OVER (PARTITION BY ContactID ORDER BY CASE WHEN IsStart = 1 THEN ModifiedOn END) AS StartTest2
, DENSE_RANK() OVER (PARTITION BY ContactID ORDER BY CASE WHEN IsEnd = 1 THEN ModifiedOn END) AS EndTest2
FROM CTE
WHERE IsStart = 1 OR IsEnd = 1
)
SELECT
Start.ContactID
, Start.ModifiedOn AS StartDate
, EndDates.ModifiedOn AS EndDate
FROM CTE2 AS Start
LEFT JOIN CTE2 AS EndDates
ON Start.ContactID = EndDates.ContactID
AND Start.StartTest2 = EndDates.EndTest2
WHERE Start.StartTest2 <> 1
ORDER BY Start.ModifiedOn
You would use either LAG() or a self-join to grab a reference to the previous row.
If the current row's StateCode is 0, and the previous row's is either NULL (first row) or 1, then this row's date is a "StartDate". If the reverse is true, then it's an "EndDate". If both row's have the same StateCode, then just ignore the row.
I was able to get this to work by using a LAG function to set a flag for the StartDates and setting a flag for EndDates by the StateCode itself. Then I did some conditional logic on a DENSE_RANK function for both Start and End flags to get the actual dates associated to the date-periods. Here's my code (needs formatting still).
WITH CTE AS (
SELECT
CASE WHEN StateCode = 1 THEN 0 ELSE (LAG(StateCode,1,1) OVER (PARTITION BY ContactID ORDER BY ModifiedOn ASC)) END AS IsStartDate
, CASE WHEN (LAG(StateCode,1,NULL) OVER (PARTITION BY ContactID ORDER BY ModifiedOn ASC)) IS NULL OR (LAG(StateCode,1,NULL) OVER (PARTITION BY ContactID ORDER BY ModifiedOn ASC)) = 1 THEN 0 ELSE StateCode END AS IsEndDate
, ContactID
, StateCode
, ModifiedOn
FROM Contact
), CTE2 AS(
SELECT
ContactID
, IsStartDate
, IsEndDate
, ModifiedOn
, CASE WHEN IsStartDate = 0 THEN -1 ELSE DENSE_RANK() OVER (PARTITION BY ContactID ORDER BY CASE WHEN IsStartDate = 1 THEN ModifiedOn END ASC) END AS StartRank
, DENSE_RANK() OVER (PARTITION BY ContactID ORDER BY CASE WHEN IsEndDate = 1 THEN ModifiedOn END ASC) AS EndRank
FROM CTE
WHERE IsStart = 1 OR IsEnd = 1
)
SELECT
Start.ContactID
, Start.ModifiedOn AS StartDate
, EndDates.ModifiedOn AS EndDate
FROM CTE2 AS Start
LEFT JOIN CTE2 AS EndDates
ON Start.ContactID = EndDates.ContactID
AND Start.StartRank = EndDates.EndRank
WHERE Start.StartRank <> -1
ORDER BY Start.ModifiedOn
I am trying to calculate churn of customers based on activity they could have done, opposed to churn by date that is the normal thing. We have events that is connected to a specific host, in my example all events are hosted by Alice but it could be different hosts.
All the people that follow a specific event should be placed in a category (new, active, churned and resurrected).
New: First time a person follow an event from the specific host.
Active: Follow again (and last event from specific host was also followed).
Churned: Follower had a chance to follow but didn't.
Resurrected: Follower that has churned has started to follow a previously followed host.
declare #events table (event varchar(50), host varchar(50), date date)
declare #eventFollows table (event varchar(50), follower varchar(50))
insert into #events values ('e_1', 'Alice', GETDATE())
insert into #events values ('e_2', 'Alice', GETDATE())
insert into #events values ('e_3', 'Alice', GETDATE())
insert into #events values ('e_4', 'Alice', GETDATE())
insert into #events values ('e_5', 'Alice', GETDATE())
insert into #eventFollows values ('e_1', 'Bob') --new
insert into #eventFollows values ('e_2', 'Bob') --active
--Bob churned
insert into #eventFollows values ('e_4', 'Megan') --new
insert into #eventFollows values ('e_5', 'Bob') --resurrected
insert into #eventFollows values ('e_5', 'Megan') --active
select * from #events
select * from #eventFollows
The expected outcome should be something like this
select 'e_1', 1 as New, 0 as resurrected, 0 as active, 0 as churned --First time Bob follows Alice event
union all
select 'e_2', 0 as New, 0 as resurrected, 1 as active, 0 as churned --Bob follows the next event that Alice host (considered as Active)
union all
select 'e_3', 0 as New, 0 as resurrected, 0 as active, 1 as churned --Bob churns since he does not follow the next event
union all
select 'e_4', 1 as New, 0 as resurrected, 0 as active, 0 as churned --First time Megan follows Alice event
union all
select 'e_5', 0 as New, 1 as resurrected, 1 as active, 0 as churned --Second time (active) for Megan and Bob is resurrected
I started with a query of something like below, but the problem is that I don't get all the events that the followers did not follow (but could have followed).
select a.event, follower, date,
LAG (a.event,1) over (partition by a.host, ma.follower order by date) as lag,
LEAD (a.event,1) over (partition by a.host, ma.follower order by date) as lead,
LAG (a.event,1) over (partition by a.host order by date) as lagP,
LEAD (a.event,1) over (partition by a.host order by date) as leadP
from #events a left join #eventFollows ma on ma.event = a.event order by host, follower, date
Any ideas?
This may seem a bit of an indirect approach, but it's possible to detect islands by checking for gaps in the numbers:
;with nrsE as
(
select *, ROW_NUMBER() over (order by event) rnrE from #events
), nrs as
(
select f.*,host, rnrE, ROW_NUMBER() over (partition by f.follower, e.host order by f.event ) rnrF
from nrsE e
join #eventFollows f on f.event = e.event
), f as
(
select host, follower, min(rnrE) FirstE, max(rnrE) LastE, ROW_NUMBER() over (partition by follower, host order by rnrE - rnrF) SeqNr
from nrs
group by host, follower, rnrE - rnrF --difference between rnr-Event and rnr-Follower to detect gaps
), stat as --from the result above on there are several options. this example uses getting a 'status' and pivoting on it
(
select e.event, e.host, case when f.FirstE is null then 'No participants' when f.LastE = e.rnrE - 1 then 'Churned' when rnrE = f.FirstE then case when SeqNr = 1 then 'New' else 'Resurrected' end else 'Active' end Status
from nrsE e
left join f on e.rnrE between f.FirstE and f.LastE + 1 and e.host = f.host
)
select p.* from stat pivot(count(Status) for Status in ([New], [Resurrected], [Active], [Churned])) p
The last 2 steps could be simplified, but getting the 'Status' this way might be reusable for other scenarios
This matches your desired result
SELECT
X.event, X.host, X.date,
IsNew = SUM(CASE WHEN X.FirstFollowerEvent = X.event THEN 1 ELSE 0 END),
IsActive = SUM(CASE WHEN X.lagFollowerEvent = X.lagEvent THEN 1 ELSE 0 END),
IsChurned = SUM(CASE WHEN X.follower IS NULL THEN 1 ELSE 0 END),
IsResurrected = SUM(CASE WHEN X.lagFollowerEvent <> X.lagEvent AND X.FirstFollowerEvent IS NOT NULL THEN 1 ELSE 0 END)
FROM
(
select
a.event, a.host, ma.follower, a.date,
FIRST_VALUE(a.event) over (partition by a.host, ma.follower order by a.date, a.event) as FirstFollowerEvent,
LAG (a.event,1) over (partition by a.host, ma.follower order by a.date, a.event) as lagFollowerEvent,
LAG (a.event,1) over (partition by a.host order by a.date, a.event) as lagEvent
FROM
#events a
LEFT join
#eventFollows ma on a.event = ma.event
) X
GROUP BY
X.event, X.host, X.date
ORDER by
X.event, X.host, X.date
A table 'readings' has a list of dates
[Date] [Value]
2015-03-19 00:30:00 1.2
2015-03-19 00:40:00 1.2
2015-03-19 00:50:00 0.1
2015-03-19 01:00:00 0.1
2015-03-19 01:10:00 2
2015-03-19 01:20:00 0.5
2015-03-19 01:30:00 0.5
I need to get the most recent instance where the value is below a set point (in this case the value 1.0), but I only want the start (earliest datetime) where the value was below 1 for consecutive times.
So with the above data I want to return 2015-03-19 01:20:00, as the most recent block of times where value < 1, but I want the start of that block.
This SQL just returns the most recent date, rather than the first date whilst the value has been low (so returns 2015-03-19 01:30:00 )
select top 1 *
from readings where value <=1
order by [date] desc
I can't work out how to group the consecutive dates, to therefore only get the first ones
It is SQL Server, the real data isn't at exactly ten min intervals, and the readings table is about 70,000 rows- so fairly large!
Thanks, Charli
Demo
SELECT * FROM (
SELECT [Date]
,Value
,ROW_NUMBER() OVER (PARTITION BY cast([Date] AS DATE) ORDER BY [Date] ASC) AS RN FROM #table WHERE value <= 1
) t WHERE t.RN = 1
Select Max( [date] )
From [dbo].[readings]
Where ( [value] <= 1 )
You seem to want the minimum date for each set of consecutive records having a value that is less than 1. The query below returns exactly these dates:
SELECT MIN([Date])
FROM (
SELECT [Date], [Value],
ROW_NUMBER() OVER (ORDER BY [Date]) -
COUNT(CASE WHEN [Value] < 1 THEN 1 END) OVER (ORDER BY [Date]) AS grp
FROM mytable) AS t
WHERE Value < 1
GROUP BY grp
grp calculated field identifies consecutive records having Value<1.
Note: The above query will work for SQL Server 2012+.
Demo here
Edit:
To get the date value of the last group you can modify the above query to:
SELECT TOP 1 MIN([Date])
FROM (
SELECT [Date], [Value],
ROW_NUMBER() OVER (ORDER BY [Date]) -
COUNT(CASE WHEN [Value] < 1 THEN 1 END) OVER (ORDER BY [Date]) AS grp
FROM mytable) AS t
WHERE Value < 1
GROUP BY grp
ORDER BY grp DESC
Demo here
i have a table like this:
Number Price Type Date Time
------ ----- ---- ---------- ---------
23456 0,665 SV 2014/02/02 08:00:02
23457 1,3 EC 2014/02/02 07:50:45
23460 0,668 SV 2014/02/02 07:36:34
For each EC I need previous/next SV price. In this case, the query is simple.
Select Lag(price, 1, price) over (order by date desc, time desc),
Lead(price, 1, price) over (order by date desc, time desc)
from ITEMS
But, there are some special cases where two or more rows are EC type:
Number Price Type Date Time
------ ----- ---- ---------- ---------
23456 0,665 SV 2014/02/02 08:00:02
23457 1,3 EC 2014/02/02 07:50:45
23658 2,4 EC 2014/02/02 07:50:45
23660 2,4 EC 2014/02/02 07:50:48
23465 0,668 SV 2014/02/02 07:36:34
can I use Lead/Lag in this cases? If not, did I have to use a subquery?
Your question (and Anon's excellent answer) is part of the SQL of islands and gaps. In this answer, I will try to examine the "row_number() magic" in detail.
I've made a simple example based on events in a ballgame. For each event, we'd like to print the previous and next quarter related message:
create table TestTable (id int identity, event varchar(64));
insert TestTable values
('Start of Q1'),
('Free kick'),
('Goal'),
('End of Q1'),
('Start of Q2'),
('Penalty'),
('Miss'),
('Yellow card'),
('End of Q2');
Here's a query showing off the "row_number() magic" approach:
; with grouped as
(
select *
, row_number() over (order by id) as rn1
, row_number() over (
partition by case when event like '%of Q[1-4]' then 1 end
order by id) as rn2
from TestTable
)
, order_in_group as
(
select *
, rn1-rn2 as group_nr
, row_number() over (partition by rn1-rn2 order by id) as rank_asc
, row_number() over (partition by rn1-rn2 order by id desc)
as rank_desc
from grouped
)
select *
, lag(event, rank_asc) over (order by id) as last_event_of_prev_group
, lead(event, rank_desc) over (order by id) as first_event_of_next_group
from order_in_group
order by
id
The first CTE called "grouped" calculates two row_number()s. The first is 1 2 3 for each row in the table. The second row_number() places pause announcements in one list, and other events in a second list. The difference between the two, rn1 - rn2, is unique for each section of the game. It's helpful to check difference in the example output: it's in the group_nr column. You'll see that each value corresponds to one section of the game.
The second CTE called "order_in_group" determines the position of the current row within its island or gap. For an island with 3 rows, the positions are 1 2 3 for the ascending order, and 3 2 1 for the descending order.
Finally, we know enough to tell lag() and lead() how far to jump. We have to lag rank_asc rows to find the final row of the previous section. To find the first row of the next section, we have to lead rank_desc rows.
Hope this helps clarifying the "magic" of Gaps and Islands. Here is a working example at SQL Fiddle.
Yes, you can use LEAD/LAG. You just need to precalculate how far to jump with a little ROW_NUMBER() magic.
DECLARE #a TABLE ( number int, price money, type varchar(2),
date date, time time)
INSERT #a VALUES
(23456,0.665,'SV','2014/02/02','08:00:02'),
(23457,1.3 ,'EC','2014/02/02','07:50:45'),
(23658,2.4 ,'EC','2014/02/02','07:50:45'),
(23660,2.4 ,'EC','2014/02/02','07:50:48'),
(23465,0.668,'SV','2014/02/02','07:36:34');
; WITH a AS (
SELECT *,
ROW_NUMBER() OVER(ORDER BY [date] DESC, [time] DESC) x,
ROW_NUMBER() OVER(PARTITION BY
CASE [type] WHEN 'SV' THEN 1 ELSE 0 END
ORDER BY [date] DESC, [time] DESC) y
FROM #a)
, b AS (
SELECT *,
ROW_NUMBER() OVER(PARTITION BY x-y ORDER BY x ASC) z1,
ROW_NUMBER() OVER(PARTITION BY x-y ORDER BY x DESC) z2
FROM a)
SELECT *,
CASE [type] WHEN 'SV' THEN
LAG(price,z1,price) OVER(PARTITION BY [type] ORDER BY x)
ELSE LAG(price,z1,price) OVER(ORDER BY x)
END,
CASE [type] WHEN 'SV' THEN
LEAD(price,z2,price) OVER(PARTITION BY [type] ORDER BY x)
ELSE LEAD(price,z2,price) OVER(ORDER BY x)
END
FROM b
ORDER BY x
Here is yet another way of achieving the same result, but using conditional max/min functions windowed over an ordinal. The ordinal can be be set up based on whatever columns fits the purpose, but in this case I believe the OP intends them to be Date and Time.
DROP TABLE IF EXISTS #t;
CREATE TABLE #t (
Number INT,
Price MONEY,
Type CHAR(2),
Date DATE,
Time TIME(0)
);
INSERT INTO #t VALUES
(23456, 0.666, 'SV', '2014/02/02', '10:00:02'),
(23457, 1.4 , 'EC', '2014/02/02', '09:50:45'),
(23658, 2.5 , 'EC', '2014/02/02', '09:50:45'),
(23660, 2.5 , 'EC', '2014/02/02', '09:50:48'),
(23465, 0.669, 'SV', '2014/02/02', '09:36:34'),
(23456, 0.665, 'SV', '2014/02/02', '08:00:02'),
(23457, 1.3 , 'EC', '2014/02/02', '07:50:45'),
(23658, 2.4 , 'EC', '2014/02/02', '07:50:45'),
(23660, 2.4 , 'EC', '2014/02/02', '07:50:48'),
(23465, 0.668, 'SV', '2014/02/02', '07:36:34'), -- which one of these?
(23465, 0.670, 'SV', '2014/02/02', '07:36:34'); --
WITH time_ordered AS (
SELECT *, DENSE_RANK() OVER (ORDER BY Date, Time) AS ordinal FROM #t
)
SELECT
*,
CASE WHEN Type = 'EC'
THEN MAX(CASE WHEN ordinal = preceding_non_EC_ordinal THEN Price END)
OVER (PARTITION BY preceding_non_EC_ordinal ORDER BY ordinal ASC) END AS preceding_price,
CASE WHEN Type = 'EC'
THEN MIN(CASE WHEN ordinal = following_non_EC_ordinal THEN Price END)
OVER (PARTITION BY following_non_EC_ordinal ORDER BY ordinal DESC) END AS following_price
FROM (
SELECT
*,
MAX(CASE WHEN Type <> 'EC' THEN ordinal END)
OVER (ORDER BY ordinal ASC) AS preceding_non_EC_ordinal,
MIN(CASE WHEN Type <> 'EC' THEN ordinal END)
OVER (ORDER BY ordinal DESC) AS following_non_EC_ordinal
FROM time_ordered
) t
ORDER BY Date, Time
Note that the example given by the OP has been extended to show that interspersed sequences of EC yeild the intended result. The ambiguity introduced by the earliest two consecutive rows with type SV will in this case lead to the maximum value being picked. Setting up the ordinal to include the Price is a possible way to change this behavior.
An SQLFiddle can be found here: http://sqlfiddle.com/#!18/85117/1
Anon's solution is wonderful and Andomar's explanation of it is also great, but there is a difficulty in using this approach in large data sets, namely that you can get conflicts in what Andomar called 'group_nr' (rn1 - rn2) where events from much earlier have the same group number. This skews the rownumber calculation (which is by group_nr) and presents incorrect results when these conflicts arise.
Posting because I ran into this myself after working through this solution and finding errors.
my fix was to implement this version:
; with grouped as
(
select *
, row_number() over (order by id) as rn1
, row_number() over (
partition by case when event like '%of Q[1-4]' then 1 end
order by id) as rn2
from TestTable
)
, order_in_group as
(
select *
, CASE
WHEN event like '%of Q[1-4]' THEN (-1*rn1-rn2)
ELSE rn1 - rn2
END as group_nr
, row_number() over (partition by rn1-rn2 order by id) as rank_asc
, row_number() over (partition by rn1-rn2 order by id desc)
as rank_desc
from grouped
)
, final_grouping AS
(SELECT *
, row_number() over (partition by group_nr order by jobid) AS rank_asc
, row_number() over (partition by rn1-rn2 order by id desc) AS rank_desc
FROM order_in_group
)
select *
, lag(event, rank_asc) over (order by id) as last_event_of_prev_group
, lead(event, rank_desc) over (order by id) as first_event_of_next_group
from final_grouping
order by
id
;
Changing the pause events' group_nr values to negatives makes sure there are no conflicts with large data sets.