how to loop thru a table to find data set? - sql

I have to find the timediff in minutes for a order lifetime.
i.e time from order was received(Activity ID 1) to keyed(2) to printed(3) to delivered(4) for each order
for eg
I am completely lost at which approach should i take??
use case or if then statement ?? something like for each to loop thru each record?
what should be the most efficient way to do it?
i know once i get dates in correct variables i can use DATEDIFF.
declare #received as Datetime, #keyed as DateTime, #printed as Datetime, #Delivered as Datetime, #TurnTime1 as int
Select
IF (tblOrderActivity.ActivityID = 1) SET #received = tblOrderActivity.ActivityDate
---
----
from tblOrderActivity
where OrderID = 1
it should show me #TurnTime1 = 48 mins as orderID 1 took 48 mins from received(activity id 1) to keyed (activity id 2) #TurnTime2 = 29 mins as it took 29mins for order 1 from keyed(activity id 2) to printed (activity id 3) so on and so forth for each order

You can do this easily by pivoting the data.It can be done in two ways.
1.Use Conditional Aggregate to pivot the data. After pivoting you can find datediff between different stages. Try this.
SELECT orderid,Received,Keyed,Printed,Delivered,
Datediff(minute, Received, Keyed) TurnTime1,
Datediff(minute, Keyed, Printed) TurnTime2,
Datediff(minute, Printed, Delivered) TurnTime3
FROM (SELECT OrderID,
Max(CASE WHEN ActivityID = 1 THEN ActivityDate END) Received,
Max(CASE WHEN ActivityID = 2 THEN ActivityDate END) Keyed,
Max(CASE WHEN ActivityID = 3 THEN ActivityDate END) Printed,
Max(CASE WHEN ActivityID = 4 THEN ActivityDate END) Delivered
FROM Yourtable
GROUP BY OrderID)A
2.use Pivot to transpose the data
SELECT orderid,
[1] AS Received,
[2] AS Keyed,
[3] AS Printed,
[4] AS Delivered,
Datediff(minute, [1], [2]) TurnTime1,
Datediff(minute, [2], [3]) TurnTime2,
Datediff(minute, [3], [4]) TurnTime3
FROM Yourtable
PIVOT (Max(ActivityDate)
FOR ActivityID IN([1],[2],[3],[4]))piv

