Calculate time ranges by by starting dates - sql-server

Saying, I have a table with "Start" dates
ID Start
1 null
2 2001-1-1
3 2002-1-1
Now, I need to calculate effective time ranges for each record in a effective way:
ID Start Finish
1 null 2000-12-31
2 2001-1-1 2001-12-31
3 2002-1-1 null

Use LEAD:
create table #dates
(
ID int,
Start date
)
insert into #dates values(1,null),(2,'01-jan-2001'),(3,'01-jan-2002')
select ID,
Start,
DATEADD(DD, -1, LEAD(Start,1) over (order by Start)) AS Finish
from #dates

Use JOIN.
Query
select t1.[ID], t1.[Start],
dateadd(day, -1, t2.[Start]) as [Finish]
from [your_table_name] t1
left join [your_table_name] t2
on t1.ID = t2.ID - 1;
Demo 1
As HABO mentioned, if Id is not in the correct order. Then give a row number order by ID column.
Query
;with cte as(
select [row_num] = row_number() over(
order by [ID]
),*
from [your_table_name]
)
select t1.[ID], t1.[Start],
dateadd(day, -1, t2.[Start]) as [Finish]
from cte t1
left join cte t2
on t1.[row_num] = t2.[row_num] - 1;
Demo 2

Related

Sum values if they are between date range sql

I want to sum values where date is between de creationdate and endDate,, hence ValueEnd.
For instances the second row, the creationDate is the same as the endDate, so I have to sum the ValuePerDay of this day to the previsou value. So in the column ValueEnd it is 3.4+1.17 = 4.57
I started by calculating the sum from the days where de Difference is 1, like this:
SELECT
CONVERT(CHAR(10), CreationDate,103) CreationDate
,CONVERT(CHAR(10), EndDate,103) EndDate
,SUM(Values_an) Values_an
FROM Dat1
WHERE Difference=1
GROUP BY CONVERT(CHAR(10), CreationDate,103), CONVERT(CHAR(10), EndDate,103), Difference
However, I'm having trouble sum the values where the difference if higher than 1. Can someone help me please?
OK, judging by the provided information - and as far as I understood everything right - the following approach might solve your problem:
DECLARE #t TABLE(
CreationDate date,
EndDate date,
Value_An decimal(19,4)
)
INSERT INTO #t VALUES
('2019-03-01', '2019-03-01', 3.4)
,('2019-03-01', '2019-03-03', 3.5)
,('2019-05-01', '2019-05-01', 3.6)
,('2019-06-01', '2019-06-04', 3.7)
;WITH cteMultiRow AS(
SELECT CreationDate, COUNT(*) cntRows
FROM #t
GROUP BY CreationDate
HAVING COUNT(*) > 1
),
cte AS(
SELECT t.*
,ROW_NUMBER() OVER (PARTITION BY t.CreationDate ORDER BY t.EndDate) AS rn
,DATEDIFF(d, t.CreationDate, t.EndDate)+1 AS Difference
,CASE WHEN m.CreationDate IS NOT NULL THEN t.Value_An/(DATEDIFF(d, t.CreationDate, t.EndDate)+1) ELSE t.Value_An END AS ValuePerD
FROM #t t
LEFT JOIN cteMultiRow m ON t.CreationDate = m.CreationDate
),
cteSums AS(
SELECT c.CreationDate, SUM(c.ValuePerD) AS ValuePerD
FROM cte c
GROUP BY c.CreationDate
)
SELECT c.CreationDate, c.EndDate, c.Value_An, c.Difference, c.ValuePerD, ISNULL(s.ValuePerD, c.Value_An) AS ValueEnd
FROM cte c
LEFT JOIN cteSums s ON c.CreationDate = s.CreationDate AND c.rn = 1

Join for tally table. Need for each day each Cust_ID

