I'm trying to do a query on this table:
Id startdate enddate amount
1 2013-01-01 2013-01-31 0.00
2 2013-02-01 2013-02-28 0.00
3 2013-03-01 2013-03-31 245
4 2013-04-01 2013-04-30 529
5 2013-05-01 2013-05-31 0.00
6 2013-06-01 2013-06-30 383
7 2013-07-01 2013-07-31 0.00
8 2013-08-01 2013-08-31 0.00
I want to get the output:
2013-01-01 2013-02-28 0
2013-03-01 2013-06-30 1157
2013-07-01 2013-08-31 0
I wanted to get that result so I would know when money started to come in and when it stopped. I am also interested in the number of months before money started coming in (which explains the first row), and the number of months where money has stopped (which explains why I'm also interested in the 3rd row for July 2013 to Aug 2013).
I know I can use min and max on the dates and sum on amount but I can't figure out how to get the records divided that way.
Thanks!
with CT as
(
select t1.*,
( select max(endDate)
from t
where startDate<t1.StartDate and SIGN(amount)<>SIGN(t1.Amount)
) as GroupDate
from t as t1
)
select min(StartDate) as StartDate,
max(EndDate) as EndDate,
sum(Amount) as Amount
from CT
group by GroupDate
order by StartDate
SQLFiddle demo
Here's one idea (and a fiddle to go with it):
;WITH MoneyComingIn AS
(
SELECT MIN(startdate) AS startdate, MAX(enddate) AS enddate,
SUM(amount) AS amount
FROM myTable
WHERE amount > 0
)
SELECT MIN(startdate) AS startdate, MAX(enddate) AS enddate,
SUM(amount) AS amount
FROM myTable
WHERE enddate < (SELECT startdate FROM MoneyComingIn)
UNION ALL
SELECT startdate, enddate, amount
FROM MoneyComingIn
UNION ALL
SELECT MIN(startdate) AS startdate, MAX(enddate) AS enddate,
SUM(amount) AS amount
FROM myTable
WHERE startdate > (SELECT enddate FROM MoneyComingIn)
And a second, without using UNION (fiddle):
SELECT MIN(startdate), MAX(enddate), SUM(amount)
FROM
(
SELECT startdate, enddate, amount,
CASE
WHEN EXISTS(SELECT 1
FROM myTable b
WHERE b.id>=a.id AND b.amount > 0) THEN
CASE WHEN EXISTS(SELECT 1
FROM myTable b
WHERE b.id<=a.id AND b.amount > 0)
THEN 2
ELSE 1
END
ELSE 3
END AS partition_no
FROM myTable a
) x
GROUP BY partition_no
although I suppose as written it assumes Id are in order. You could substitute this with a ROW_NUMBER() OVER(ORDER BY startdate).
Something like that should do it :
select min(startdate), max(enddate), sum(amount) from paiements
where enddate < (select min(startdate) from paiements where amount >0)
union
select min(startdate), max(enddate), sum(amount) from paiements
where startdate >= (select min(startdate) from paiements where amount >0)
and enddate <= (select max(enddate) from paiements where amount >0)
union
select min(startdate), max(enddate), sum(amount) from paiements
where startdate > (select max(enddate) from paiements where amount >0)
But for this kind of reporting, It's probably more explicit using multiple queries.
This does what you want:
-- determine the three periods
DECLARE #StartMoneyIn INT
DECLARE #EndMoneyIn INT
SELECT #StartMoneyIn = MIN(Id)
FROM [Amounts]
WHERE amount > 0
SELECT #EndMoneyIn = MAX(Id)
FROM [Amounts]
WHERE amount > 0
-- retrieve the amounts
SELECT MIN(startdate) AS startdate, MAX(enddate) AS enddate, SUM(amount) AS amount
FROM [Amounts]
WHERE Id < #StartMoneyIn
UNION
SELECT MIN(startdate), MAX(enddate), SUM(amount)
FROM [Amounts]
WHERE Id >= #StartMoneyIn AND Id <= #EndMoneyIn
UNION
SELECT MIN(startdate), MAX(enddate), SUM(amount)
FROM [Amounts]
WHERE Id > #EndMoneyIn
If all you want to do is to see when money started coming in and when it stopped, this might work for you:
select
min(startdate),
max(enddate),
sum(amount)
where
amount > 0
This would not include the periods where there was no money coming in though.
If you don't care about the total in the period, but only want the records where you go from 0 to something and vica versa, you could do something crazy like this:
select *
from MoneyTable mt
where exists ( select *
from MoneyTable mtTemp
where mtTemp.enddate = dateadd(day, -1, mt.startDate)
and mtTemp.amount <> mt.amount
and mtTemp.amount * mt.amount = 0)
Or if you must include the first record:
select *
from MoneyTable mt
where exists ( select *
from MoneyTable mtTemp
where mtTemp.enddate = dateadd(day, -1, mt.startDate)
and mtTemp.amount <> mt.amount
and mtTemp.amount * mt.amount = 0 )
or not exists ( select *
from MoneyTable mtTemp
where mtTemp.enddate = dateadd(day, -1, mt.startDate))
Sql Fiddle
Related
I have a data set as such:
Date Value Type
2020-06-01 103 B
2020-06-01 100 A
2020-06-01 133 A
2020-06-11 150 A
2020-07-01 1000 A
2020-07-21 104 A
2020-07-25 140 A
2020-07-28 1600 A
2020-08-01 100 A
Like this:
Type ISHIGH
A 1
B 0
Here's the query i tried,
select type, case when sum(value) > 10 then 1 else 0 end as total_usage
from table_a
where (select sum(value) as usage from tableA where date = max(date)-7)
group by type, date
This is clearly not right. What is a simple way to do this?
It is a simply group by except that you need to be able to access max date before grouping:
select type
, max(date) as last_usage_date
, sum(value) as total_usage
, case when sum(case when date >= cutoff_date then value end) >= 1000 then 'y' end as [is high!]
from t
cross apply (
select dateadd(day, -6, max(date))
from t as x
where x.type = t.type
) as ca(cutoff_date)
group by type, cutoff_date
If you want just those two columns then a simpler approach is:
select t.type, case when sum(value) >= 1000 then 'y' end as [is high!]
from t
left join (
select type, dateadd(day, -6, max(date)) as cutoff_date
from t
group by type
) as a on t.type = a.type and t.date >= a.cutoff_date
group by t.type
Find the max date by type. Then used it to find last 7 days and sum() the value.
with
cte as
(
select [type], max([Date]) as MaxDate
from tableA
group by [type]
)
select c.[type], sum(a.Value),
case when SUM(a.Value) > 1000 then 1 else 0 end as ISHIGH
from cte c
inner join tableA a on a.[type] = c.[type]
and a.[Date] >= DATEADD(DAY, -7, c.MaxDate)
group by c.[type]
This can be done through a cumulative total as follows:
;With CTE As (
Select [type], [date],
SUM([value]) Over (Partition by [type] Order by [date] Desc) As Total,
Row_Number() Over (Partition by [type] Order by [date] Desc) As Row_Num
From Tbl)
Select Distinct CTE.[type], Case When C.[type] Is Not Null Then 1 Else 0 End As ISHIGH
From CTE Left Join CTE As C On (CTE.[type]=C.[type]
And DateDiff(dd,CTE.[date],C.[date])<=7
And C.Total>1000)
Where CTE.Row_Num=1
I think you are quite close with you initial attempt to solve this. Just a tiny edit:
select type, case when sum(value) > 1000 then 1 else 0 end as total_usage
from tableA
where date > (select max(date)-7 from tableA)
group by type
Here is my data:
id
customercode
startdate
enddate
1
122
20200812
20200814
2
122
20200816
20200817
3
122
20200817
20200819
4
122
20200821
20200822
5
122
20200823
20200824
I tried the following code:
select Customercode, min(startdate) as startdate, max(enddate) as enddate
from (
select Customercode, startdate, enddate
sum(rst) over (order by Customercode, DOS) as grp
from (
select Customercode, startdate, enddate
case when coalesce(lag(enddate) over (partition by Customercode order by Customercode, startdate), startdate) + 1 <> startdate then 1 end rst
from tbl
) t1
) t2
group by grp, Customercode
order by startdate
My result
id
customercode
startdate
enddate
1
122
20200812
20200814
2
122
20200816
20200817
3
122
20200817
20200819
4
122
20200821
20200824
The desired output should be like this. Please share your thoughts.
id
customercode
startdate
enddate
1
122
20200812
20200814
2
122
20200816
20200819
3
122
20200821
20200824
It is unclear if you want to group records whose start date is the same as the previous end date, or one day afterwards.
If you want group on the same date, you would phrase the query as:
select customercode, min(startdate), max(enddate)
from (
select t.*,
sum(case when startdate = lag_enddate then 0 else 1 end)
over(partition by customercode order by startdate) as grp
from (
select t.*,
lag(enddate) over(partition by customercode order by startdate) as lag_enddate
from tbl t
) t
) t
group by customercode, grp
order by min(startdate)
You can also allow both cases at once, by modifying the conditional window sum(). This requires a little date artithmetics, whose syntax varies across databases. In standard SQL:
sum(case when startdate <= lag_enddate + interval '1' day then 0 else 1 end)
over(partition by customercode order by startdate) as grp
I have a data ranges with start and end date for a persons, I want to get the continuous date ranges only per persons:
Input:
NAME | STARTDATE | END DATE
--------------------------------------
MIKE | **2019-05-15** | 2019-05-16
MIKE | 2019-05-17 | **2019-05-18**
MIKE | 2020-05-18 | 2020-05-19
Expected output like:
MIKE | **2019-05-15** | **2019-05-18**
MIKE | 2020-05-18 | 2020-05-19
So basically output is MIN and MAX for each continuous period for the person.
Appreciate any help.
I have tried the below query:
With N AS ( SELECT Name, StartDate, EndDate
, LastStop = MAX(EndDate)
OVER (PARTITION BY Name ORDER BY StartDate, EndDate
ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING) FROM Table ), B AS ( SELECT Name, StartDate, EndDate
, Block = SUM(CASE WHEN LastStop Is Null Then 1
WHEN LastStop < StartDate Then 1
ELSE 0
END)
OVER (PARTITION BY Name ORDER BY StartDate, LastStop) FROM N ) SELECT Name
, MIN(StartDate) DateFrom
, MAX(EndDate) DateTo FROM B GROUP BY Name, Block ORDER BY Name, Block
But its not considering the continuous period. It's showing the same input.
This is a type of gap-and-islands problem. There is no need to expand the data out by day! That seems very inefficient.
Instead, determine the "islands". This is where there is no overlap -- in your case lag() is sufficient. Then a cumulative sum and aggregation:
select name, min(startdate), max(enddate)
from (select t.*,
sum(case when prev_enddate >= dateadd(day, -1, startdate) then 0 else 1 end) over
(partition by name order by startdate) as grp
from (select t.*,
lag(enddate) over (partition by name order by startdate) as prev_enddate
from t
) t
) t
group by name, grp;
Here is a db<>fiddle.
Here is an example using an ad-hoc tally table
Example or dbFiddle
;with cte as (
Select A.[Name]
,B.D
,Grp = datediff(day,'1900-01-01',D) - dense_rank() over (partition by [Name] Order by D)
From YourTable A
Cross Apply (
Select Top (DateDiff(DAY,StartDate,EndDate)+1) D=DateAdd(DAY,-1+Row_Number() Over (Order By (Select Null)),StartDate)
From master..spt_values n1,master..spt_values n2
) B
)
Select [Name]
,StartDate= min(D)
,EndDate = max(D)
From cte
Group By [Name],Grp
Returns
Name StartDate EndDate
MIKE 2019-05-15 2019-05-18
MIKE 2020-05-18 2020-05-19
Just to help with the Visualization, the CTE generates the following
This will give you the same result
SELECT subquery.name,min(subquery.startdate),max(subquery.enddate1)
FROM (SELECT NAME,startdate,
CASE WHEN EXISTS(SELECT yt1.startdate
FROM t yt1
WHERE yt1.startdate = DATEADD(day, 1, yt2.enddate)
) THEN null else yt2.enddate END as enddate1
FROM t yt2) as subquery
GROUP by NAME, CAST(MONTH(subquery.startdate) AS VARCHAR(2)) + '-' + CAST(YEAR(subquery.startdate) AS VARCHAR(4))
For the CASE WHEN EXISTS I refered to SQL CASE
For the group by month and year you can see this GROUP BY MONTH AND YEAR
DB_FIDDLE
I am trying to calculate the years of experience
Let's say someone has multiple employments as follow:
startdate enddate
2007-08-27 2008-05-09
2007-08-27 2008-05-09
2012-01-01 2018-07-31
2013-01-06 2019-03-25
What would be the correct sql syntax to just select startdate, enddate which will be later pluged in a function to calculate the number of days for each employment?
Here's my expected result:
1st row: startdate 8/27/2007, enddate 5/9/2008
2nd row: startdate 1/1/2012, enddate 3/25/2019
The second employment took place during the same time for the 1st employment so, it will not be counted. The fourth employment started before the third employment ended so we should use the startdate for the third employment and enddate for the fourth employment
Use Distinct to remove dplicated records. Then you can use this query:
Select totaldays / 365 from
(Select Sum(
DATEDIFF(day, srartdate, enddate )
) As totaldays
)
This query returns the dates without overlapping:
select
v.startdate startdate,
min(vv.enddate) enddate
from view_appemployment v
inner join view_appemployment vv
on v.startdate <= vv.enddate
and not exists (
select * from view_appemployment vvv
where vv.enddate >= vvv.startdate and vv.enddate < vvv.enddate
)
where not exists (
select * from view_appemployment vvvv
where v.startdate > vvvv.startdate and v.startdate <= vvvv.enddate
)
group by v.startdate
See the demo
Results:
startdate | enddate
------------------ | ------------------
27/08/2007 00:00:00 | 09/05/2008 00:00:00
01/01/2012 00:00:00 | 25/03/2019 00:00:00
This is a Gaps & Islands in Sequences problem, the following query should do what you want:
CREATE TABLE #emp (empid int, startdate date,enddate date)
INSERT INTO #emp VALUES
(1,'2007-08-27','2008-05-09'),
(1,'2007-08-27','2008-05-09'),
(1,'2012-01-01','2018-07-31'),
(1,'2013-01-06','2019-03-25')
;WITH starts AS (
SELECT em.*,
(CASE WHEN EXISTS (SELECT 1
FROM #emp em2
WHERE em2.EmpID = em.EmpID AND
em2.StartDate < em.StartDate AND
em2.EndDate >= em.StartDate
) THEN 0 ELSE 1 END) AS [IsStart]
FROM #emp em )
SELECT EmpID
,MIN(StartDate) AS [StartDate]
,MAX(EndDate) AS [EndDate]
FROM (SELECT s.*, SUM(isstart) OVER (PARTITION BY EmpID ORDER BY StartDate) AS [grp]
FROM starts s
) s
GROUP BY EmpID, grp
ORDER BY EmpID
Please try this:
SELECT concat(id," row: start date ",date_format(start_date,'%d/%m/%y')," end date ",date_format(end_date,'%d/%m/%y'))as dateinfo FROM `dates`
Suppose you have a table like (am using SQL Server 2008, no audit log - table is HUGE):
SecID | Date | Price
1 1/1/11 10
1 1/2/11 10
1 1/3/11 5
1 1/4/11 10
1 1/5/11 10
Suppose this table is HUGE (millions of rows for different secIDs and Date) - I would like to return the records when the price changed (looking for something better than using a cursor and iterating):
Am trying to figure out how to get:
SecID | StartDate | EndDate | Price
1 1/1/11 1/2/11 10
1 1/3/11 1/3/11 5
1 1/4/11 1/5/11 10
i.e. another way to look at it is that I am looking for a range of dates where the price has stayed the same.
This is an "islands" problem.
declare #Yourtable table
(SecID int, Date Date, Price int)
INSERT INTO #Yourtable
SELECT 1,GETDATE()-5,10 union all
SELECT 1,GETDATE()-4,10 union all
SELECT 1,GETDATE()-3,5 union all
SELECT 1,GETDATE()-2,10 union all
SELECT 1,GETDATE()-1, 10
;WITH cte AS
(
SELECT SecID,Date,Price,
ROW_NUMBER() OVER (PARTITION BY SecID ORDER BY Date) -
ROW_NUMBER() OVER (PARTITION BY Price, SecID ORDER BY Date) AS Grp
FROM #Yourtable
)
SELECT SecID,Price, MIN(Date) StartDate, MAX(Date) EndDate
FROM cte
GROUP BY SecID, Grp, Price
ORDER BY SecID, MIN(Date)
If the value does not change, the std deviation will be zero
select secId
from ...
group by secId
having count(*) = 1
OR stdev(price) = 0
I think this should work
SELECT SecID, Min(Date) AS StartDate, Max(Date) AS EndDate, Price FROM BigTable GROUP BY SecID, EndDate Having Min(Date) != MAx(Date) And Date != NULL