Cumulative Counts and the Dates the Counts Become Negative and Positive - sql-server

I have the below scenario
Date Amount Item
6/17/08 208 1
9/24/08 -48 1
6/15/09 -160 1
9/23/09 40 1
For the same items, I want to get the date where the cumulative amount is less than or equal to 0 the first time
and if the cumulative amount becomes positive later, I want to get the date it turned positive as well.
Date Amount Item Cumulative Amount
6/17/08 208 1 208
9/24/08 -48 1 160
6/15/09 -160 1 0 --This date
9/23/09 40 1 40 --This date
Any suggestions on how to achieve this?

In SQL Server 2012:
SELECT date
FROM (
SELECT *,
SUM(amount) OVER (PARTITION BY item ORDER BY date) AS csum,
SUM(amount) OVER (PARTITION BY item ORDER BY date ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING) AS psum
FROM mytable
) q
WHERE (csum <= 0 AND psum > 0)
OR
(csum > 0 AND psum <= 0)
In earlier versions:
SELECT date
FROM (
SELECT *,
(
SELECT SUM(amount)
FROM mytable mi
WHERE mi.item = m.item
AND mi.date < m.date
) AS psum
FROM mytable m
) q
WHERE (psum + amount <= 0 AND psum > 0)
OR
(psum + amount > 0 AND psum <= 0)

For the date when it first turns non-positive:
SELECT MIN(t3.Date) AS dateCumAmountTurnsLTEZero
FROM
(SELECT Date,
(SELECT SUM(Amount)
FROM mytable t1
WHERE t1.Date <= t2.Date) AS cumulativeAmount
FROM mytable t2) t3
WHERE t3.cumulativeAmount <= 0
To get the date it turns positive again you could either inject this result into another query or assuming you want to do it all in SQL it gets a bit hairy! See below:
SELECT MIN(t6.Date)
FROM
(SELECT Date,
(SELECT SUM(Amount)
FROM mytable t4
WHERE t4.Date <= t5.Date) AS cumulativeAmount
FROM mytable t5) t6
WHERE t6.cumulativeAmount >= 0
AND t6.Date >
(SELECT MIN(t3.Date)
FROM
(SELECT Date,
(SELECT SUM(Amount)
FROM mytable t1
WHERE t1.Date <= t2.Date) AS cumulativeAmount
FROM mytable t2) t3
WHERE t3.cumulativeAmount <= 0)
See SQL Fiddle Demo

Related

SQL Server pivot datetime records

