Date difference in sql - sql

I have following table
AssignmentID UserFrom UserTo GroupFrom GroupTo CreatedOn
201410 NULL 4327 103 103 2014/11/11 09:24.7
201549 NULL 4327 103 103 2014/11/11 09:32.4
201549 NULL 4327 103 103 2014/11/11 09:38.4
201673 NULL 4328 103 103 2014/12/11 09:56.1
201673 NULL 4328 103 103 2014/12/11 10:55.1
201673 NULL 4328 103 103 2014/12/11 10:59.1
I want to have datedifference in minutes vertically group by userto
in following way.Please give me suggestion to produce following output.
userto minutes
4327 8
4327 6
4328 55
4328 4

If you are using sqlserver 2008, you could use CROSS APPLY
Note this will not work well with duplicated CreatedOn within the same UserTo:
SELECT
t1.UserTo,
DateDiff(minute, 0, t1.CreatedOn - t2.previousCreatedOn) minutes
FROM yourtable t1
CROSS APPLY
(
SELECT
MAX(CreatedOn) previousCreatedOn
FROM yourtable
WHERE
t1.UserTo = UserTo
AND CreatedOn < t1.CreatedOn
HAVING
MAX(CreatedOn) is not null
) t2
If you are using sqlserver 2012 it is easy using LAG:
;WITH CTE AS
(
SELECT
userto,
datediff(minute, 0, createdon -lag(createdon) over
(partition by userto order by createdon)) minutes
FROM yourtable
)
SELECT userto, minutes
FROM CTE
WHERE minutes is not null

In SQL Server 2012+, you can use lag():
select userto, diff
from (select userto,
datediff(minute, lag(createdon) over (partition by userto order by createdon), createdon) as diff
from table t
) t
where diff is not null;

you can join the table to itself, with join conditions that only allow each row to join (match) the very next row for the same user in chronological sequence
Select a.userTo,
datediff(minute, a.createdOn, b.CreatedON) minutes
from table a
join table b
on b.userto = a.Userto
and b.createdon =
(Select Min(createdOn)
From table
Where userTo = a.UserTo
and createdOn > a.createdOn)

Related

get max date when sum of a field equals a value

I have a problem with writing a query.
Row data is as follow :
DATE CUSTOMER_ID AMOUNT
20170101 1 150
20170201 1 50
20170203 1 200
20170204 1 250
20170101 2 300
20170201 2 70
I want to know when(which date) the sum of amount for each customer_id becomes more than 350,
How can I write this query to have such a result ?
CUSTOMER_ID MAX_DATE
1 20170203
2 20170201
Thanks,
Simply use ANSI/ISO standard window functions to calculate the running sum:
select t.*
from (select t.*,
sum(t.amount) over (partition by t.customer_id order by t.date) as running_amount
from t
) t
where running_amount - amount < 350 and
running_amount >= 350;
If for some reason, your database doesn't support this functionality, you can use a correlated subquery:
select t.*
from (select t.*,
(select sum(t2.amount)
from t t2
where t2.customer_id = t.customer_id and
t2.date <= t.date
) as running_amount
from t
) t
where running_amount - amount < 350 and
running_amount >= 350;
ANSI SQL
Used for the test: TSQL and MS SQL Server 2012
select
"CUSTOMER_ID",
min("DATE")
FROM
(
select
"CUSTOMER_ID",
"DATE",
(
SELECT
sum(T02."AMOUNT") AMOUNT
FROM "TABLE01" T02
WHERE
T01."CUSTOMER_ID" = T02."CUSTOMER_ID"
AND T02."DATE" <= T01."DATE"
) "AMOUNT"
from "TABLE01" T01
) T03
where
T03."AMOUNT" > 350
group by
"CUSTOMER_ID"
GO
CUSTOMER_ID | (No column name)
----------: | :------------------
1 | 03/02/2017 00:00:00
2 | 01/02/2017 00:00:00
db<>fiddle here
DB-Fiddle
SELECT
tmp.`CUSTOMER_ID`,
MIN(tmp.`DATE`) as MAX_DATE
FROM
(
SELECT
`DATE`,
`CUSTOMER_ID`,
`AMOUNT`,
(
SELECT SUM(`AMOUNT`) FROM tbl t2 WHERE t2.`DATE` <= t1.`DATE` AND `CUSTOMER_ID` = t1.`CUSTOMER_ID`
) AS SUM_UP
FROM
`tbl` t1
ORDER BY
`DATE` ASC
) tmp
WHERE
tmp.`SUM_UP` > 350
GROUP BY
tmp.`CUSTOMER_ID`
Explaination:
First I select all rows and subselect all rows with SUM and ID where the current row DATE is smaller or same as all rows for the customer. From this tabe i select the MIN date, which has a current sum of >350
I think it is not an easy calculation and you have to calculate something. I know It could be seen a little mixed but i want to calculate step by step. As fist step if we can get success for your scenario, I believe it can be made better about performance. If anybody can make better my query please edit my post;
Unfortunately the solution that i cannot try on computer is below, I guess it will give you expected result;
-- Get the start date of customers
SELECT MIN(DATE) AS DATE
,CUSTOMER_ID
INTO #table
FROM TABLE t1
-- Calculate all possible date and where is sum of amount greater than 350
SELECT t1.CUSTOMER_ID
,SUM(SELECT Amount FROM TABLE t3 WHERE t3.DATE BETWEEN t1.DATE
AND t2.DATE) AS total
,t2.DATE AS DATE
INTO #tableCalculated
FROM #table t1
INNER JOIN TABLE t2 ON t.ID = t2.ID
AND t1.DATE != t2.DATE
WHERE total > 350
-- SELECT Min amount and date for per Customer_ID
SELECT CUSTOMER_ID, MIN(DATE) AS DATE
FROM #tableCalculated
GROUP BY ID
SELECT CUSTOMER_ID, MIN(DATE) AS GOALDATE
FROM ( SELECT cd1.*, (SELECT SUM(AMOUNT)
FROM CustData cd2
WHERE cd2.CUSTOMER_ID = cd1.CUSTOMER_ID
AND cd2.DATE <= cd1.DATE) AS RUNNINGTOTAL
FROM CustData cd1) AS custdata2
WHERE RUNNINGTOTAL >= 350
GROUP BY CUSTOMER_ID
DB Fiddle

