SQL Server 2008 calculating data difference when we have only one date column - sql

I have a date column Order_date and I am looking for ways to calculate the date difference between customer last order date and his recent previous ( previous form last) order_date ....
Example
Customer : 1, 2 , 1 , 1
Order_date: 01/02/2007, 02/01/2015, 06/02/2014, 04/02/2015
As you can see customer # 1 has three orders.
I want to know the date difference between his recent order date (04/02/2015) and his recent previous (06/02/2014).

For SQL Server 2012 & 2014 you could use LAG with a DATEDIFF to see the number of days between them.
For older versions, a CTE would probably be your best bet:
;WITH CTE AS
(
SELECT CustomerID,
Order_Date,
rn = ROW_NUMBER() OVER (PARTITION BY CustomerID ORDER BY Order_Date DESC)
)
SELECT c1.CustomerID,
DATEDIFF(d, c1.Order_Date, c2.Order_Date)
FROM CTE c1
INNER JOIN CTE c2 ON c2.rn = c1.rn + 1

In SQL Server 2012+, you can use lag() to get the difference between any two dates:
select t.*,
datediff(day, lag(order_date) over (partition by customer order by order_date),
order_date) as days_dff
from table t;
If you have an older version, you can do something similar with correlated subqueries or outer apply.
EDIT:
If you just want the difference between the two most recent dates, use conditional aggregation instead:
select customer,
datediff(day, max(case when seqnum = 2 then order_date end),
max(case when seqnum = 1 then order_date end)
) as MostRecentDiff
from (select t.*,
row_number() over (partition by customer order by order_date desc) as seqnum
from table t
) t
group by customer;

If you're using SQL Server 2008 or later, you can try CROSS APPLY.
SELECT [customers].[customer_id], DATEDIFF(DAY, MIN([recent_orders].[order_date]), MAX([recent_orders].[order_date])) AS [elapsed]
FROM [customers]
CROSS APPLY (
SELECT TOP 2 [order_date]
FROM [orders]
WHERE ([orders].[customer_id] = [customers].[customer_id])
) [recent_orders]
GROUP BY [customers].[customer_id]

SELECT DATEDIFF(DAY, Y.PrevLastOrderDate, Y.LastOrderDate) AS PreviousDays
FROM
(
SELECT X.LastOrderDate
, (SELECT MAX(OrderDate) FROM dbo.Orders SO WHERE SO.CustomerID=1 AND SO.OrderDate < X.LastOrderDate) AS PrevLastOrderDate
FROM
(
select MAX(OrderDate) AS LastOrderDate
FROM dbo.Orders O
WHERE O.CustomerID=1
)X
)Y

drop table #Invoices
create table #Invoices ( OrderId int , OrderDate datetime )
insert into #Invoices (OrderId , OrderDate )
select 101, '01/01/2001' UNION ALL Select 202, '02/02/2002' UNION ALL Select 303, '03/03/2003'
UNION ALL Select 808, '08/08/2008' UNION ALL Select 909, '09/09/2009'
;
WITH
MyCTE /* http://technet.microsoft.com/en-us/library/ms175972.aspx */
( OrderId,OrderDate,ROWID) AS
(
SELECT
OrderId,OrderDate
, ROW_NUMBER() OVER ( ORDER BY OrderDate ) as ROWID
FROM
#Invoices inv
)
SELECT
OrderId,OrderDate
,(Select Max(OrderDate) from MyCTE innerAlias where innerAlias.ROWID = (outerAlias.ROWID-1) ) as PreviousOrderDate
,
[MyDiff] =
CASE
WHEN (Select Max(OrderDate) from MyCTE innerAlias where innerAlias.ROWID = (outerAlias.ROWID-1) ) iS NULL then 0
ELSE DATEDIFF (mm, OrderDate , (Select Max(OrderDate) from MyCTE innerAlias where innerAlias.ROWID = (outerAlias.ROWID-1) ) )
END
, ROWIDMINUSONE = (ROWID-1)
, ROWID as ROWID_SHOWN_FOR_KICKS , OrderDate as OrderDateASecondTimeForConvenience
FROM
MyCTE outerAlias
ORDER BY outerAlias.OrderDate Desc , OrderId