When I execute the following statement:
select *
from data
where startdate >= '4/06/2018' and enddate <= '11/06/2018'
I get these results:
I would like to get this result in SQL Server:
Is this possible in SQL Server? I'm not sure if I need to use a pivot or not?
Thanks in advance!
If you don't have a calendar table or tally table, you can use an ad-hoc tally table in concert with a LEFT JOIN
Example
Declare #Date1 date = '2018-06-04'
Declare #Date2 date = '2018-06-11'
Select A.[Date]
,Val = sum(isnull(B.Val,0))
From (
Select Top (DateDiff(DAY,#Date1,#Date2)+1) [Date]=DateAdd(DAY,-1+Row_Number() Over (Order By (Select Null)),#Date1)
From master..spt_values n1,master..spt_values n2
) A
Left Join YourTable B
on A.[Date] >= B.StartDate and A.[Date] < B.EndDate
Group By A.[Date]
Order by A.[Date]
Returns
Date Val
2018-06-04 0
2018-06-05 2
2018-06-06 2
2018-06-07 3
2018-06-08 3
2018-06-09 0
2018-06-10 0
2018-06-11 0

How do I sum the records upto a limit

I have a table
CREATE TBALE #tableA (PID int, Amount decimal (10,2), CorrectAmount decimal(10,2))
INSERT INTO #TableA (PID, Amount)
VALUES (1,100), (2,100), (3,100), (4,100), (5,100)
Logic to populate correctAmount:
Now I always need the sum of CorrectAmount as 350 (hard coded)
Keep the 'correct amount' as 'Amount' until its 350
Start with the top record until it hits 350.
Expected result:
#tableA:
PID Amount CorrectAmount
-------------------------
1 100 100
2 100 100
3 100 100
4 100 50
5 100 0
;WITH Joined as(
SELECT
t1.PID,
t1.Amount,
RunningTotal = SUM(t2.Amount),
PrevRunningTotal = SUM(t2.Amount) - t1.Amount,
rn = ROW_NUMBER() OVER(ORDER BY SUM(t2.Amount))
FROM #tableA t1
INNER JOIN #tableA t2
ON t2.PID <= t1.PID
GROUP BY
t1.PID, t1.Amount
HAVING
SUM(t2.Amount) > 350
)
UPDATE t
SET t.CorrectAmount =
CASE
WHEN j.PID IS NULL THEN t.Amount
ELSE
CASE
WHEN j.rn = 1 THEN 350 - j.PrevRunningTotal
ELSE 0
END
END
FROM #tableA t
LEFT JOIN Joined j ON j.PID = t.PID

SQL Server: Gap / Island, 365 day "contiguous" block

I have a table that looks like this:-
tblMeterReadings
id meter date total
1 1 03/01/2014 100.1
1 1 04/01/2014 184.1
1 1 05/01/2014 134.1
1 1 06/01/2014 132.1
1 1 07/01/2014 126.1
1 1 08/01/2014 190.1
This is an 8 day "contiguous block" from '2014-01-03' to '2014-01-08'.
In the real table there are "contiguous blocks" of years in length.
I need to select the MOST RESCENT CONTINUOUS 365 DAY BLOCK (filtered by meter column). If 365 cannot be found, then it should select next largest continuous block.
When I say CONTINUOUS I mean there must be no days missing.
This is beyond me, so if someone can solve... I will be very impressed.
using distinct to not count days with 2 sets of data
declare #gapdays int = 2 -- replace this with 365 in your case
;with x as
(
select datediff(d, '2014-01-01', [date])-dense_rank()over(order by [date]) grp
,[date]
from #t
)
select top 1 max([date]) last_date, min([date]) first_date, count(distinct [date]) days_in_a_row
from x
group by grp
having count(distinct [date]) >= #gapdays
order by max([date]) desc
There you go:
declare #tblMeterReadings table (id int, meter int, [date] date, total money)
insert into #tblMeterReadings ( id, meter, date, total )
values
(1, 1, '03/01/2014', 100.1),
(1, 1, '04/01/2014', 184.1),
(1, 1, '05/01/2014', 134.1),
(1, 1, '06/01/2014', 132.1),
(1, 1, '07/01/2014', 126.1),
(1, 1, '08/01/2014', 190.1),
(1, 1, '10/01/2014', 200.1),
(1, 1, '12/01/2014', 202.1),
(1, 1, '13/01/2014', 204.1)
;with data as (
select i = datediff(day, '2014', [date]), *
from #tblMeterReadings l
)
, islands as (
select island = l.i - row_number() over (order by i), l.*
from data l
)
, spans as (
select l = min(i), r = max(i)
from islands i
group by island
)
select *
from spans s
left join data l on s.l = l.i
left join data r on s.r = r.i
Most recent continuous block not exceeding 365 days in length will be as follows:
select top 1 *
from spans s
left join data l on s.l = l.i
left join data r on s.r = r.i
where s.l - s.r < 365
order by s.l - s.r desc, s.r desc
With recursive CTE and datepart(dayofyear, date):
with cte as
(
select id, meter, date, datepart(dayofyear, date) as x, cast(1 as int) as level, t1.date as startDate from tblMeterReadings t1
where meter = 1
and not exists(select * from tblMeterReadings t2 where (datepart(dayofyear, t1.date) - 1) = datepart(dayofyear, t2.date))
union all
select t1.id, t1.meter, t1.date, datepart(dayofyear, t1.date) as x, t2.level + 1, t2.startDate from tblMeterReadings t1
inner join cte t2 ON (datepart(dayofyear, t1.date)) = (datepart(dayofyear, t2.date) + 1)
)
select TOP 365 * from cte
where cte.startDate = (select top 1 startdate
from cte
--where Level <= 365
order by Level desc, startDate desc)
order by Level desc
OPTION ( MAXRECURSION 365 )
SQL Fiddle example

SQL Server: Data between two dates

Given that there are two dates like 2012-07-08 and 2013-02-06. I need to count record of each user for every month. If there is no data for a user in particular month then it should return zero. The output should look like:-
id name July-12 August-12 September-12 October-12 November-12 December-12 January-13 February-13
1 John 1 2 0 0 1 1 2 2
2 David 0 1 0 1 1 1 2 2
3 Marry 2 1 0 0 0 1 2 2
You cant use BETWEEN in case of date even if BETWEEN is inclusive.
For such cases it is better to use
startdate>='2012-07-08 ' and enddate<=2013-02-06
Because 2013-02-06 will be treated as 2013-02-06 00:00:00 12AM
Try this solution for your problem
DECLARE #temp AS TABLE(Month_Name varchar(25),ContactName varchar(50)) -- newly added
;WITH cte
AS
(
SELECT datename(month, #startdate) AS [Month_Name], #startdate AS dat
UNION ALL
SELECT datename(month, DateAdd(Month, 1, dat)), DateAdd(Month, 1, dat)
FROM cte
WHERE DateAdd(Month, 1, dat) < #enddate
)
INSERT INTO #temp
SELECT c.Month_Name,Cs.username
FROM cte c CROSS JOIN usertbale Cs
ORDER BY c.Month_Name
SELECT c.Month_Name,c.username,ISNULL(tt.coun,0) as coun
FROM #temp c LEFT JOIN
(
SELECT datename(month,OrderDate) AS month_Name
,count(OrderID) as coun
,ContactName
FROM tblpublisher p INNER JOIN tbluser u ON u.userid=p.userid
GROUP BY month_Name,datename(month,publishdate)
) as tt
ON c.Month_Name=tt.month_Name AND c.username=tt.username
EDIT
Using pivot in above query you can achieve the exact result as in your question
Check this
Hope you would require the below function to populate all dates between given condition and you may need to use pivoting to accomplish the task.
Exec: SELECT * FROM dbo.GetDates('2012-07-08','2013-02-06')
CREATE FUNCTION [dbo].[GetDates](#StartDate DATETIME, #EndDate DATETIME)
RETURNS TABLE AS
RETURN (
WITH
N0 AS (SELECT 1 AS N UNION ALL SELECT 1)
,N1 AS (SELECT 1 AS N FROM N0 T1, N0 T2)
,N2 AS (SELECT 1 AS N FROM N1 T1, N1 T2)
,N3 AS (SELECT 1 AS N FROM N2 T1, N2 T2)
,N4 AS (SELECT 1 AS N FROM N3 T1, N3 T2)
,N5 AS (SELECT 1 AS N FROM N4 T1, N4 T2)
,N6 AS (SELECT 1 AS N FROM N5 T1, N5 T2)
,NUMS AS (SELECT ROW_NUMBER() OVER (ORDER BY (SELECT 1)) AS NUM FROM N6)
SELECT CAST(DATEADD(day,num-1,#StartDate) AS Date) as TheDate
FROM NUMS
WHERE NUM <= DATEDIFF(day,#StartDate,#EndDate) + 1
);

how to fetch record monthly total in select query in sql server 2005

I am having following output of query
Query:
SELECT DATENAME(mm, date) [Month], sum(braekTime) [TotalBreakTime],
sum(DATEPART(hh,totalTime) * 60 + DATEPART(mi,totalTime) + DATEPART(ss,totalTime) * 0.017) [Minute],firstName
FROM employeeAttendance,employee
where FK_employeeId = employee.employeeId
GROUP BY DATENAME(mm, date),firstName
ORDER BY [Month]
but I want each n every month record with null/ 0 value
like June and July record is not available then it should display like following
Month TotalBreakTime Minute firstName
----- -------------- ------ ---------
January 0 0 NULL
February 0 0 NULL
March 0 0 NULL
April 0 0 NULL
May 50 1015.000 foramaa
June 0 0 NULL
July 0 0 NULL
.... Like till Dec
You should create a virtual table or subquery for the months, and left join it to the totals query.
eg
select * from
(
select number, datename(m,DATEADD(m, number-1, 0)) as monthname
from master..spt_values
where type='p' and number between 1 and 12
) months
left join
(your totals query) totals
on months.monthname = totals.month
try this:
;with cte as(
select 1 as rn union all select 2 union all select 3),
cte1 as (select ROW_NUMBER() over(order by c1.rn) as row_num
from cte cross join cte c1 cross join cte c2)
select * from cte1
left join
(SELECT DATENAME(mm, date) [Month],
sum(braekTime) [TotalBreakTime],
sum(DATEPART(hh,totalTime) * 60 + DATEPART(mi,totalTime) + DATEPART(ss,totalTime) * 0.017) [Minute],
firstName
FROM employeeAttendance join employee
on FK_employeeId = employee.employeeId
GROUP BY DATENAME(mm, date),firstName
ORDER BY [Month])B
on B.[Month]=DateName( month , DateAdd( month ,cte1.row_num , 0 ) - 1 )
and cte1.row_num <=12

Resources