aggregating data to getting running total - sql

I have a query which outputs the below
I need to get it to provide a running total so for March it would give whats been paid in Feb and Mar, then for April Feb,Mar & Apr and so on.
Never come across needing this kind of aggregation before in SQL.

select
[monthid],
[month],
( select sum([paid]) from tbl t2 where t2.[monthid] <= t1.[monthid] ) as paid
from tbl t1

SELECT
T.MonthId
,T.[Month]
,T.Value
,RT.runningTotal
from Table_Name T
CROSS APPLY
(
SELECT SUM(value) as runningTotal
FROM Table_Name
WHERE MonthId <= T.MonthId
) as RT
order by T.MonthId
Test Data
declare #t1 TABLE (Monthid int, month varchar(10), Value decimal(18,2))
insert into #t1
values
(1,'JAN-13',35.00)
,(2, 'FEB-13',35.00)
,(3,'MAR-13',35.00)
,(4,'APR-13',35.00)
,(5,'JUN-13',35.00)
,(6,'Jul-13',35.00)
,(7,'Aug-13',35.00)
SELECT
T.MonthId
,T.[Month]
,T.Value
,RT.runningTotal
from #t1 T
CROSS APPLY
(
SELECT SUM(value) as runningTotal
FROM #t1
WHERE MonthId <= T.MonthId
) as RT
order by T.MonthId
RESULTS
MonthId Month Value runningTotal
1 JAN-13 35.00 35.00
2 FEB-13 35.00 70.00
3 MAR-13 35.00 105.00
4 APR-13 35.00 140.00
5 JUN-13 35.00 175.00
6 Jul-13 35.00 210.00
7 Aug-13 35.00 245.00

You can check this question and my answer on it. Turns out that recursive common table expression is the fastest method to get running total in SQL Server < 2012.
So in your case it could be something like:
with cte as
(
select T.MonthID, T.Month, T.Paid, T.Paid as Running_Paid
from Table1 as T
where T.MonthID = 118
union all
select T.MonthID, T.Month, T.Paid, T.Paid + C.Running_Paid as Running_Paid
from cte as C
inner join Table1 as T on T.MonthID = C.MonthID + 1
)
select *
from cte
option (maxrecursion 0)

Running totals in 2008 are kind of a pain. SQL Fiddle seems to have gone MIA again, but here's a simplistic example of how you can do it.
declare #t1 TABLE
(monthid int,
mth varchar(10),
paid decimal(18,2),
running_paid decimal(18,2))
insert into #t1
values (1,'JAN-13',35.00,0)
,(2, 'FEB-13',35.00,0)
,(3,'MAR-13',35.00,0)
declare #running decimal(18,2)
set #running= 0
update #t1 set running_paid = #running, #running= #running+ paid
select
*
from
#t1
Which will give you:
ID MTH PAID RUNNING_PAID
1 JAN-13 35.00 35.00
2 FEB-13 35.00 70.00
3 MAR-13 35.00 105.00
EDIT:
As Bogdan Sahlean points out, this is a very funky little process. You could also use a cursor:
declare #t1 TABLE
(monthid int,
mth varchar(10),
paid decimal(18,2)
)
insert into #t1
values (1,'JAN-13',35.00)
,(2, 'FEB-13',35.00)
,(3,'MAR-13',35.00)
declare #running table
(monthid int,
mth varchar(10),
paid decimal(18,2),
running_paid decimal(18,2))
declare c cursor
for select monthid,mth,paid from #t1
open c
declare #Id int
declare #Mth varchar(10)
declare #paid decimal(18,2)
declare #Running_Total decimal(18,2)
set #Running_Total = 0
fetch next from c
into #Id,#Mth,#paid
WHILE ##FETCH_STATUS = 0
begin
fetch next from c
into #Id,#Mth,#paid
select #Running_Total = #Running_Total + #paid --Here's this version's hack for running total
insert into #running values (#Id,#Mth,#paid,#Running_Total)
end
select
*
from
#running
They all kind of stink. This is a lot easier in SQL 2012.

Related

Fill up date gap by month