Related

SQL - Return count of consecutive days where value was unchanged

I have a table like
date
ticker
Action
'2022-03-01'
AAPL
BUY
'2022-03-02'
AAPL
SELL.
'2022-03-03'
AAPL
BUY.
'2022-03-01'
CMG
SELL.
'2022-03-02'
CMG
HOLD.
'2022-03-03'
CMG
HOLD.
'2022-03-01'
GPS
SELL.
'2022-03-02'
GPS
SELL.
'2022-03-03'
GPS
SELL.
I want to do a group by ticker then count all the times that Actions have sequentially been the value that they are as of the last date, here it's 2022-03-03. ie for this example table it'd be like;
ticker
NumSequentialDaysAction
AAPL
0
CMG
1
GPS
2
Fine to pass in 2022-03-03 as a value, don't need to figure that out on the fly.
Tried something like this
---Table Creation---
CREATE TABLE UserTable
([Date] DATETIME2, [Ticker] varchar(5), [Action] varchar(5))
;
INSERT INTO UserTable
([Date], [Ticker], [Action])
VALUES
('2022-03-01' , 'AAPL' , 'BUY'),
('2022-03-02' , 'AAPL' , 'SELL'),
('2022-03-03' , 'AAPL' , 'BUY'),
('2022-03-01' , 'CMG' , 'SELL'),
('2022-03-02' , 'CMG' , 'HOLD'),
('2022-03-03' , 'CMG' , 'HOLD'),
('2022-03-01' , 'GPS' , 'SELL'),
('2022-03-02' , 'GPS' , 'SELL'),
('2022-03-03' , 'GPS' , 'SELL')
;
---Attempted Solution---
I'm thinking that I need to do a sub query to get the last value and join on itself to get the matching values. Then apply a window function, ordered by date to see that the proceeding value is sequential.
WITH CTE AS (SELECT Date, Ticker, Action,
ROW_NUMBER() OVER (PARTITION BY Ticker, Action ORDER BY Date) as row_num
FROM UserTable)
SELECT Ticker, COUNT(DISTINCT Date) as count_of_days
FROM CTE
WHERE row_num = 1
GROUP BY Ticker;
WITH CTE AS (SELECT Date, Ticker, Action,
DENSE_RANK() OVER (PARTITION BY Ticker ORDER BY Action,Date) as rank
FROM table)
SELECT Ticker, COUNT(DISTINCT Date) as count_of_days
FROM CTE
WHERE rank = 1
GROUP BY Ticker;
You can do this with the help of the LEAD function like so. You didn't specify which RDBMS you're using. This solution works in PostgreSQL:
WITH "withSequential" AS (
SELECT
ticker,
(LEAD("Action") OVER (PARTITION BY ticker ORDER BY date ASC) = "Action") AS "nextDayIsSameAction"
FROM UserTable
)
SELECT
ticker,
SUM(
CASE
WHEN "nextDayIsSameAction" IS TRUE THEN 1
ELSE 0
END
) AS "NumSequentialDaysAction"
FROM "withSequential"
GROUP BY ticker
Here is a way to do this using gaps and islands solution.
Thanks for sharing the create and insert scripts, which helps to build the solution quickly.
dbfiddle link.
https://dbfiddle.uk/rZLDTrNR
with data
as (
select date
,ticker
,action
,case when lag(action) over(partition by ticker order by date) <> action then
1
else 0
end as marker
from usertable
)
,interim_data
as (
select *
,sum(marker) over(partition by ticker order by date) as grp_val
from data
)
,interim_data2
as (
select *
,count(*) over(partition by ticker,grp_val) as NumSequentialDaysAction
from interim_data
)
select ticker,NumSequentialDaysAction
from interim_data2
where date='2022-03-03'
Another option, you could use the difference between two row_numbers approach as the following:
select [Ticker], count(*)-1 NumSequentialDaysAction -- you could use (distinct) to remove duplicate rows
from
(
select *,
row_number() over (partition by [Ticker] order by [Date]) -
row_number() over (partition by [Ticker], [Action] order by [Date]) grp
from UserTable
where [date] <= '2022-03-03'
) RN_Groups
/* get only rows where [Action] = last date [Action] */
where [Action] = (select top 1 [Action] from UserTable T
where T.[Ticker] = RN_Groups.[Ticker] and [date] <= '2022-03-03'
order by [Date] desc)
group by [Ticker], [Action], grp
See demo