SQL Calculation between dates

I have the following two columns.
Date | Market Value
------------------------------
2016-09-08 | 100
2016-09-07 | 130
2016-09-06 | 140
2016-09-05 | 180
I want to add a column that calulcate the difference in Market Value between the two dates.
Date | Market Value | Delta
------------------------------------------
2016-09-08 | 100 | -30
2016-09-07 | 130 | -10
2016-09-06 | 140 | -40
2016-09-05 | 180 |
.
100 (2016-09-08) minus 130 (2016-09-07) = -30
How do I write that function?
In SQL Server 2012+ the most efficient and simple way is to use the built-in LEAD function.
SELECT
[Date]
,[Market Value]
,LEAD([Market Value]) OVER (ORDER BY [Date] DESC) - [Market Value] AS Delta
FROM YourTable
;
LEAD returns the value of the next row as specified by its ORDER BY clause.
All other methods that self-join the table are less efficient.
If you have continous date you can do
select t1.date, t1.market_value, t1.market_value-t2.market_value from data_table t1 left join data_table t2 on t1.date-1=t2.date
If you dont have continous date and want to calculate diffrence between monday and friday you can use rownum for example like this
select t1.date, t1.market_value, t1.market_value-t2.market_value from (select rownum, date,market_value from data_table) t1 left join (select rownum, date,market_value from data_table) t2 on t1.rownum-1=t2.rownum
CREATE PROCEDURE UPDATE_DELTA
#START_DATE DATETIME,
#END_DATE DATETIME
AS BEGIN
UPDATE T
SET DELTA = MARKET_VALUE - (SELECT MARKET_VALUE
FROM YOURTABLE
WHERE [DATE] = T.[DATE] - 1)
FROM YOURTABLE T
WHERE [DATE] BETWEEN #START_DATE AND #END_DATE
END
And then to execute:
EXEC UPDATE_DELTA '2016-09-05', '2016-09-08'
This works as long as you have sequenced dates.
For SQL-Server below 2012 you could try this:
with cte as
(SELECT
ROW_NUMBER() OVER (ORDER BY [Date] DESC) row,
[Date],
[Market Value]
FROM [YourTable])
SELECT
a.[Date] ,
b.[Market Value] - ISNULL(a.[Market Value],0) AS Delta
FROM
cte a
LEFT JOIN cte b
on a.row = b.row+1
The original post is from here: SQL difference between rows
For SQL-Server 2012 and above you can use the recommended LEAD-Function.
Add column and update in the following way:
UPDATE t SET t.Delta = t.Market_Value-t2.Market_Value
FROM yourtable t
INNER JOIN yourtable t2 ON DATEADD(DD,-1,t.Date) = t2.Date