As you mentioned in your question, one possible way is to use CASE statement
DECLARE #i INT, #max INT
SELECT #i = MIN(OrderId) FROM tblOrderActivity
SELECT #max = MAX(OrderId) from tblOrderActivity
WHILE #i <= #max
BEGIN
SELECT OrderId
,ActivityID
,ActivityDate
,CASE
WHEN ActivityID = 1 THEN DATEDIFF(MINUTE, ActivityDate, (SELECT ActivityDate FROM C WHERE ActivityID = 2 AND OrderId = #i))
END AS tokeyed
,CASE
WHEN ActivityID = 2 THEN DATEDIFF(MINUTE, ActivityDate, (SELECT ActivityDate FROM C WHERE ActivityID = 3 AND OrderId = #i))
END AS toprinted
,CASE
WHEN ActivityID = 3 THEN DATEDIFF(MINUTE, ActivityDate, (SELECT ActivityDate FROM C WHERE ActivityID = 4 AND OrderId = #i))
END AS todelivered
FROM tblOrderActivity
SET #i = #i + 1
END

At first I make a list of all orders (CTE_Orders).
For each order I get four dates, one for each ActivityID using OUTER APPLY. I assume that some activities could be missing (not completed yet), so OUTER APPLY would return NULL there. When I calculate durations I assume that if activity is not in the database, it hasn't happened yet and I calculate duration till the current time. You can handle this case differently if you have other requirements.
I assume that each order can have at most one row for each Activity ID. If you can have two or more rows with the same Order ID and Activity ID, then you need to decide which one to pick by adding ORDER BY to the SELECT inside the OUTER APPLY.
DECLARE #TOrders TABLE (OrderID int, ActivityID int, ActivityDate datetime);
INSERT INTO #TOrders (OrderID, ActivityID, ActivityDate) VALUES (1, 1, '2007-04-16T08:34:00');
INSERT INTO #TOrders (OrderID, ActivityID, ActivityDate) VALUES (1, 1, '2007-04-16T08:34:00');
INSERT INTO #TOrders (OrderID, ActivityID, ActivityDate) VALUES (1, 2, '2007-04-16T09:22:00');
INSERT INTO #TOrders (OrderID, ActivityID, ActivityDate) VALUES (1, 3, '2007-04-16T09:51:00');
INSERT INTO #TOrders (OrderID, ActivityID, ActivityDate) VALUES (1, 4, '2007-04-16T16:14:00');
INSERT INTO #TOrders (OrderID, ActivityID, ActivityDate) VALUES (2, 1, '2007-04-16T08:34:00');
INSERT INTO #TOrders (OrderID, ActivityID, ActivityDate) VALUES (3, 1, '2007-04-16T08:34:00');
INSERT INTO #TOrders (OrderID, ActivityID, ActivityDate) VALUES (3, 2, '2007-04-16T09:22:00');
INSERT INTO #TOrders (OrderID, ActivityID, ActivityDate) VALUES (3, 3, '2007-04-16T09:51:00');
INSERT INTO #TOrders (OrderID, ActivityID, ActivityDate) VALUES (3, 4, '2007-04-16T16:14:00');
INSERT INTO #TOrders (OrderID, ActivityID, ActivityDate) VALUES (4, 1, '2007-04-16T08:34:00');
INSERT INTO #TOrders (OrderID, ActivityID, ActivityDate) VALUES (4, 2, '2007-04-16T09:22:00');
INSERT INTO #TOrders (OrderID, ActivityID, ActivityDate) VALUES (4, 3, '2007-04-16T09:51:00');
WITH
CTE_Orders
AS
(
SELECT DISTINCT Orders.OrderID
FROM #TOrders AS Orders
)
SELECT
CTE_Orders.OrderID
,Date1_Received
,Date2_Keyed
,Date3_Printed
,Date4_Delivered
,DATEDIFF(minute, ISNULL(Date1_Received, GETDATE()), ISNULL(Date2_Keyed, GETDATE())) AS Time12
,DATEDIFF(minute, ISNULL(Date2_Keyed, GETDATE()), ISNULL(Date3_Printed, GETDATE())) AS Time23
,DATEDIFF(minute, ISNULL(Date3_Printed, GETDATE()), ISNULL(Date4_Delivered, GETDATE())) AS Time34
FROM
CTE_Orders
OUTER APPLY
(
SELECT TOP(1) Orders.ActivityDate AS Date1_Received
FROM #TOrders AS Orders
WHERE
Orders.OrderID = CTE_Orders.OrderID
AND Orders.ActivityID = 1
) AS OA1_Received
OUTER APPLY
(
SELECT TOP(1) Orders.ActivityDate AS Date2_Keyed
FROM #TOrders AS Orders
WHERE
Orders.OrderID = CTE_Orders.OrderID
AND Orders.ActivityID = 2
) AS OA2_Keyed
OUTER APPLY
(
SELECT TOP(1) Orders.ActivityDate AS Date3_Printed
FROM #TOrders AS Orders
WHERE
Orders.OrderID = CTE_Orders.OrderID
AND Orders.ActivityID = 3
) AS OA3_Printed
OUTER APPLY
(
SELECT TOP(1) Orders.ActivityDate AS Date4_Delivered
FROM #TOrders AS Orders
WHERE
Orders.OrderID = CTE_Orders.OrderID
AND Orders.ActivityID = 4
) AS OA4_Delivered
ORDER BY OrderID;
This the result set:
OrderID Date1_Received Date2_Keyed Date3_Printed Date4_Delivered Time12 Time23 Time34
1 2007-04-16 08:34:00.000 2007-04-16 09:22:00.000 2007-04-16 09:51:00.000 2007-04-16 16:14:00.000 48 29 383
2 2007-04-16 08:34:00.000 NULL NULL NULL 4082575 0 0
3 2007-04-16 08:34:00.000 2007-04-16 09:22:00.000 2007-04-16 09:51:00.000 2007-04-16 16:14:00.000 48 29 383
4 2007-04-16 08:34:00.000 2007-04-16 09:22:00.000 2007-04-16 09:51:00.000 NULL 48 29 4082498
You can easily calculate other durations, like the total time for the order (time 4 - time1).
Once you have several different queries that produce the same correct result that you need you should measure their performance with your real data on your system to decide which is more efficient.

this one should fill your needs, but I would suggest to use this query while you insert the values into the table and directly add this value in a new column
SELECT OrderID,
ActivityID,
ActivityDate,
Datediff(MINUTE, ActivityDate, (SELECT ActivityDate
FROM [TestDB].[dbo].[tblOrderActivity] AS b
WHERE b.OrderID = a.OrderID
AND a.ActivityID + 1 = b.ActivityID))
FROM [TestDB].[dbo].[tblOrderActivity] AS a

Related

SQL Server : loop through one table and get the sum till threshold value is reached and update the sum value in another table

I have two tables:
Sales table:
Returns table:
I have to loop through the Sales table and get sum of all the Qty based on Material+Batch+customer combination until it exceeds the value of Return_qty, and update the Summed value in the Returns table.
This is the desired output:
As you can see, from the Sales table until Sales_Invoice 4 only it considered as it exceeded the value of return_Qty.
What I have tried till now?
I have tried to use while loop to loop through and calculate running total. But its not working out. Maybe approach is wrong.
Any inputs will be highly appreciated.
Try this:
DECLARE #Sales TABLE
(
[Sales_Invoice] SMALLINT
,[Invoice_Date] DATE
,[Material] VARCHAR(3)
,[Batch] VARCHAR(2)
,[Customer] VARCHAR(4)
,[Qty] SMALLINT
);
DECLARE #Returns TABLE
(
[Return_Invoice] SMALLINT
,[Invoice_Date] DATE
,[Material] VARCHAR(3)
,[Batch] VARCHAR(2)
,[Customer] VARCHAR(4)
,[Return_Qty] SMALLINT
,[Sales_Qty] SMALLINT
);
INSERT INTO #Sales ([Sales_Invoice], [Invoice_Date], [Material], [Batch], [Customer], [Qty])
VALUES (1, '2019-06-07', 'AB1', 'B1', 'B001', 50)
,(2, '2019-06-07', 'AB1', 'B1', 'B001', 20)
,(3, '2019-06-06', 'AB1', 'B1', 'B001', 25)
,(4, '2019-06-06', 'AB1', 'B1', 'B001', 11)
,(5, '2019-06-06', 'AB1', 'B1', 'B001', 20)
,(6, '2019-06-01', 'BA2', 'C1', 'Y001', 100);
INSERT INTO #Returns ([Return_Invoice], [Invoice_Date], [Material], [Batch], [Customer], [Return_Qty])
VALUES (212, '2019-06-08', 'AB1', 'B1', 'B001', 100);
WITH DataSource AS
(
SELECT [Material], [Batch], [Customer]
,SUM([Qty]) OVER (PARTITION BY [Material], [Batch], [Customer] ORDER BY [Sales_Invoice] ASC) AS [Return_Qty]
FROM #Sales
)
UPDATE #Returns
SET [Sales_Qty] = DS.[Return_Qty]
FROM #Returns R
INNER JOIN
(
SELECT [Material], [Batch], [Customer]
,MIN([Return_Qty]) AS [Return_Qty]
FROM DataSource
WHERE [Return_Qty] >= 100
GROUP BY [Material], [Batch], [Customer]
) DS
ON R.[Material] = DS.[Material]
AND R.[Batch] = DS.[Batch]
AND R.[Customer] = DS.[Customer];
SELECT *
FROM #Returns;
If you want to be more dynamical, you can use the following:
WITH DataSource AS
(
SELECT [Material], [Batch], [Customer]
,SUM([Qty]) OVER (PARTITION BY [Material], [Batch], [Customer] ORDER BY [Sales_Invoice] ASC) AS [Return_Qty]
FROM #Sales
)
UPDATE #Returns
SET [Sales_Qty] = DataSource.[Return_Qty]
FROM #Returns R
CROSS APPLY
(
SELECT DS.[Material], DS.[Batch], DS.[Customer]
,MIN(DS.[Return_Qty]) AS [Return_Qty]
FROM DataSource DS
WHERE DS.[Return_Qty] >= R.[Return_Qty]
AND R.[Material] = DS.[Material]
AND R.[Batch] = DS.[Batch]
AND R.[Customer] = DS.[Customer]
GROUP BY [Material], [Batch], [Customer]
) DataSource;
you should really show your while statement in your post - can you do that please?
I think a common table expression using recursion is a good solution for you. something along the lines of ...
;
WITH
cte1 AS
(
SELECT
RANK() OVER
(ORDER BY S.Material, S.Batch, S.Customer) GroupId,
RANK() OVER
(
PARTITION BY S.Material, S.Batch, S.Customer,
ORDER BY S.INVOICE_Date) Seqn,
S.Material, S.Batch, S.Customer, S.qty, R.Return_qty
FROM
Sales S
JOIN
Returns R
ON S.Material = R.Material AND S.Batch = R.Batch AND S.Customer = R.Customer
),
cte2 AS
(
SELECT
GroupId, Seqn,Material, Batch, Customer, qty AS TriggeringQty, Return_qty
FROM cte1
WHERE seqn =1
UNION ALL
SELECT
cte1.GroupId, cte1.Seqn, cte1.Material, cte1.Batch, cte1.Customer,
cte1.qty + cte2.qty, cte1.Return_qty
FROM cte2
JOIN cte1
ON cte1.GroupId = cte2.GroupID AND cte1.seqn = cte2.seqn+1
WHERE
cte2.qty < 100 AND cte1.seqn + cte2.seqn+1 >= Return_qty )
UPDATE R
SET R.Sales_qty = cte2.triggeringqty
FROM Returns R
JOIN cte2 S ON
S.Material = R.Material AND S.Batch = R.Batch AND S.Customer = R.Customer
WHERE cte2.triggeringqty >= 100;
Sorry I haven't tried the above so probably won't run, but hopefully you see what's happening.

SQL number greater than select results

I'm struggling to think of a way to do this with T-SQL.
I have a table which is populated every 5 seconds with the prices of three currencies (GBP, EUR & USD)
I've created a trigger (after insert), which selects the last 5 records entered for a given currency:
SELECT TOP 5 Price from dbo.prices where coin='GBP' ORDER BY Date Desc
I want to determine if the last inserted currency price is greater than the selected 5 above, how do i do this?
Thanks
As I guess: there cant be two entries for the same currency at one time. Only one insert per currency per some time (5sec). So this should fit yours requirements:
declare #prices table ([Date] int IDENTITY(1,1) primary key, Price float, coin varchar(3));
insert into #prices (coin, Price) values
('GBP', 3.20),('EUR', 3.14),('USD', 3.14),
('GBP', 3.17),('EUR', 3.16),('USD', 3.11),
('GBP', 3.14),('EUR', 3.13),('USD', 3.16),
('GBP', 3.15),('EUR', 3.12),('USD', 3.17),
('GBP', 3.16),('EUR', 3.17),('USD', 3.11),
('GBP', 3.15),('EUR', 3.14),('USD', 3.12),
('GBP', 3.19),('EUR', 3.14),('USD', 3.16)
select
case
when NEW.Price > PREV.Price Then 'yes'
else 'No'
end as CURR_JUMP_UP
from
(
select top 1 COALESCE(Price,0) Price, [Date]
from #prices where coin='GBP' order by [Date] desc
) NEW
cross apply
(
select MAX(Price) Price from
(
select top 5 Price
from #prices
where coin='GBP' and [Date]<NEW.[Date]
order by [Date] desc
) t
) PREV
Try this query:
DECLARE #AmountLastFiveEntry DECIMAL= (SELECT TOP 5 SUM(Price) FROM dbo.prices WHERE
ID NOT IN (SELECT TOP 1 ID
FROM dbo.prices where coin='GBP' ORDER BY Date Desc) where coin='GBP' ORDER BY Date Desc)
IF #AmountLastFiveEntry<(SELECT TOP 1 Price
FROM dbo.prices where coin='GBP' ORDER BY Date Desc)
BEGIN
SELECT #AmountLastFiveEntry --To do task
END
Trigger part is confusing
This will report if the latest price is higher (or equal) to the largest of the prior 5.
declare #currency table (iden int IDENTITY(1,1) primary key, exchange smallint, coin tinyint);
insert into #currency (coin, exchange) values
(1, 1)
, (1, 2)
, (1, 3)
, (1, 4)
, (1, 5)
, (1, 6)
, (2, 1)
, (2, 2)
, (2, 3)
, (2, 4)
, (2, 5)
, (2, 3);
select cccc.coin, cccc.exchange
, case when cccc.rn = cccc.rne then 'yes'
else 'no'
end as 'high'
from ( select ccc.iden, ccc.coin, ccc.exchange, ccc.rn
, ROW_NUMBER() over (partition by ccc.coin order by ccc.exchange desc, ccc.rn) rne
from ( select cc.iden, cc.coin, cc.exchange, cc.rn
from ( select c.iden, c.coin, c.exchange
, ROW_NUMBER() over (partition by coin order by iden desc) as rn
from #currency c
) cc
where cc.rn <= 6
) ccc
) cccc
where cccc.rn = 1
order by cccc.coin

SQL Update Or Insert By Comparing Dates

I am trying to do the UPDATE or INSERT, but I am not sure if this is possible without using loop. Here is the example:
Says, I have this SQL below in which I joined two tables: tblCompany and tblOrders.
SELECT CompanyID, CompanyName, c.LastSaleDate, o.SalesOrderID, o.SalesPrice
, DATEADD(m, -6, GETDATE()) AS DateLast6MonthFromToday
FROM dbo.tblCompany c
CROSS APPLY (
SELECT TOP 1 SalesOrderID, SalesPrice
FROM dbo.tblOrders o
WHERE c.CompanyID = o.CompanyID
ORDER BY SalesOrderID DESC
) AS a
WHERE Type = 'End-User'
Sample Result:
CompanyID, SalesOrderID, SalesPrice, LastSalesDate, DateLast6MonthFromToday
101 10001 50 2/01/2016 10/20/2016
102 10002 80 12/01/2016 10/20/2016
103 10003 80 5/01/2016 10/20/2016
What I am trying to do is comparing the LastSalesDate and the DateLast6MonthFromToday. Condition is below:
If the LastSalesDate is lesser (earlier), then do the INSERT INTO tblOrders (CompanyID, Column1, Column2...) VALUES (CompanyIDFromQuery, Column1Value, Column2Value)
Else, do UPDATE tblOrders SET SalesPrice = 1111 WHERE SalesOrderID = a.SalesOrderID
As the above sample result, the query will only update SalesOrderID 10001 and 10003. And For Company 102, NO insert since the LastSaleDate is greater, then just do the UPDATE for the SalesOrderID.
I know it is probably can be done if I create a Cursor to loop through every record and do the comparison then Update or Insert, but I wonder if there is another way perform this without the loop since I have around 20K records.
Sorry for the confusion,
I don't know your tables structure and your data types. Also I know nothing
about duplicates and join ralationships between this 2 tables.
But I want only show how it works on next example:
use [your test db];
go
create table dbo.tblCompany
(
companyid int,
companyname varchar(max),
lastsaledate datetime,
[type] varchar(max)
);
create table dbo.tblOrders
(
CompanyID int,
SalesOrderID int,
SalesPrice float
);
insert into dbo.tblCompany
values
(1, 'Avito', '2016-01-01', 'End-User'),
(2, 'BMW', '2016-05-01', 'End-User'),
(3, 'PornHub', '2017-01-01', 'End-User')
insert into dbo.tblOrders
values
(1, 1, 500),
(1, 2, 700),
(1, 3, 900),
(2, 1, 500),
(2, 2, 700),
(2, 3, 900),
(3, 1, 500),
(3, 2, 700),
(3, 3, 900)
declare #column_1_value int = 5;
declare #column_2_value int = 777;
with cte as (
select
CompanyID,
SalesOrderID,
SalesPrice
from (
select
CompanyID,
SalesOrderID,
SalesPrice,
row_number() over(partition by CompanyID order by SalesOrderId desc) as rn
from
dbo.tblOrders
) t
where rn = 1
)
merge cte as target
using (select * from dbo.tblCompany where [type] = 'End-User') as source
on target.companyid = source.companyid
and source.lastsaledate >= dateadd(month, -6, getdate())
when matched
then update set target.salesprice = 1111
when not matched
then insert (
CompanyID,
SalesOrderID,
SalesPrice
)
values (
source.CompanyId,
#column_1_value,
#column_2_value
);
select * from dbo.tblOrders
If you will give me an information, then I can prepare target and source tables properly.

Need a SQL Server query that gets sets of 3 rows based on a date given

I need to get 3 rows per set based on a date given (must be this date) but I want the rows to be based on this date as:
1 ( row where the date from the date column is the next date after given date )
0 ( row where the date is the closest date prior to the date given )
-1 ( prior to the date at 0 )
And add a column with the relative number.
** The dates for the same name and item will never repeat.
For example, a set of rows:
Row ID, Name, Item, Number, Date
1 Andy, Item1, 12030, 2014-06-30
2 Andy, Item1, 62030, 2014-03-31
3 Andy, Item1, 30300, 2013-12-31
4 Andy, Item1, 40030, 2013-10-31
5 Andy, Item1, 50030, 2013-08-30
6 John, Item2, 50240, 2014-04-30
7 John, Item2, 41400, 2014-03-31
8 John, Item2, 40509, 2014-01-31
9 Andy, Item2, 24004, 2014-03-31
10 Andy, Item2, 20144, 2013-12-31
11 Andy, Item2, 20450, 2013-09-30
12 Andy, Item2, 25515, 2013-06-30
If I have 2014-03-15 as the date and search for 'Andy', I expect:
Row ID, Item, Date, Relative Date
2, Item1, 2014-03-31, 1
3, Item1, 2013-12-31, 0
4, Item1, 2013-10-31, -1
9, Item2, 2014-03-31, 1
10, Item2, 2013-12-31, 0
11, Item2, 2013-09-30, -1
This is what I'm using which I have no issues switching if necessary:
DATEDIFF( quarter, 2014-03-31, date )
date BETWEEN DATEADD( quarter, -1, '20140315' ) AND
DATEADD( day, 1 ( DATEADD ( quarter, 2, '20140315' ) )
which returns:
Row ID, Item, Date, Relative Date
2, Item1, 2014-06-30, 1
3, Item1, 2014-03-31, 0
4, Item1, 2013-12-31, -1
9, Item2, 2014-03-31, 0
10, Item2, 2013-12-31, -1
Is there a better way of doing this without date math? I don't think I can accomplish exactly what I want with date math because it's hard to capture the exact 3 rows I need.
Perhaps something with row_number()?
Something like...
CREATE TABLE #TEMP
(
RowID int
, Name varchar(25)
, Item varchar(25)
, Number int
, [Date] datetime
)
INSERT INTO #TEMP
VALUES (1, 'Andy', 'Item1', 12030, '2014-06-30T00:00:00')
, (2, 'Andy', 'Item1', 62030, '2014-03-31T00:00:00')
, (3, 'Andy', 'Item1', 30300, '2013-12-31T00:00:00')
, (4, 'Andy', 'Item1', 40030, '2013-10-31T00:00:00')
, (5, 'Andy', 'Item1', 50030, '2013-08-30T00:00:00')
DECLARE #Date datetime
SET #Date = '2014-03-15T00:00:00'
CREATE TABLE #NameItem
(
ID int identity(1,1)
, Name varchar(25)
, Item varchar(25)
)
CREATE TABLE #Results
(
NIID int
, RowID int
, [Date] datetime
, RelativeDate int
)
INSERT INTO #NameItem
(Name, Item)
SELECT DISTINCT Name, Item
FROM #TEMP a
INSERT INTO #Results
(NIID, RowID, [Date], RelativeDate)
SELECT a.ID, b.RowID, b.[Date], b.RelativeDate
FROM #NameItem a
CROSS APPLY (
SELECT TOP 1 z.RowID, z.[Date], 1 AS RelativeDate
FROM #TEMP z
WHERE z.Name = a.Name
AND z.Item = a.Item
AND [Date] > #Date
ORDER BY [Date]
) b
INSERT INTO #Results
(NIID, RowID, [Date], RelativeDate)
SELECT a.ID, b.RowID, b.[Date], b.RelativeDate
FROM #NameItem a
CROSS APPLY (
SELECT TOP 1 z.RowID, z.[Date], 0 AS RelativeDate
FROM #TEMP z
WHERE z.Name = a.Name
AND z.Item = a.Item
AND [Date] < #Date
ORDER BY [Date] DESC
) b
; with cte_0 as
(
SELECT a.NIID, a.[Date]
FROM #Results a
WHERE a.RelativeDate = 0
)
INSERT INTO #Results
(NIID, RowID, [Date], RelativeDate)
SELECT b.ID, c.RowID, c.[Date], c.RelativeDate
FROM cte_0 a
INNER JOIN #NameItem b
ON a.NIID = b.ID
CROSS APPLY (
SELECT TOP 1 z.RowID, z.[Date], -1 AS RelativeDate
FROM #TEMP z
WHERE z.Name = b.Name
AND z.Item = b.Item
AND z.[Date] < a.[Date]
ORDER BY [Date] DESC
) c
SELECT a.Name, a.Item, b.RowID, b.[Date], b.RelativeDate
FROM #NameItem a
INNER JOIN #Results b
ON a.ID = b.NIID
ORDER BY a.ID, b.RelativeDate DESC
DROP TABLE #NameItem
DROP TABLE #Results
DROP TABLE #TEMP

Counting ordered data

I have the following problem to solve and I can't seem to be able to come up with an algorithm yet, nevermind an actual solution.
I have a table of similar structure/data as the following, where IDs are not always in sequence for the same Ticker/QuouteType:
ID Ticker PriceDateTime QuoteType OpenPrice HighPrice LowPrice ClosePrice
------- ------ ---------------- --------- --------- --------- -------- ----------
2036430 ^COMP 2012-02-10 20:50 95/Minute 2901.57 2905.04 2895.37 2901.71
2036429 ^COMP 2012-02-10 19:15 95/Minute 2909.63 2910.98 2899.95 2901.67
2036428 ^COMP 2012-02-10 17:40 95/Minute 2905.9 2910.27 2904.29 2909.64
2036427 ^COMP 2012-02-10 16:05 95/Minute 2902 2908.29 2895.1 2905.89
2036426 ^COMP 2012-02-09 21:00 95/Minute 2926.12 2928.01 2925.53 2927.21
The information I need to extract from this data is the following:
How many consecutive rows are there? Counting downwards from the most recent (as recorded in PriceDateTime), looking at ClosePrice?
IE: For the current example the answer should be 2. ClosePrice (row 1) = 2901.71 which is greater than ClosePrice (row 2) = 2901.67 but lower than ClosePrice (row 3) = 2909.64. As such, looking back from the most recent price, we have 2 rows that "go in the same direction".
Of course I have to do this across a lot of other names, so speed is quite important.
PS: Thank you all for your help, I've drawn inspiration from all your answers when building the final procedure. You're all very kind!
Try this: (I have simplified the test data I'm using as it only requires 2 columns to demonstrate the logic).
CREATE TABLE #Test (PriceDateTime DATETIME, ClosePrice DECIMAL(6, 2))
INSERT #Test VALUES
('20120210 20:50:00.000', 2901.71),
('20120210 19:15:00.000', 2901.67),
('20120210 17:40:00.000', 2900.64),
('20120210 16:05:00.000', 2905.89),
('20120209 21:00:00.000', 2927.21)
-- FIRST CTE, JUST DEFINES A VIEW GIVING EACH ENTRY A ROW NUMBER
;WITH CTE AS
( SELECT *,
ROW_NUMBER() OVER(ORDER BY PriceDateTime DESC) [RowNumber]
FROM #Test
),
-- SECOND CTE, ASSIGNES EACH ENTRY +1 OR -1 DEPENDING ON HOW THE VALUE HAS CHANGED COMPARED TO THE PREVIOUS RECORD
CTE2 AS
( SELECT a.*, SIGN(a.ClosePrice - b.ClosePrice) [Movement]
FROM CTE a
LEFT JOIN CTE b
ON a.RowNumber = b.RowNumber - 1
),
-- THIRD CTE, WILL LOOP THROUGH THE DATA AS MANY TIMES AS POSSIBLE WHILE THE PREVIOUS ENTRY HAS THE SAME "MOVEMENT"
CTE3 AS
( SELECT *, 1 [Recursion]
FROM CTE2
UNION ALL
SELECT a.PriceDateTime, a.ClosePrice, a.RowNumber, a.Movement, b.Recursion + 1
FROM CTE2 a
INNER JOIN CTE3 b
ON a.RowNumber = b.RowNumber - 1
AND a.Movement = b.Movement
)
SELECT MAX(Recursion) + 1 -- ADD 1 TO THE RECORD BECAUSE THERE WILL ALWAYS BE AT LEAST TWO ROWS
FROM CTE3
WHERE RowNumber = 1 -- LATEST ENTRY
DROP TABLE #Test
I've tried to comment the answer to explain as I go. If anything is not clear from the comments let me know and I will try and explain further
Solution below should be efficient enough, but it will fail if there are gaps in ID sequence.
Please update your topic, if it is the point.
DECLARE #t TABLE (
ID INT,
ClosePrice DECIMAL(10, 5)
)
INSERT #t (ID, ClosePrice)
VALUES (2036430, 2901.71), (2036429, 2901.67), (2036428, 2909.64), (2036427, 2905.89), (2036426, 2927.21)
;WITH CTE AS (
SELECT TOP 1 ID, ClosePrice, 1 AS lvl
FROM #t
ORDER BY ID DESC
UNION ALL
SELECT s.ID, s.ClosePrice, CTE.lvl + 1
FROM #t AS s
INNER JOIN CTE
ON s.ID = CTE.ID - 1 AND s.ClosePrice < CTE.ClosePrice
)
SELECT MAX(lvl) AS answer
FROM CTE
I'd join your data on itself (with +1 on your primary key / ordering key) then use a simple CASE to track the change (assuming i've understood your question properly).
For example:
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[tbl_NumericSequence](
[ID] [int] NULL,
[Value] [int] NULL
) ON [PRIMARY]
GO
INSERT [dbo].[tbl_NumericSequence] ([ID], [Value]) VALUES (1, 1)
GO
INSERT [dbo].[tbl_NumericSequence] ([ID], [Value]) VALUES (2, 2)
GO
INSERT [dbo].[tbl_NumericSequence] ([ID], [Value]) VALUES (3, 3)
GO
INSERT [dbo].[tbl_NumericSequence] ([ID], [Value]) VALUES (4, 2)
GO
INSERT [dbo].[tbl_NumericSequence] ([ID], [Value]) VALUES (5, 1)
GO
INSERT [dbo].[tbl_NumericSequence] ([ID], [Value]) VALUES (6, 3)
GO
INSERT [dbo].[tbl_NumericSequence] ([ID], [Value]) VALUES (7, 3)
GO
INSERT [dbo].[tbl_NumericSequence] ([ID], [Value]) VALUES (8, 8)
GO
INSERT [dbo].[tbl_NumericSequence] ([ID], [Value]) VALUES (9, 1)
GO
WITH RawData ( [ID], [Value] )
AS ( SELECT [ID] ,
[Value]
FROM [Test].[dbo].[tbl_NumericSequence]
)
SELECT RawData.ID ,
RawData.Value ,
CASE WHEN RawDataLag.Value = RawData.Value THEN 'No Change'
WHEN RawDataLag.Value > RawData.Value THEN 'Down'
WHEN RawDataLag.Value < RawData.Value THEN 'Up'
END AS Change
FROM RawData
LEFT OUTER JOIN RawData RawDataLag ON RawData.ID = RawDataLag.iD + 1
ORDER BY RawData.ID ASC
I would approach it with recursive common table expressions:
CREATE TABLE #MyTable (ID INT, ClosePrice MONEY)
INSERT INTO #MyTable ( ID, ClosePrice )
VALUES (2036430,2901.71),
(2036429,2901.67),
(2036428,2909.64),
(2036427,2905.89),
(2036426,2927.21)
WITH CTE AS (
SELECT TOP 1 id, closeprice, 1 Consecutive
FROM #MyTable
ORDER BY id DESC
UNION ALL
SELECT A.id, A.closeprice, CASE WHEN A.ClosePrice < B.ClosePrice THEN Consecutive+1 ELSE 1 END
FROM #MyTable A INNER JOIN cte B ON A.ID=B.id -1
)
SELECT * FROM cte
--OR to just get the max consecutive
--select max(Consecutive) from cte
DROP TABLE #MyTable