How to get the validity date range of a price from individual daily prices in SQL

I have some prices for the month of January.
Date,Price
1,100
2,100
3,115
4,120
5,120
6,100
7,100
8,120
9,120
10,120
Now, the o/p I need is a non-overlapping date range for each price.
price,from,To
100,1,2
115,3,3
120,4,5
100,6,7
120,8,10
I need to do this using SQL only.
For now, if I simply group by and take min and max dates, I get the below, which is an overlapping range:
price,from,to
100,1,7
115,3,3
120,4,10
This is a gaps-and-islands problem. The simplest solution is the difference of row numbers:
select price, min(date), max(date)
from (select t.*,
row_number() over (order by date) as seqnum,
row_number() over (partition by price, order by date) as seqnum2
from t
) t
group by price, (seqnum - seqnum2)
order by min(date);
Why this works is a little hard to explain. But if you look at the results of the subquery, you will see how the adjacent rows are identified by the difference in the two values.
SELECT Lag.price,Lag.[date] AS [From], MIN(Lead.[date]-Lag.[date])+Lag.[date] AS [to]
FROM
(
SELECT [date],[Price]
FROM
(
SELECT [date],[Price],LAG(Price) OVER (ORDER BY DATE,Price) AS LagID FROM #table1 A
)B
WHERE CASE WHEN Price <> ISNULL(LagID,1) THEN 1 ELSE 0 END = 1
)Lag
JOIN
(
SELECT [date],[Price]
FROM
(
SELECT [date],Price,LEAD(Price) OVER (ORDER BY DATE,Price) AS LeadID FROM [#table1] A
)B
WHERE CASE WHEN Price <> ISNULL(LeadID,1) THEN 1 ELSE 0 END = 1
)Lead
ON Lag.[Price] = Lead.[Price]
WHERE Lead.[date]-Lag.[date] >= 0
GROUP BY Lag.[date],Lag.[price]
ORDER BY Lag.[date]
Another method using ROWS UNBOUNDED PRECEDING
SELECT price, MIN([date]) AS [from], [end_date] AS [To]
FROM
(
SELECT *, MIN([abc]) OVER (ORDER BY DATE DESC ROWS UNBOUNDED PRECEDING ) end_date
FROM
(
SELECT *, CASE WHEN price = next_price THEN NULL ELSE DATE END AS abc
FROM
(
SELECT a.* , b.[date] AS next_date, b.price AS next_price
FROM #table1 a
LEFT JOIN #table1 b
ON a.[date] = b.[date]-1
)AA
)BB
)CC
GROUP BY price, end_date

Joining sub-queries to get data for start and end provided dates optimization

I'm using SQL-Server 2008.
I have to select stock of items at provided start date and stock of items at provided end date from 2 warehouses.
This is how I'm selecting QuantityStock for #startDate:
DECLARE #startDate DATE = '20160111'
SELECT *
FROM (
SELECT SUM(QuantityStock) AS QuantityStockStart, Vendor, ItemNo, Company, [Date]
FROM WarehouseA wha
WHERE [Date] = (SELECT MAX([Date])
FROM WarehouseA wha2
WHERE wha.Vendor = wha2.Vendor
AND wha.ItemNo = wha2.ItemNo
AND wha.Company= wha2.Company
AND [Date] <= #startDate)
GROUP BY Vendor, ItemNo, Company, [Date]
UNION ALL
SELECT SUM(QuantityStock) AS QuantityStockStart, Vendor, ItemNo, Company, [Date]
FROM WarehouseB whb
WHERE [Date] = (SELECT MAX([Date])
FROM WarehouseB whb2
WHERE whb.Vendor = whb2.Vendor
AND whb.ItemNo = whb2.ItemNo
AND whb.Company= whb2.Company
AND [Date] <= #startDate)
GROUP BY Vendor, ItemNo, Company, [Date]
) stock_start
LEFT JOIN some_table st ON.....
As you see there are 2 similar queries, just selecting from different tables, for that I'm using UNION ALL
Also I'm using [DATE] <= #startDate that because not every day remaining stock is inserting, so for provided date '20160111' there can be no data, so need to select max date where remaining stock is inserted.
With query above a bit slowly, but working fine.
Problem is that I need to do the same with #endDate to get remaining stock for end date. Query is similar as above just instead of #startDate I need to use #endDate.
I've tried to use query above and LEFT JOIN similar query, just with #endDate instead of #startDate in following:
DECLARE #startDate DATE = '20160111',
#endDate DATE = '20165112'
SELECT stock_start.*, stock_end.QuantityStockEnd
FROM (
SELECT SUM(QuantityStock) AS QuantityStockStart, Vendor, ItemNo, Company, [Date]
FROM WarehouseA wha
WHERE [Date] = (SELECT MAX([Date])
FROM WarehouseA wha2
WHERE wha.Vendor = wha2.Vendor
AND wha.ItemNo = wha2.ItemNo
AND wha.Company= wha2.Company
AND [Date] <= #startDate)
GROUP BY Vendor, ItemNo, Company, [Date]
UNION ALL
SELECT SUM(QuantityStock) AS QuantityStock, Vendor, ItemNo, Company, [Date]
FROM WarehouseB whb
WHERE [Date] = (SELECT MAX([Date])
FROM WarehouseB whb2
WHERE whb.Vendor = whb2.Vendor
AND whb.ItemNo = whb2.ItemNo
AND whb.Company= whb2.Company
AND [Date] <= #startDate)
GROUP BY Vendor, ItemNo, Company, [Date]
) stock_start
LEFT JOIN (
SELECT SUM(QuantityStock) AS QuantityStockEnd, Vendor, ItemNo, Company, [Date]
FROM WarehouseA wha
WHERE [Date] = (SELECT MAX([Date])
FROM WarehouseA wha2
WHERE wha.Vendor = wha2.Vendor
AND wha.ItemNo = wha2.ItemNo
AND wha.Company= wha2.Company
AND [Date] <= #endDate)
GROUP BY Vendor, ItemNo, Company, [Date]
UNION ALL
SELECT SUM(QuantityStock) AS QuantityStockEnd, Vendor, ItemNo, Company, [Date]
FROM WarehouseB whb
WHERE [Date] = (SELECT MAX([Date])
FROM WarehouseB whb2
WHERE whb.Vendor = whb2.Vendor
AND whb.ItemNo = whb2.ItemNo
AND whb.Company= whb2.Company
AND [Date] <= #endDate)
GROUP BY Vendor, ItemNo, Company, [Date]
) stock_end ON stock_start.Vendor = stock_end.Vendor AND stock_start.ItemNo = stock_end.ItemNo AND stock_start.Company = stock_end.Company
LEFT JOIN some_table st ON.....
In this way I got desired results, but Its execution time so high (about 10x longer than first query only with #startDate). Have you ideas how could I optimize It? It looks like there should be any other, simpler way, without repeating code...
So final results should be:
QuantityStockStart | Vendor | ItemNo | Company | [Date] | QuantityStockEnd
I suggest use of the analytic function ROW_NUMBER() to locate the wanted source table rows. While there is no sample data to test against it is something of a guess but I think you may be able to do this:
SELECT
whab.Vendor
, whab.ItemNo
, whab.Company
, MIN(CASE WHEN whab.start_rn = 1 THEN whab.[Date] END) start_dt
, SUM(CASE WHEN whab.start_rn = 1 THEN whab.QuantityStock END) qty_at_start
, MAX(CASE WHEN whab.end_rn = 1 THEN whab.[Date] END) end_dt
, SUM(CASE WHEN whab.end_rn = 1 THEN whab.QuantityStock END) qty_at_end
FROM (
SELECT
Vendor
, ItemNo
, Company
, [Date]
, QuantityStock
, ROW_NUMBER() OVER (PARTITION BY Vendor, ItemNo, Company
ORDER BY CASE WHEN [Date] <= #startDate THEN 1 ELSE 2 END, [Date] DESC) AS start_rn
, ROW_NUMBER() OVER (PARTITION BY Vendor, ItemNo, Company
ORDER BY CASE WHEN [Date] <= #endDate THEN 1 ELSE 2 END, [Date] DESC) AS end_rn
FROM WarehouseA
UNION ALL
SELECT
Vendor
, ItemNo
, Company
, [Date]
, QuantityStock
, ROW_NUMBER() OVER (PARTITION BY Vendor, ItemNo, Company
ORDER BY CASE WHEN [Date] <= #startDate THEN 1 ELSE 2 END, [Date] DESC) AS start_rn
, ROW_NUMBER() OVER (PARTITION BY Vendor, ItemNo, Company
ORDER BY CASE WHEN [Date] <= #endDate THEN 1 ELSE 2 END, [Date] DESC) AS end_rn
FROM WarehouseB
) whab
WHERE whab.start_rn = 1
OR whab.end_rn = 1
GROUP BY
whab.Vendor
, whab.ItemNo
, whab.Company
Try removing the subquery that follows WHERE [Date]= but keeping the where conditions from that subquery. Change [Date] to MAX([Date]) and remove [Date] from the GROUP BY in the query that used to use the removed subquery.

OVER PARTITION BY giving error in SQL Server 2008

I have following query:
SELECT
*,
SUM(BalanceQty) OVER (PARTITION BY Item_Code ORDER BY [Date])
FROM
(SELECT
Date, Item_Code,
SUM(In_Quantity) AS In_Quantity,
SUM(Issue_Quantity) AS Issue_Quantity,
(SUM(In_Quantity) - SUM(issue_Quantity)) AS BalanceQty
FROM
(SELECT
tbl_add_product.Date as Date,
tbl_add_product.Item_Code,
tbl_add_product.In_Quantity,
0 as Issue_Quantity
FROM
tbl_add_product
WHERE
Item_Code = 'pen'
UNION ALL
SELECT
tbl_issue_product.Date as Date,
tbl_issue_product.Item_Code,
0 as In_Quantity,
Issue_Quantity
FROM
tbl_issue_product
WHERE
Item_Code = 'pen') X
GROUP BY
Item_Code, Date) o
It is working fine in SQL Server 2012 but it is causing an error in SQL server 2008. Please suggest solution to it.
if you want a solution which can work on both then try this :
;WITH cte
AS (SELECT Date,
Item_Code,
Sum(In_Quantity) AS In_Quantity,
Sum(Issue_Quantity) AS Issue_Quantity,
( Sum(In_Quantity) - Sum(issue_Quantity) ) AS BalanceQty
FROM (SELECT tbl_add_product.Date AS Date,
tbl_add_product.Item_Code,
tbl_add_product.In_Quantity,
0 AS Issue_Quantity
FROM tbl_add_product
WHERE Item_Code = 'pen'
UNION ALL
SELECT tbl_issue_product.Date AS Date,
tbl_issue_product.Item_Code,
0 AS In_Quantity,
Issue_Quantity
FROM tbl_issue_product
WHERE Item_Code = 'pen') X
GROUP BY Item_Code,Date)
SELECT *,(select SUM(BalanceQty) from cte c2 where c2.Date <=c1.Date)
FROM cte c1
you need to change the database compatibility mode in SQL SERVER 2008, Right click on database select properties, click on "Options" change "Compatibility Level:
You can do this with outer apply or a correlated subquery:
with t as (<your big subquery here>)
select t.*
(select sum(BalanceQty)
from t t2
where t2.Item_Code = t.Item_Code and t2.[Date] <= t.[Date]
) as RunningBalance
from t;
EDIT:
You can add the Item_Code = 'pen' to the outer query, if you want to limit the results to one item.

How to pull the whole row which has the latest date in SQL?

I’m trying to select the pair of product – distribution center attached with the most recent order (based on order date). For one order I can have multiple products, but the whole order will be shipped from one specific distribution center.
How do I select the specific product-distribution center attached with the latest order?
My structure is basically like this:
data.orderdetail table has ordernum, orderdate, distributioncenter
I tried to pull like this, but it doesn’t give me the desired result. I’m using sql server 2008:
SELECT DISTINCT y.OrderNum, y.Product, y.DistributionCenter
, CAST(y.OrderDate AS DATE) AS Orderdate
FROM (SELECT OrderNum, MAX(CAST(Orderdate AS date)) AS orderdate
FROM data.OrderDetail
GROUP BY OrderNum) AS x
INNER JOIN data.OrderDetail AS y
ON y.OrderNum = x.OrderNum
It looks as if you need one more clause in your join Condition
You've got
ON y.OrderNum = x.OrderNum
Which will return all the orders that match the Order number in the subquery
But you'll need
ON y.OrderNum = x.OrderNum
AND y.OrderDate = x.orderdate
Which will return all the orders that match the Order number in the subquery and the maximum date for that order number
SELECT DISTINCT
y.OrderNum,
y.Product,
y.DistributionCenter,
CAST(y.OrderDate AS DATE) AS Orderdate
FROM (
SELECT
OrderNum,
MAX(CAST(Orderdate AS date)) AS orderdate
FROM data.OrderDetail
GROUP BY OrderNum
) AS x
INNER JOIN
data.OrderDetail AS y
ON y.OrderNum = x.OrderNum
AND y.OrderDate = x.orderdate
I believe what you are looking for is row_number. This will partition your result set by OrderNum then rank the sets by the OrderDate. You can then filter off the extra rows in another where clause.
select result.*,
CAST(result.OrderDate as date) as Orderdate,
from (
select y.*,
row_number() over (
partition by y.OrderNum order by CAST(y.OrderDate as date) desc
) rank_
from (
select OrderNum,
MAX(CAST(Orderdate as date)) as orderdate
from data.OrderDetail
group by OrderNum
) as x
inner join data.OrderDetail as y on y.OrderNum = x.OrderNum
) result
where result.rank_ = 1;
select * from
(
SELECT OrderNum, Product, DistributionCenter, OrderDate
, ROW_NUMBER() over (partition by OrderNum order by OrderDate desc) as rownum
FROM OrderDetail
) as xxx
where xxx.rownum = 1
ROW_NUMBER (Transact-SQL)
Try this.
; WITH CTE1
AS (
SELECT
od.OrderNum
, od.Product
, od.DistributionCenter
, CAST(od.OrderDate AS DATE) AS OrderDate
, RowNumber = ROW_NUMBER() OVER (PARTITION BY od.Product, od.DistributionCenter ORDER BY CAST(od.OrderDate AS DATE) DESC)
FROM data.OrderDetail od
)
SELECT
OrderNum
, Product
, DistributionCenter
, OrderDate
FROM CTE1
WHERE RowNumber = 1