Joining two tables on the nearest single date

I was hoping someone might help me on this one. I have two tables that need to be joined on the nearest date (nearest before date). I have found with some searching a way to do this using the DATEDIFF and Row_Number functions, but the output is not quite what I want. Here is what i am trying to do:
CREATE TABLE #OPS ([Date] Date, [Runtime] FLOAT, [INTERVAL] INT)
INSERT INTO #OPS Values
( '2015-02-09',29540.3,12),
('2015-02-16',29661.7, 10),
('2015-03-02',29993.7,10),
('2015-03-09',30161.7,12),
('2015-03-16',30333.4,12),
('2015-03-23',30337.9,5),
('2015-03-30',30506.9,12),
('2015-04-06',30628.1,6),
('2015-04-13',30795,4),
('2015-04-20',30961.2,6)
SELECT * FROM #OPS
CREATE TABLE #APPS ([Date] DATE, [Value] INT)
INSERT INTO #APPS Values
('2015-03-05', 1000),('2015-03-27', 1040), ('2015-04-17', 1070)
;WITH Nearest_date AS
(
SELECT
t1.*, t2.Date as date2, t2.Value,
ROW_NUMBER() OVER
(
PARTITION BY t1.[Date]
ORDER BY t2.[Date] DESC
) AS RowNum
FROM #OPS t1
LEFT JOIN #APPS t2
ON t2.[Date] <= t1.[Date]
)
SELECT *
FROM Nearest_date
WHERE RowNum = 1
ORDER BY Date ASC
--This is what I get
Date Runtime INTERVAL date2 Value
2/9/2015 29540.3 12 NULL NULL
2/16/2015 29661.7 10 NULL NULL
3/2/2015 29993.7 10 NULL NULL
3/9/2015 30161.7 12 3/5/2015 1000
3/16/2015 30333.4 12 3/5/2015 1000
3/23/2015 30337.9 5 3/5/2015 1000
3/30/2015 30506.9 12 3/27/2015 1040
4/6/2015 30628.1 6 3/27/2015 1040
4/13/2015 30795 4 3/27/2015 1040
4/20/2015 30961.2 6 4/17/2015 1070
-- This is what I want
Date Runtime INTERVAL date2 Value
2/9/2015 29540.3 12 NULL NULL
2/16/2015 29661.7 10 NULL NULL
3/2/2015 29993.7 10 NULL NULL
3/9/2015 30161.7 12 3/5/2015 1000
3/16/2015 30333.4 12 NULL NULL
3/23/2015 30337.9 5 NULL NULL
3/30/2015 30506.9 12 3/27/2015 1040
4/6/2015 30628.1 6 NULL NULL
4/13/2015 30795 4 NULL NULL
4/20/2015 30961.2 6 4/17/2015 1070
You can see that I want to select the nearest date that date compared against all dates in the second table. The query I created shows the same date for multiple values - when only one of those dates is truly the closest. Any help would be, as always, massively appreciated. -- running MSSQL 2014
Using OUTER APPLY and LEFT JOIN:
SQL Fiddle
SELECT
o.*,
Date2 = t.Date,
t.Value
FROM #OPS o
LEFT JOIN(
SELECT
a.*, Date2 = x.Date
FROM #APPS a
OUTER APPLY(
SELECT TOP 1 *
FROM #OPS
WHERE
[Date] <= a.Date
ORDER BY [Date] DESC
)x
)t
ON t.Date2 = o.Date

How to ignore the first zeros in the result of a query