Can you help to figure the way to produce that output table like on the pic below. This is part of membership/gap tricky processing I need. Could not figure out how to do this for EACH Cust_ID to have entry for each tally date.
Sample code: (* Last select need to be improved))
CREATE TABLE #test
(
Cust_ID VARCHAR(14),
Contr_ID INT,
ENR_START DATE,
ENR_END DATE
)
INSERT INTO #test
VALUES (1, 1, '2018-1-2', '2018-01-5'),
(1, 2, '2018-01-7', '2018-1-8'),
(2, 1, '2018-01-6', '2019-1-10') ----- select * from #test
SELECT TOP (DATEDIFF(DAY, #Period_Start, #Period_End + 1)) ----- create tally
DATEADD(dd, ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) - 1, #Period_Start) dt
INTO
#c -- select * from #c -- 10 days
FROM
master..spt_values
SELECT
t.*, c.dt
FROM
#c c
LEFT JOIN
#test t ON c.dt BETWEEN t.ENR_START AND t.ENR_END
ORDER BY
1, 5
I think this is the logic that you want:
select cu.cust_id, c.dt, t.*
from (select distinct cust_id from test) cu join
c
on c.dt between '2018-01-01' and '2018-01-10' left join
test t
on t.cust_id = cu.cust_id and c.dt between enr_start and enr_end
order by cu.cust_id, c.dt;
The idea is to generate all cust_id/date combinations and then left join to the original data to get any matches.
Here is a db<>fiddle.

Trying to get DateDiff Based on One Field and Update Another Field

I am trying to update DaysInPeriod with the DateDiff function, based on the change in EFFECTIVESTARTDATE field.
Here is my DLL:
DROP TABLE Reporting_Table
CREATE TABLE Reporting_Table (
Credit_Line_NO Varchar(10),
CURRENCY VARCHAR(3),
AMOUNT INT,
StartDate DATE,
EFFECTIVESTARTDATE DATE,
EXPIRY_DATE Date,
FREQUENCY INT,
CO_CODE VARCHAR(10),
AsOfDate Date,
SOURCEID_REVISED VARCHAR(255),
PID VARCHAR(5),
DaysInPeriod INT
)
INSERT INTO Reporting_Table(CREDIT_LINE_NO,CURRENCY,AMOUNT,STARTDATE,EFFECTIVESTARTDATE,EXPIRY_DATE,FREQUENCY,CO_CODE,ASOFDATE,SourceID_Revised,PID,DaysInPeriod)
VALUES
('1026321','USD','16875','9/30/2017','9/30/2017','9/30/2019','8','US0010001','7/31/2017','','',''),
('1026321','USD','16875','9/30/2017','12/31/2017','9/30/2019','8','US0010001','7/31/2017','','',''),
('1026321','USD','16875','9/30/2017','3/31/2018','9/30/2019','8','US0010001','7/31/2017','','',''),
('1026321','USD','16875','9/30/2017','6/30/2018','9/30/2019','8','US0010001','7/31/2017','','',''),
('1026321','USD','16875','9/30/2017','9/30/2018','9/30/2019','8','US0010001','7/31/2017','','',''),
('1026321','USD','16875','9/30/2017','12/31/2018','9/30/2019','8','US0010001','7/31/2017','','',''),
('1026321','USD','16875','9/30/2017','3/31/2019','9/30/2019','8','US0010001','7/31/2017','','',''),
('1026321','USD','16875','9/30/2017','6/30/2019','9/30/2019','8','US0010001','7/31/2017','','',''),
('1026329','USD','16875','9/30/2017','9/30/2017','9/30/2019','8','US0010001','7/31/2017','','',''),
('1026329','USD','16875','9/30/2017','12/31/2017','9/30/2019','8','US0010001','7/31/2017','','',''),
('1026329','USD','16875','9/30/2017','3/31/2018','9/30/2019','8','US0010001','7/31/2017','','','')
Select *
From Reporting_Table
Select *
From Reporting_Table
I have this SQL:
with cte as
(
select *, rn = row_number() over (partition by Credit_Line_NO,ASOFDATE order by ASOFDATE)
from Reporting_Table
)
Select *
From cte
Basically, when rn=1, DaysInPeriod = 90, and then it should increment by DateDiff(days,rn-1,rn) for every next rn. It should reset based on the change in Credit_Line_NO & ASOFDATE, so I am using:
partition by Credit_Line_NO,ASOFDATE
Here is a sample of what I want to achieve.
I am using SQL Server 2008, so I can't use the Lead/Lag functions. I put together the SQL below, but it doens't execute.
SELECT T1.CREDIT_LINE_NO,
T1.CURRENCY,
T1.AMOUNT,
T1.STARTDATE,
T1.EFFECTIVESTARTDATE,
T1.EXPIRY_DATE,
T1.FREQUENCY,
T1.CO_CODE,
T1.AsOfDate
MIN(T2.EFFECTIVESTARTDATE) AS Date2,
DATEDIFF("D", T1.EFFECTIVESTARTDATE, MIN(T2.EFFECTIVESTARTDATE)) AS DaysDiff
FROM Reporting_Table T1
LEFT JOIN Reporting_Table T2
ON T1.CREDIT_LINE_NO = T2.CREDIT_LINE_NO
AND T2.EFFECTIVESTARTDATE > T1.EFFECTIVESTARTDATE
GROUP BY T1.CREDIT_LINE_NO,
T1.CURRENCY,
T1.AMOUNT,
T1.STARTDATE,
T1.EFFECTIVESTARTDATE,
T1.EXPIRY_DATE,
T1.FREQUENCY,
T1.CO_CODE,
T1.AsOfDate
Finally, I want to run an UPDATE query, or SELECT * INTO NEW_TABLE query.
Your query fails because line 9 T1.AsOfDate is missing a comma. Joining on AND T2.EFFECTIVESTARTDATE > T1.EFFECTIVESTARTDATE creates a 1 to many join which is not necessary. We can imitate a LAG function by applying row_number in a CTE then joining on T1.rn = T2.rn +1.
Edit: I updated your ROW_NUMBER to order by EFFECTIVESTARTDATE since ASOFDATE is a partition column and will always be the same within a window.
Here is the SQL fiddle for this solution.
You can SELECT INTO this result set into a new table or UPDATE an existing table.
WITH cte AS (
SELECT
Credit_Line_NO,
CURRENCY,
AMOUNT,
StartDate,
EFFECTIVESTARTDATE,
EXPIRY_DATE,
FREQUENCY,
CO_CODE,
AsOfDate,
SOURCEID_REVISED,
PID,
DaysInPeriod,
ROW_NUMBER() OVER (PARTITION BY Credit_Line_NO, ASOFDATE ORDER BY EFFECTIVESTARTDATE) AS rn
FROM Reporting_Table
)
SELECT
T1.Credit_Line_NO,
T1.CURRENCY,
T1.AMOUNT,
T1.StartDate,
T1.EFFECTIVESTARTDATE,
T1.EXPIRY_DATE,
T1.FREQUENCY,
T1.CO_CODE,
T1.AsOfDate,
T1.SOURCEID_REVISED,
T1.PID,
CASE
WHEN T1.rn = 1 THEN 90
ELSE DATEDIFF("D", t2.effectivestartdate, t1.effectivestartdate)
END AS DaysInPreiod,
T1.rn
FROM cte AS t1
LEFT JOIN cte AS t2 ON
t1.credit_line_no = t2.credit_line_no
AND t1.rn = t2.rn + 1

Show LastDateOfMonth even where there is no value [duplicate]

This question already has answers here:
Left Join With Where Clause
(7 answers)
Closed 4 years ago.
Here is what I´m doing:
WITH cte AS (
SELECT * FROM TimeDim
)
SELECT t.TimeDimPK, c.ID
FROM CTE AS t
LEFT OUTER JOIN TABLE c ON c.TimeDimFK = t.TimeDimPK
ORDER BY t.TimeDimPK
WHERE c.ID = 1
Result, which is missing dates as shown below:
TimeDimPK ID
20120930 1
20121231 1
20130131 1
What I´m trying to get
TimeDimPK ID
20120930 1
20121031 NULL
20121130 NULL
20121231 1
20130131 1
It looks like your WHERE clause is getting rid of the other dates. Try this instead:
WITH cte
AS ( SELECT TimedimPK
FROM TimeDim
)
SELECT t.TimeDimPK ,
c.ID
FROM cte t
LEFT OUTER JOIN TABLEname c ON c.TimeDimFK = t.TimeDimPK
WHERE c.ID = 1
OR c.ID IS NULL
ORDER BY t.TimeDimPK
You can try to use a "number/date generator", this queries will fill in all missing days, then select the last day of the month. I am not sure about all your data details, so I am currently giving you two suggestions:
1 - Compact suggestion:
DECLARE #minDate DATETIME, #maxDate DATETIME;
SELECT #minDate = MIN(TimeDimPK), #maxDate = MAX(TimeDimPK) FROM TimeDim;
WITH DateGenerator AS
(
--Create a list with a lot of dates from #minDate
SELECT TimeDimPK = CONVERT(DATE, DATEADD(dd, ROW_NUMBER() OVER (ORDER BY OBJECT_ID), #minDate)) FROM sys.objects
)
--List all days, including missing days
SELECT TimeDimPK
, c.ID
FROM DateGenerator n
LEFT JOIN AnotherTable c ON c.TimeDimFK = n.TimeDimPK
WHERE
--Stop the number generator at the last day of the last month from the cte table
n.TimeDimPK <= CONVERT(DATE, DATEADD(s,-1,DATEADD(mm, DATEDIFF(m,0, #maxDate )+1,0)))
--This will get the last day of every month
AND TimeDimPK = CONVERT(DATE, DATEADD(s,-1,DATEADD(mm, DATEDIFF(m,0,TimeDimPK)+1,0)))
2 - Suggestion with your cte clause, if you want to use it for some tweaking:
WITH NumberGenerator AS
(
--Create a number list 1,2,3,4,5,6,7,8,++
SELECT Number = ROW_NUMBER() OVER (ORDER BY OBJECT_ID) FROM sys.objects
), cte AS (
--Your cte query with a date number based on the days between the firts and current days
SELECT
TimeDimPK = CONVERT(DATETIME, TimeDimPK)
--Get the number of days to add from the first day in your table
, DateNumber = DATEDIFF(dd, MIN(TimeDimPK) OVER (), TimeDimPK)
FROM #TimeDim
), TableWithMissingDates AS
(
--Fill missing days
SELECT TimeDimPK = CONVERT(DATE, DATEADD(dd, n.Number - 1, MIN(t.TimeDimPK) OVER ()))
, c.ID
FROM NumberGenerator n
LEFT JOIN cte t ON t.DateNumber = n.Number
LEFT JOIN #Test2 c ON c.TimeDimFK = t.TimeDimPK
--Stop the number generator at the last day of the last month from the cte table
WHERE CONVERT(DATE, DATEADD(dd, n.Number - 1, (SELECT MIN(TimeDimPK) FROM cte))) < CONVERT(DATE, DATEADD(s,-1,DATEADD(mm, DATEDIFF(m,0, (SELECT MAX(TimeDimPK) FROM cte) )+1,0)))
)
SELECT * FROM TableWithMissingDates
WHERE
--This will get the last day of every month
TimeDimPK = CONVERT(DATE, DATEADD(s,-1,DATEADD(mm, DATEDIFF(m,0,TimeDimPK)+1,0)))
Both queries will return the same table:

SQL Server - Get customers with nth order in specific date range

I'm tasked with the following:
Select a list of all customers who had their nth order during a certain date range (usually a specific month).
This list needs to contain: customer id, sum of first n orders
My tables are something like this:
[dbo.customers]: customerID
[dbo.orders]: orderID, customerID,
orderDate, orderTotal
Here is what I've tried so far:
-- Let's assume our threshold (n) is 10
-- Let's assume our date range is April 2013
-- Get customers that already had n orders before the beginning of the given date range.
DECLARE #tmpcustomers TABLE (tmpcustomerID varchar(8))
INSERT INTO
#tmpcustomers
SELECT
c.customerID
FROM
orders o
INNER JOIN customers c ON o.customerID = c.customerID
WHERE
o.orderDate < '2013-04-01'
GROUP BY c.customerID
HAVING (COUNT(o.orderID) >= 10)
-- Now get all customers that have n orders sometime within the given date range
-- but did not have n orders before the beginning of the given date range.
SELECT
a.customerID, SUM(orderTotal) AS firstTenOrderTotal
SELECT
o.customerID, o.orderID, o.orderTotal
FROM
orders o
INNER JOIN customers c ON c.customerID = o.customerID
WHERE
a.customerID NOT IN ( SELECT tmpcustomerID FROM #tmpcustomers )
AND
o.orderDate > '2013-04-01'
AND
o.orderDate < '2013-05-01'
GROUP BY c.customerID
HAVING COUNT(o.orderID) >= 10
This seems to work but it's clunky and slow. Another big problem is that the firstTenOrderTotal is actually the SUM of the total amount of orders by the end of the given date range and not necessarily the first 10.
Any suggestions for a better approach would be much appreciated.
In the insert to #tmpcustomers, why are you joining back to the customer table? The order table already has the customerID that you want. Also, why are you looking for orders where the order date is before your date range? Don't you just want customers with more than n orders between a date range? This will make the second query easier.
By only having the customers with n or more orders in the table variable #tmpcustomers, you should just be able to join it and the orders table in the second query to get the sum of all the orders for those customers where you would once again limit order table records to your date range (so you do not get orders outside of that range). This will remove the having statement and the join to the customers table in your final result query.
Give this a try. Depending on your order distribution it may perform better. In this query im assembling the list of orders in the range, and then looking back to count the number of prior orders (also grabbing the orderTotal).
note: I am assuming the orderID increments as orders are placed.
If this isnt the case just use a row_number over the date to project the sequence into the query.
declare #orders table (orderID int primary key identity(1,1), customerID int, orderDate datetime, orderTotal int)
insert into #orders (customerID, orderDate, orderTotal)
select 1, '2013-01-01', 1 union all
select 1, '2013-01-02', 2 union all
select 1, '2013-02-01', 3 union all
select 2, '2013-01-25', 5 union all
select 2, '2013-01-26', 5 union all
select 2, '2013-02-02', 10 union all
select 2, '2013-02-02', 10 union all
select 2, '2013-02-04', 20
declare #N int, #StartDate datetime, #EndDate datetime
select #N = 3,
#StartDate = '2013-02-01',
#EndDate = '2013-02-20'
select o.customerID,
[total] = o.orderTotal + p.total --the nth order + total prior
from #orders o
cross
apply ( select count(*)+1, sum(orderTotal)
from #orders
where customerId = o.customerID and
orderID < o.orderID and
orderDate <= o.orderDate
) p(n, total)
where orderDate between #StartDate and #EndDate and p.n = #N
Here is my suggestion:
Use Northwind
GO
select ords.OrderID , ords.OrderDate , '<-->' as Sep1 , derived1.* from
dbo.Orders ords
join
(
select CustomerID, OrderID, ROW_NUMBER() OVER(PARTITION BY CustomerID ORDER BY OrderId DESC) AS ThisCustomerCardinalOrderNumber from dbo.Orders
) as derived1
on ords.OrderID = derived1.OrderID
where
derived1.ThisCustomerCardinalOrderNumber = 3
and ords.OrderDate between '06/01/1997' and '07/01/1997'
EDIT:::::::::
I took my CTE example, and reworked it for multiple Customers (seen below).
Give it the college try.
Use Northwind
GO
declare #BeginDate datetime
declare #EndDate datetime
select #BeginDate = '01/01/1900'
select #EndDate = '12/31/2010'
;
WITH
MyCTE /* http://technet.microsoft.com/en-us/library/ms175972.aspx */
( ShipName,ShipAddress,ShipCity,ShipRegion,ShipPostalCode,ShipCountry,CustomerID,CustomerName,[Address],
City,Region,PostalCode,Country,Salesperson,OrderID,OrderDate,RequiredDate,ShippedDate,ShipperName,
ProductID,ProductName,UnitPrice,Quantity,Discount,ExtendedPrice,Freight,ROWID) AS
(
SELECT
ShipName ,ShipAddress,ShipCity,ShipRegion,ShipPostalCode,ShipCountry,CustomerID,CustomerName,[Address]
,City ,Region,PostalCode,Country,Salesperson,OrderID,OrderDate,RequiredDate,ShippedDate,ShipperName
,ProductID ,ProductName,UnitPrice,Quantity,Discount,ExtendedPrice,Freight
, ROW_NUMBER() OVER (PARTITION BY CustomerID ORDER BY OrderDate , ProductName ASC ) as ROWID /* Note that the ORDER BY (here) is directly related to the ORDER BY (near the very end of the query) */
FROM
dbo.Invoices inv /* “Invoices” is a VIEW, FYI */
where
(inv.OrderDate between #BeginDate and #EndDate)
)
SELECT
/*
ShipName,ShipAddress,ShipCity,ShipRegion,ShipPostalCode,ShipCountry,CustomerID,CustomerName,[Address],
City,Region,PostalCode,Country,Salesperson,OrderID,OrderDate,RequiredDate,ShippedDate,ShipperName,
ProductID,ProductName,UnitPrice,Quantity,Discount,ExtendedPrice,Freight,
*/
/*trim the list down a little for the final output */
CustomerID ,OrderID , OrderDate, (ExtendedPrice + Freight) as ComputedTotal
/*The below line is the “trick”. I reference the above CTE, but only get data that is less than or equal to the row that I am on (outerAlias.ROWID)*/
, (Select SUM (ExtendedPrice + Freight) from MyCTE innerAlias where innerAlias.ROWID <= outerAlias.ROWID and innerAlias.CustomerID = outerAlias.CustomerID) as RunningTotal
, ROWID as ROWID_SHOWN_FOR_KICKS , OrderDate as OrderDate
FROM
MyCTE outerAlias
GROUP BY CustomerID ,OrderID, OrderDate, ProductName,(ExtendedPrice + Freight) ,ROWID,OrderDate
/*Two Order By Options*/
ORDER BY outerAlias.CustomerID , outerAlias.OrderDate , ProductName
/* << Whatever the ORDER BY is here, should match the “ROW_NUMBER() OVER ( ORDER BY ________ ASC )” statement inside the CTE */
/*ORDER BY outerAlias.ROWID */ /* << Or, to keep is more “trim”, ORDER BY the ROWID, which will of course be the same as the “ROW_NUMBER() OVER ( ORDER BY” inside the CTE */

Resources