I have table of products and their sales quantity in months.
Product Month Qty
A 2018-01-01 5
A 2018-02-01 3
A 2018-05-01 5
B 2018-08-01 10
B 2018-10-01 12
...
I'd like to first fill in the data gap between each product's min and max dates like below:
Product Month Qty
A 2018-01-01 5
A 2018-02-01 3
A 2018-03-01 0
A 2018-04-01 0
A 2018-05-01 5
B 2018-08-01 10
B 2018-09-01 0
B 2018-10-01 12
...
Then I would need to perform an accumulation of each product's sales quantity by month.
Product Month total_Qty
A 2018-01-01 5
A 2018-02-01 8
A 2018-03-01 8
A 2018-04-01 8
A 2018-05-01 13
B 2018-08-01 10
B 2018-09-01 10
B 2018-10-01 22
...
I fumbled over the "cross join" clause, however it seems to generate some unexpected results for me. Could someone help to give a hint how I can achieve this in SQL?
Thanks a lot in advance.
I think a recursive CTE is a simple way to do this. The code is just:
with cte as (
select product, min(mon) as mon, max(mon) as end_mon
from t
group by product
union all
select product, dateadd(month, 1, mon), end_mon
from cte
where mon < end_mon
)
select cte.product, cte.mon, coalesce(qty, 0) as qty
from cte left join
t
on t.product = cte.product and t.mon = cte.mon;
Here is a db<>fiddle.
Hi i think this example can help you and perform what you excepted :
CREATE TABLE #MyTable
(Product varchar(10),
ProductMonth DATETIME,
Qty int
);
GO
CREATE TABLE #MyTableTempDate
(
FullMonth DATETIME
);
GO
INSERT INTO #MyTable
SELECT 'A', '2019-01-01', 214
UNION
SELECT 'A', '2019-02-01', 4
UNION
SELECT 'A', '2019-03-01', 50
UNION
SELECT 'B', '2019-01-01', 214
UNION
SELECT 'B', '2019-02-01', 10
UNION
SELECT 'C', '2019-04-01', 150
INSERT INTO #MyTableTempDate
SELECT '2019-01-01'
UNION
SELECT '2019-02-01'
UNION
SELECT '2019-03-01'
UNION
SELECT '2019-04-01'
UNION
SELECT '2019-05-01'
UNION
SELECT '2019-06-01'
UNION
SELECT '2019-07-01';
------------- FOR NEWER SQL SERVER VERSION > 2005
WITH MyCTE AS
(
SELECT T.Product, T.ProductMonth AS 'MMonth', T.Qty
FROM #MyTable T
UNION
SELECT T.Product, TD.FullMonth AS 'MMonth', 0 AS 'Qty'
FROM #MyTable T, #MyTableTempDate TD
WHERE NOT EXISTS (SELECT 1 FROM #MyTable TT WHERE TT.Product = T.Product AND TD.FullMonth = TT.ProductMonth)
)
-- SELECT * FROM MyCTE;
SELECT Product, MMonth, Qty, SUM( Qty) OVER(PARTITION BY Product ORDER BY Product
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) as 'TotalQty'
FROM MyCTE
ORDER BY Product, MMonth ASC;
DROP TABLE #MyTable
DROP TABLE #MyTableTempDate
I have other way to perform this in lower SQL Server Version (like 2005 and lower)
It's a SELECT on SELECT if it's your case let me know and i provide some other example.
You can create the months with a recursive CTE
DECLARE #MyTable TABLE
(
ProductID CHAR(1),
Date DATE,
Amount INT
)
INSERT INTO #MyTable
VALUES
('A','2018-01-01', 5),
('A','2018-02-01', 3),
('A','2018-05-01', 5),
('B','2018-08-01', 10),
('B','2018-10-01', 12)
DECLARE #StartDate DATE
DECLARE #EndDate DATE
SELECT #StartDate = MIN(Date), #EndDate = MAX(Date) FROM #MyTable
;WITH dates AS (
SELECT #StartDate AS Date
UNION ALL
SELECT DATEADD(Month, 1, Date)
FROM dates
WHERE Date < #EndDate
)
SELECT A.ProductID, d.Date, COALESCE(Amount,0) AS Amount, COALESCE(SUM(Amount) OVER(PARTITION BY A.ProductID ORDER BY A.ProductID, d.Date ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW),0) AS Total
FROM
(
SELECT ProductID, MIN(date) as DateStart, MAX(date) as DateEnd
FROM #MyTable
GROUP BY ProductID -- As I read in your comments that you need different min and max dates per product
) A
JOIN dates d ON d.Date >= A.DateStart AND d.Date <= A.DateEnd
LEFT JOIN #MyTable T ON A.ProductID = T.ProductID AND T.Date = d.Date
ORDER BY A.ProductID, d.Date
Try this below
IF OBJECT_ID('tempdb..#Temp') IS NOT NULL
DROP TABLE #Temp
;WITH CTE(Product,[Month],Qty)
AS
(
SELECT 'A','2018-01-01', 5 UNION ALL
SELECT 'A','2018-02-01', 3 UNION ALL
SELECT 'A','2018-05-01', 5 UNION ALL
SELECT 'B','2018-08-01', 10 UNION ALL
SELECT 'D','2018-10-01', 12
)
SELECT ct.Product,[MonthDays],ct.Qty
INTO #Temp
FROM
(
SELECT c.Product,[Month],
ISNULL(Qty,0) AS Qty
FROM CTE c
)ct
RIGHT JOIN
(
SELECT -- This code is to get month data
CONVERT(VARCHAR(10),'2018-'+ RIGHT('00'+CAST(MONTH(DATEADD(MM, s.number, CONVERT(DATETIME, 0)))AS VARCHAR),2) +'-01',120) AS [MonthDays]
FROM master.dbo.spt_values s
WHERE [type] = 'P' AND s.number BETWEEN 0 AND 11
)DT
ON dt.[MonthDays] = ct.[Month]
SELECT
MAX(Product)OVER(ORDER BY [MonthDays])AS Product,
[MonthDays],
ISNULL(Qty,0) Qty,
SUM(ISNULL(Qty,0))OVER(ORDER BY [MonthDays]) As SumQty
FROM #Temp
Result
Product MonthDays Qty SumQty
------------------------------
A 2018-01-01 5 5
A 2018-02-01 3 8
A 2018-03-01 0 8
A 2018-04-01 0 8
A 2018-05-01 5 13
A 2018-06-01 0 13
A 2018-07-01 0 13
B 2018-08-01 10 23
B 2018-09-01 0 23
D 2018-10-01 12 35
D 2018-11-01 0 35
D 2018-12-01 0 35
First of all, i would divide month and year to get easier with statistics.
I will give you an example query, not based on your table but still helpful.
--here i create the table that will be used as calendar
Create Table MA_MonthYears (
Month int not null ,
year int not null
PRIMARY KEY ( month, year) )
--/////////////////
-- here i'm creating a procedure to fill the ma_monthyears table
declare #month as int
declare #year as int
set #month = 1
set #year = 2015
while ( #year != 2099 )
begin
insert into MA_MonthYears(Month, year)
select #month, #year
if #month < 12
set #month=#month+1
else
set #month=1
if #month = 1
set #year = #year + 1
end
--/////////////////
--here you are the possible result you are looking for
select SUM(Ma_saledocdetail.taxableamount) as Sold, MA_MonthYears.month , MA_MonthYears.year , item
from MA_MonthYears left outer join MA_SaleDocDetail on year(MA_SaleDocDetail.DocumentDate) = MA_MonthYears.year
and Month(ma_saledocdetail.documentdate) = MA_MonthYears.Month
group by MA_SaleDocDetail.Item, MA_MonthYears.year , MA_MonthYears.month
order by MA_MonthYears.year , MA_MonthYears.month

Adding all the amounts per month using sql server

Here is my data
date amount
2017-07-10 15.00
2017-07-10 15.00
2017-07-28 25.00
2017-08-01 100.00
2017-08-12 15.00
2017-08-29 200.00
2017-09-18 105.00
2017-09-21 200.00
2017-09-23 25.00
2017-10-12 15.00
2017-10-14 500.00
2017-11-01 200.00
2017-11-02 200.00
I want to add it by month so what we will get that in June i got a total of 55, August i will get 315, September 330, October 515, November 400 and the past dates with no amount will be 0 how will i do that?
Here is my temporary table codes:
create table #TempTable
(month varchar(50),
amount decimal(18,2))
insert into #TempTable (month)
SELECT TOP 12
DATENAME(MONTH, DATEADD(MONTH,ROW_NUMBER() OVER (ORDER BY object_id) - 1,0))
FROM sys.columns
create table #Data
(date date,
amount decimal(18,2))
insert into #Data(date,amount) values('2017-07-10',15.00)
insert into #Data(date,amount) values('2017-07-10',15.00)
insert into #Data(date,amount) values('2017-07-28',25.00)
insert into #Data(date,amount) values('2017-08-01',100.00)
insert into #Data(date,amount) values('2017-08-12',15.00)
insert into #Data(date,amount) values('2017-08-29',200.00)
insert into #Data(date,amount) values('2017-09-18',105.00)
insert into #Data(date,amount) values('2017-09-21',200.00)
insert into #Data(date,amount) values('2017-09-23',25.00)
insert into #Data(date,amount) values('2017-10-12',15.00)
insert into #Data(date,amount) values('2017-10-14',500.00)
insert into #Data(date,amount) values('2017-11-01',200.00)
insert into #Data(date,amount) values('2017-11-02',200.00)
select * from #Data
select * from #TempTable
drop table #TempTable
drop table #Data
PS. Just update the #TempTable and put the total on it thank you :)
Use the below code:
SELECT Res1.[Month]
,ISNULL(Res2.Amount,0)
FROM #TempTable Res1
LEFT JOIN
(
select DATENAME(MONTH,Res1.date) AS [Month]
,SUM(amount) AS Amount
from #Data Res1
GROUP BY DATENAME(MONTH,Res1.date)
)Res2 ON Res2.[Month] = Res1.[Month]
SELECT a.Month,SUM(ISNULL(b.Amount,0))
FROM #TempTable a
LEFT JOIN #Data b
ON a.Month = DATENAME(MONTH,date)
GROUP BY a.Month
for update
UPDATE a
SET Amount = ISNULL(GroupSuma,0)
FROM #TempTable a
LEFT JOIN
(
select DATENAME(MONTH,date) as month, SUM(b.Amount) as GroupSuma
FROM #Data b
GROUP BY DATENAME(MONTH,date)
) as c
ON a.Month = c.Month
update temptable from data table
update #TempTable
set amount = d.amount from ( select datename(month, date) date
, SUM(amount) amount from #Data
group by datename(month, date)
) d inner join #TempTable t on t.month = d.date
select * from #TempTable
One of your requirements is:
past dates with no amount will be 0
Your dataset didn't include this so I've added two additional rows in the example code below. I also added a different year to illustrate what happens with different years. I UNION ALL the values from #data with a CTE that select years, every month# of year, and 0. When SUM'ed, most of the 0 entries are eliminated, but months in the year without values in #Data remain with amount = 0. This result is subqueried to eliminate the 0 rows out of range of the min and max dates in #data.
create table #Data (date date, amount decimal(18,2))
insert into #Data(date,amount) values('2017-07-10',15.00)
insert into #Data(date,amount) values('2017-07-10',15.00)
insert into #Data(date,amount) values('2017-07-28',25.00)
insert into #Data(date,amount) values('2017-08-01',100.00)
insert into #Data(date,amount) values('2017-08-12',15.00)
insert into #Data(date,amount) values('2017-08-29',200.00)
insert into #Data(date,amount) values('2017-09-18',105.00)
insert into #Data(date,amount) values('2017-09-21',200.00)
insert into #Data(date,amount) values('2017-09-23',25.00)
insert into #Data(date,amount) values('2017-10-12',15.00)
insert into #Data(date,amount) values('2017-10-14',500.00)
insert into #Data(date,amount) values('2017-11-01',200.00)
insert into #Data(date,amount) values('2017-11-02',200.00)
--additional test values
insert into #Data(date,amount) values('2017-05-04',5.00)
insert into #Data(date,amount) values('2018-02-22',1.00)
DECLARE #minYear int = (SELECT YEAR(MIN(date)) FROM #data)
,#minMonth int = (SELECT MONTH(MIN(date)) FROM #data)
,#maxYear int = (SELECT MAX(YEAR(date)) FROM #data)
,#maxMonth int = (SELECT MONTH(MAX(date)) FROM #data);
WITH cteYear AS
(
SELECT #minYear AS num
UNION ALL
SELECT num + 1 FROM cteYear WHERE num + 1 <= #maxYear
)
SELECT dT.dateyear
,DateName( month , DateAdd( month , dT.datemonth, -1 ) ) AS [month]
,SUM(dT.amount) AS [Sum Amount]
FROM (
SELECT YEAR(date) AS [dateyear], MONTH(date) AS [datemonth], amount -- AS [SumAmount]
FROM #Data D
UNION ALL
SELECT num, monthlist.month, 0
FROM cteYear CROSS JOIN (SELECT 1 AS [month] UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL
SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL
SELECT 9 UNION ALL SELECT 10 UNION ALL SELECT 11 UNION ALL SELECT 12
) AS monthlist
) AS dT
WHERE (#minYear <> #maxYear AND dateyear = #minYear AND datemonth >= #minMonth)
OR
(#minYear <> #maxYear AND dateyear = #maxYear AND datemonth <= #maxMonth)
OR
(#minYear <> #maxYear AND dateyear <> #minYear AND dateyear <> #maxYear)
OR
(#minYear = #maxYear AND datemonth >= #minMonth AND datemonth <= #maxMonth)
GROUP BY dateyear, datemonth
ORDER BY dateyear, datemonth
Gives output:
dateyear month Sum Amount
2017 May 5.00
2017 June 0.00
2017 July 55.00
2017 August 315.00
2017 September 330.00
2017 October 515.00
2017 November 400.00
2017 December 0.00
2018 January 0.00
2018 February 1.00

Select sum with other table in SQL

How do I select sum with other table if I have data like below:
Table Member
MemberID Name DateJoin
M0001 John 01/01/2015
M0002 Willy 03/20/2016
M0003 Teddy 02/01/2017
etc....
Table Transaction
MemberID TransDate Total
M0002 02/01/2015 100000
M0002 02/28/2015 222000
M0001 01/01/2016 150000
M0001 01/26/2017 160000
M0002 01/25/2017 160000
M0003 02/01/2017 9000
I want the result as a sum of how many times the member transaction in shop in years 2015-2017
The result I want it's:
MemberID 2015 2016 2017
M0001 0 1 1
M0002 2 0 1
M0003 0 0 1
How many members will appear in Result although don't have transaction too.
try dynamic sql .
--load in #temp table
select MemberID , datepart (yyyy ,TransDate ) as TransDate ,COUNT(*)as cnt into #temp from [Transaction]
group by MemberID , datepart (yyyy ,TransDate )
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX);
SET #cols = STUFF((SELECT distinct ',' + QUOTENAME(c.TransDate)
FROM #temp c
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT MemberID, ' + #cols + ' from
(
select MemberID
, cnt
, TransDate
from #temp
) x
pivot
(
max(cnt)
for TransDate in (' + #cols + ')
) p '
execute(#query)
drop #temp -- cleanup of #temp table
CREATE TABLE #Table1
([MemberID] varchar(5), [Name] varchar(5), [DateJoin] datetime)
;
INSERT INTO #Table1
([MemberID], [Name], [DateJoin])
VALUES
('M0001', 'John', '2015-01-01 00:00:00'),
('M0002', 'Willy', '2016-03-20 00:00:00'),
('M0003', 'Teddy', '2017-02-01 00:00:00')
;
CREATE TABLE #Table2
([MemberID] varchar(5), [TransDate] datetime, [Total] int)
;
INSERT INTO #Table2
([MemberID], [TransDate], [Total])
VALUES
('M0002', '2015-02-01 00:00:00', 100000),
('M0002', '2015-02-28 00:00:00', 222000),
('M0001', '2016-01-01 00:00:00', 150000),
('M0001', '2017-01-26 00:00:00', 160000),
('M0002', '2017-01-25 00:00:00', 160000),
('M0003', '2017-02-01 00:00:00', 9000)
;
select MemberID,[2015], [2016], [2017]
from
(
select a.MemberID,a.name,a.DateJoin,year(b.TransDate)[year],b.Total from #Table1 A join
#Table2 B on a.MemberID=b.MemberID
) src
pivot
(
count(total)
for year in ([2015], [2016], [2017])
) piv;
output
MemberID 2015 2016 2017
M0001 0 1 1
M0002 2 0 1
M0003 0 0 1
IN 2000
SELECT MEMBERID, COUNT(CASE WHEN YEAR=2015 THEN YEAR END ) AS [2015],
COUNT(CASE WHEN YEAR=2016 THEN YEAR END ) AS [2016],
COUNT(CASE WHEN YEAR=2017 THEN YEAR END ) AS [2017]
FROM (
SELECT A.MEMBERID,A.NAME,A.DATEJOIN,YEAR(B.TRANSDATE)[YEAR],B.TOTAL FROM #TABLE1 A JOIN
#TABLE2 B ON A.MEMBERID=B.MEMBERID)A
GROUP BY MEMBERID
It seems there is no information you need from table member. So select from table transaction alone and count conditionally.
select
memberid,
count(case when year(transdate) = 2015 then 1 end) as [2015],
count(case when year(transdate) = 2016 then 1 end) as [2016],
count(case when year(transdate) = 2017 then 1 end) as [2017]
from transaction
group by memberid
order by memberid;
If you want to include members that don't have any transaction, then you do need a join (an outer join that is):
select
m.memberid,
count(case when year(t.transdate) = 2015 then 1 end) as [2015],
count(case when year(t.transdate) = 2016 then 1 end) as [2016],
count(case when year(t.transdate) = 2017 then 1 end) as [2017]
from member m
left join transaction t on t.memberid = m.memberid
group by m.memberid
order by m.memberid;

SQL self join pairwise

Suppose I have a table consisting of entries like
ID Arrival Date Arrival City Departure Date Departure City
1 Jun 27 2015 Berlin Jun 20 2015 Paris
1 Jul 1 2015 Rome Jun 29 2015 Berlin
1 Jul 30 2015 Vienna Jul 15 2015 Rome
2 Jun 28 2015 Prague Jun 23 2015 Vienna
2 Jul 1 2015 Rome Jun 29 2015 Prague
2 Jul 30 2015 Vienna Jul 15 2015 Moscow
...
and for each ID I want to join this data on itself such that observations with subsequent Departure Date and Arrival Date are grouped pairwise - i.e. a departure is paired with the previous arrival for each ID.
In the example above (where the observations are sorted for convenience) the 2nd row would be appended to the 1st, the 3rd to the 2nd, the 5th to the 4th and the 6th to the 5th (thus producing 4 rows with fields ID Arrival Date Arrival City Departure Date Departure City Arrival Date2 Arrival City2 Departure Date2 Departure City2).
There could potentially be more than three departures for each ID so a general approach is required. Also please note that there can be holes in the data where Arrival City and Departure City does not match - e.g. the Arrival City of the 5th row is not the Departure City of the 6th row but they should still be merged. In fact a major goal is to get a better view of how many holes there are in the data.
A solution is to use a CTE and consider that the difference between two consecutive rows (identified by the rowno) is 1 all the time (and also consider the dates):
;WITH CTE AS (
SELECT
rownum = ROW_NUMBER() OVER (ORDER BY t.ID, t.arrivalDate),
t.ID,
t.arrivalDate,
t.arrivalCity,
t.departureDate,
t.departureCity
FROM #test t
)
SELECT *
FROM CTE c1
JOIN CTE c2
ON c1.ID = c2.ID
AND c2.departureDate > c1.arrivalDate
AND c2.rownum - c1.rownum = 1
GO
-- structure of the #test table
CREATE TABLE #test (
ID int,
arrivalDate date,
arrivalCity varchar(30),
departureDate date,
departureCity varchar(30)
)
SQL fiddle here: SQLFiddle
Try this:
SELECT a.id
,a.arrival_date
,a.arrival_city
,a.departure_date
,a.departure_city
,b.arrival_date arrival_date_2
,b.arrival_city arrival_city_2
,b.departure_date departure_date_2
,b.departure_city departure_city_2
FROM triptable a
JOIN triptable b ON a.id = b.id
AND a.departure_date = (SELECT min(departure_date) FROM so34815894 x WHERE x.departure_date > b.arrival_date AND x.id = b.id)
Edited based on your comment to:
find the record with the earliest departure date after the previous record's
arrival date, and
ignore the fact that the sample data's 6th record has a different
departure city than the 5th record's arrival city.
Not entirely sure what result set your looking for.. but I thought I'd give this a shot and see if any of these help you out.
drop table #t1
create table #t1 (id int, ArrivalDate datetime, ArrivalCity varchar(50), Departuredate datetime, DepartureCity varchar(50))
insert into #t1
values (1, 'Jun 27 2015', 'Berlin', 'Jun 20 2015','Paris'),
(1, 'Jul 1 2015', 'Rome','Jun 29 2015','Berlin'),
(1, 'Jul 30 2015', 'Vienna','Jul 15 2015','Rome'),
(2, 'Jun 28 2015','Prague','Jun 23 2015','Vienna'),
(2, 'Jul 1 2015','Rome','Jun 29 2015','Prague'),
(2, 'Jul 30 2015','Vienna','Jul 15 2015','Moscow')
select *, case when lead(departurecity) over (partition by id order by Arrivaldate) = ArrivalCity or lead(departurecity) over (partition by id order by Arrivaldate) is null then 1 else 0 end as PairID into #t2 from #t1
update #t2
set PairID = id
where pairid != id
and pairid != 0
This is the code to start up..
select * from #t2
will result in:
id ArrivalDate ArrivalCity Departuredate DepartureCity PairID
1 2015-06-27 Berlin 2015-06-20 Paris 1
1 2015-07-01 Rome 2015-06-29 Berlin 1
1 2015-07-30 Vienna 2015-07-15 Rome 1
2 2015-06-28 Prague 2015-06-23 Vienna 2
2 2015-07-01 Rome 2015-06-29 Prague 0
2 2015-07-30 Vienna 2015-07-15 Moscow 2
Any location where the pair id = 0 ... you have a gap/baddata however you want to put it..
You could also:
select *, lead(departurecity) over (partition by ID order by ArrivalDate) as PreviousDepartureCity, lead(Departuredate) over (partition by ID order by ArrivalDate) as PreviousDepartureDate from #t2
This will add previous departure city and date.. and you can do what you want with the nulls.. they will signify the first flight.. or a gap if the subsequent pair id = 0 ...
The select options become endless.... if null and lag(pairid) = 0 then you have the row with the gap.. if null and pair id = id.. and lag(pairid) = id then you have your first flight..
I mean I can keep going.. and give you more details but I'm not sure this is what your looking for.. Hope it helped anyway..
Good luck!
P.S Didn't see why you needed to join the table to itself .. maybe I missed the whole point..lol..sorry if that's the case..
It sounds to me like you're wanting to pivot the results and put the results in additional columns. I used ROW_NUMBER() for the ordering. I concatenated the columns in a row prior to the pivot, pivoted, then used a function to reverse the concatenation.
SELECT
p.ID,
dbo.SplitString(p.[1], CHAR(13), 1) AS arrivalDate1,
dbo.SplitString(p.[1], CHAR(13), 2) AS arrivalCity1,
dbo.SplitString(p.[1], CHAR(13), 3) AS departureDate1,
dbo.SplitString(p.[1], CHAR(13), 4) AS departureCity1,
*
FROM
(
SELECT *
FROM
(
SELECT
ID,
ROW_NUMBER() OVER (PARTITION BY ID ORDER BY arrivalDate) RowNum,
CAST(arrivalDate AS VARCHAR(MAX)) + CHAR(13)
+ arrivalCity + CHAR(13)
+ CAST(departureDate AS VARCHAR(MAX)) + CHAR(13)
+ departureCity TripDetails
FROM trip t
) t
PIVOT (MIN(t.TripDetails) FOR t.RowNum IN ([1], [2], [3], [4], [5] /* , ... */)) p
) p;
using this SplitString function
CREATE FUNCTION dbo.SplitString (
#stringToSplit VARCHAR(MAX),
#delim VARCHAR(255),
#occurence INT )
RETURNS VARCHAR(MAX) AS
BEGIN
DECLARE #name NVARCHAR(255);
DECLARE #pos INT;
DECLARE #orderNum INT;
SET #orderNum=0;
WHILE CHARINDEX(#delim, #stringToSplit) > 0
BEGIN
SELECT #orderNum=#orderNum+1;
SELECT #pos = CHARINDEX(#delim, #stringToSplit) ;
SELECT #name = SUBSTRING(#stringToSplit, 1, #pos-1);
IF #orderNum = #occurence
BEGIN
RETURN #name;
END
SELECT #stringToSplit = SUBSTRING(#stringToSplit, #pos+1, LEN(#stringToSplit)-#pos)
END
SELECT #orderNum=#orderNum+1;
IF #orderNum = #occurence
BEGIN
RETURN #stringToSplit;
END
RETURN NULL;
END
This should work:
with cte as(select *, row_number() over(partition by id order by date) rn from table)
select * from cte c1
join cte c2 on c1.id = c2.id and c1.rn = c2.rn - 1
try this,
declare #t table(ID int,ArrivalDate datetime, ArrivalCity varchar(50)
,DepartureDate datetime,DepartureCity varchar(50))
insert into #t values
(1, 'Jun 27 2015', 'Berlin', 'Jun 20 2015', 'Paris ')
,(1, 'Jul 1 2015 ', 'Rome ', 'Jun 29 2015', 'Berlin ')
,(1, 'Jul 30 2015', 'Vienna', 'Jul 15 2015', 'Rome ')
,(2, 'Jun 28 2015', 'Prague', 'Jun 23 2015', 'Vienna ')
,(2, 'Jul 1 2015 ', 'Rome ', 'Jun 29 2015', 'Prague ')
,(2 , 'Jul 30 2015', 'Vienna', 'Jul 15 2015', 'Moscow ')
;WITH CTE
AS (
SELECT *
,ROW_NUMBER() OVER (
ORDER BY id
,arrivaldate
) rn
FROM #t
)
SELECT A.arrivaldate
,a.arrivalcity
,a.DepartureDate
,a.DepartureCity
,b.arrivaldate
,b.arrivalcity
,b.DepartureDate
,b.DepartureCity
FROM CTE A
LEFT JOIN CTE b ON a.rn + 1 = b.rn

SQL query to calculate days worked per Month

Im stuck on a SQL query. Im using SQL Server.
Given a table that contains Jobs with a start and end date. These jobs can span days or months. I need to get the total combined number of days worked each month for all jobs that intersected those months.
Jobs
-----------------------------------
JobId | Start | End | DayRate |
-----------------------------------
1 | 1.1.13 | 2.2.13 | 2500 |
2 | 5.1.13 | 5.2.13 | 2000 |
3 | 3.3.13 | 2.4.13 | 3000 |
The results i need are:
Month | Days
--------------
Jan | 57
Feb | 7
Mar | 28
Apr | 2
Any idea how i would right such a query ?
I would also like to work out the SUM for each month based on multiplying the dayrate by number of days worked for each job, how would i add this to the results ?
Thanks
You can use recursive CTE to extract all days from start to end for each JobID and then just group by month (and year I guess).
;WITH CTE_TotalDays AS
(
SELECT [Start] AS DT, JobID FROM dbo.Jobs
UNION ALL
SELECT DATEADD(DD,1,c.DT), c.JobID FROM CTE_TotalDays c
WHERE c.DT < (SELECT [End] FROM Jobs j2 WHERE j2.JobId = c.JobID)
)
SELECT
MONTH(DT) AS [Month]
,YEAR(DT) AS [Year]
,COUNT(*) AS [Days]
FROM CTE_TotalDays
GROUP BY MONTH(DT),YEAR(DT)
OPTION (MAXRECURSION 0)
SQLFiddle DEMO
PS: There are 58 days in Jan in your example and not 57 ;)
You can do it using following approach:
/* Your table with periods */
declare #table table(JobId int, Start date, [End] date, DayRate money)
INSERT INTO #table (JobId , Start, [End], DayRate)
VALUES
(1, '20130101','20130202', 2500),
(2,'20130105','20130205', 2000),
(3,'20130303','20130402' , 3000 )
/* create table where stored all possible dates
if this code are supposed to be executed often you can create
table with dates ones to avoid overhead of filling it */
declare #dates table(d date)
declare #d date='20000101'
WHILE #d<'20500101'
BEGIN
INSERT INTO #dates (d) VALUES (#d)
SET #d=DATEADD(DAY,1,#d)
END;
/* and at last get desired output */
SELECT YEAR(d.d) [YEAR], DATENAME(month,d.d) [MONTH], COUNT(*) [Days]
FROM #dates d
CROSS JOIN #table t
WHERE d.d BETWEEN t.Start AND t.[End]
GROUP BY YEAR(d.d), DATENAME(month,d.d)
This only have 1 recursive call instead of 1 for each row. I imagine this will perform better than the chosen answer when you have large amount of data.
declare #t table(JobId int, Start date, [End] date, DayRate int)
insert #t values
(1,'2013-01-01','2013-02-02', 2500),(2,'2013-01-05','2013-02-05', 2000),(3,'2013-03-03', '2013-04-02',3000)
;WITH a AS
(
SELECT min(Start) s, max([End]) e
FROM #t
), b AS
(
SELECT s, e from a
UNION ALL
SELECT dateadd(day, 1, s), e
FROM b WHERE s <> e
)
SELECT
MONTH(b.s) AS [Month]
,YEAR(b.s) AS [Year]
,COUNT(*) AS [Days]
,SUM(DayRate) MonthDayRate
FROM b
join #t t
on b.s between t.Start and t.[End]
GROUP BY MONTH(b.s),YEAR(b.s)
OPTION (MAXRECURSION 0)
Result:
Month Year Days MonthDayRate
1 2013 58 131500
2 2013 7 15000
3 2013 29 87000
4 2013 2 6000