I would like to ignore if there are any zero values in the first days of production.
SELECT D_DATE, PRODUCE FROM PRODUCTION
Dataset
Date Produce
1/1/2015 0
1/2/2015 0
1/3/2015 0
1/4/2015 6
1/5/2015 5
1/6/2015 2
1/7/2015 0
1/8/2015 1
1/9/2015 1
The first three days are zeros which I would like to ignore in my result but the 7th day should not be ignored
Desired Result
Date Produce
1/4/2015 6
1/5/2015 5
1/6/2015 2
1/7/2015 0
1/8/2015 1
1/9/2015 1
For simplicity I assume that there is at least one day with produce > 0.
SELECT d_date, produce
FROM production
WHERE
d_date >= (
SELECT MIN(d_date)
FROM production
WHERE
produce != 0
)
;
You can use SUM as analytical function to calculate the cumulative sum of produce and filter those greater than zero.
select d_date, produce
from (
select
d_date,
produce,
sum(produce) over (order by d_date) cuml_produce
from production
)
where cuml_produce > 0
order by d_date;
Try this
SELECT date,
produce
FROM
(
SELECT date,
produce,
row_number() over (order by date) r1,
row_number() over (order by produce, date) r2
FROM production
) A
WHERE r1 != r2
Use correlated subquery.
SELECT Date, Produce,
ROW_NUMBER() OVER (ORDER BY DATE) RN
INTO #Temp
FROM tbl
SELECT t.Date, t.Produce FROM #Temp t
WHERE
EXISTS(
SELECT 1 FROM #Temp t1
WHERE t1.rn < t.rn
AND t1.PRODUCE !=0)
OR T.Produce != 0
Fiddle here
Assuming your date is in DATETIME, will this help you?
SELECT D_DATE, PRODUCE
FROM PRODUCTION
where Date >= (select TOP 1 Date from PRODUCTION where PRODUCE > 0 Order by DATE)

Concatenation of adjacent dates in SQL

I would like to know how to make intersections or concatenations of adjacent date ranges in sql.
I have a list of customer start and end dates, for example (in dd/mm/yyyy format, where 31/12/9999 means the customer is still a current customer).
CustID | StartDate | Enddate |
1 | 01/08/2011|19/06/2012|
1 | 20/06/2012|07/03/2012|
1 | 03/05/2012|31/12/9999|
2 | 09/03/2009|16/08/2009|
2 | 16/01/2010|10/10/2010|
2 | 11/10/2010|31/12/9999|
3 | 01/08/2010|19/08/2010|
3 | 20/08/2010|26/12/2011|
Although the dates in different rows don't overlap, I would consider some of the ranges as a contigous period of time, e.g when the start date comes one day after an end date (for a given customer). Hence I would like to return a query that returns just the intersection of the dates,
CustID | StartDate | Enddate |
1 | 01/08/2011|07/03/2012|
1 | 03/05/2012|31/12/9999|
2 | 09/03/2009|16/08/2009|
2 | 16/01/2010|31/12/9999|
3 | 01/08/2010|26/12/2011|
I've looked at CTE tables, but I can't figure out how to return just one row for one contigous block of dates.
This should work in 2005 forward:
;WITH cte2 AS (SELECT 0 AS Number
UNION ALL
SELECT Number + 1
FROM cte2
WHERE Number < 10000)
SELECT CustID, Min(GroupStart) StartDate, MAX(EndDate) EndDate
FROM (SELECT *
, DATEADD(DAY,b.number,a.StartDate) GroupStart
, DATEADD(DAY,1- DENSE_RANK() OVER (PARTITION BY CustID ORDER BY DATEADD(DAY,b.number,a.StartDate)),DATEADD(DAY,b.number,a.StartDate)) GroupDate
FROM Table1 a
JOIN cte2 b
ON b.number <= DATEDIFF(d, startdate, EndDate)
) X
GROUP BY CustID, GroupDate
ORDER BY CustID, StartDate
OPTION (MAXRECURSION 0)
Demo: SQL Fiddle
You can build a quick table of numbers 0-something large enough to cover the spread of dates in your ranges to replace the cte so it doesn't run each time, indexed properly it will run quickly.
you can do this with recursive common table expression:
with cte as (
select t.CustID, t.StartDate, t.EndDate, t2.StartDate as NextStartDate
from Table1 as t
left outer join Table1 as t2 on t2.CustID = t.CustID and t2.StartDate = case when t.EndDate < '99991231' then dateadd(dd, 1, t.EndDate) end
), cte2 as (
select c.CustID, c.StartDate, c.EndDate, c.NextStartDate
from cte as c
where c.NextStartDate is null
union all
select c.CustID, c.StartDate, c2.EndDate, c2.NextStartDate
from cte2 as c2
inner join cte as c on c.CustID = c2.CustID and c.NextStartDate = c2.StartDate
)
select CustID, min(StartDate) as StartDate, EndDate
from cte2
group by CustID, EndDate
order by CustID, StartDate
option (maxrecursion 0);
sql fiddle demo
Quick performance tests:
Results on 750 rows, small periods of 2 days length:
sql fiddle demo
My query: 300 ms
Goat CO query with CTE: 10804 ms
Goat CO query with table of fixed numbers: 7 ms
Results on 5 rows, large periods:
sql fiddle demo
My query: 1 ms
Goat CO query with CTE: 700 ms
Goat CO query with table of fixed numbers: 36 ms