Maybe this is a newbie question, but
Imagine I have a report that shows the sales-order list pr sales-rep, itemized
to sku level, and want to show how many percent of the total sale of 1 sku, the sales-rep has sold.
I.e.
Sales-person
List of orders
- List of items no sold: 5 out of this months total 942
Example:
John Doe
- Order #12312
- SKU SP1231 Sold 5 . Month total 445
- SKU SP4141 Sold 63 . Month total 300
Emma Doe
- Order #123324
- SKU SP1231 Sold 65 . Month total 445
- SKU SP4141 Sold 2 . Month total 300
etc
The Month total figure is the number of items sold of that particular sku in the total reporting period.
How do I go about adding this number? If I use Fields!TotalAmount.Value it gives the total as the group'ed total. i.e. how many of sku Y was sold on order X by sales-rep Z.
I need the global total of sales of that particular SKU.
If i say SUM(Fields!Amount,Nothing) to set global scope, it gives the sum of ALL sku's, not just the sku in question.
How do I do this?
EDIT
The Report Server is SSRS, the report uses a shared Datasource that is a Report Model already hosted on the reporting server, which points to a SQL Server database with
the contents.
You didn't say what DBMS you are using (not Oracle clearly from the Fields!Amount syntax). Does this work for your DBMS?:
with sku_sales as
( select sku, sum(value) as sku_value
from sales
group by sku
)
select sales.salesperson, sum(sales.value), sku_sales.sku_value
from sales
join sku_sales on sku_sales.sku = sales.sku
group by sales.salesperson, sku_sales.sku_value
What would I do is select Sku total sum in your report dataset using comma separated lists:
-- testing data
DECLARE #Order TABLE (ID INT, SalesRepID INT, Date DATETIME)
INSERT INTO #Order
SELECT 1, 1, GETDATE() UNION
SELECT 2, 2, GETDATE() UNION
SELECT 3, 1, GETDATE() UNION
SELECT 4, 1, GETDATE() UNION
SELECT 5, 2, GETDATE()
DECLARE #OrderDetail TABLE (ID INT, OrderID INT, SkuID INT, SkuCount INT)
INSERT INTO #OrderDetail
SELECT 1, 1, 1, 10 UNION
SELECT 2, 1, 2, 5 UNION
SELECT 3, 1, 3, 20 UNION
SELECT 4, 1, 4, 10 UNION
SELECT 5, 2, 1, 15 UNION
SELECT 6, 2, 2, 25 UNION
SELECT 7, 2, 3, 15 UNION
SELECT 8, 3, 1, 15 UNION
SELECT 9, 3, 1, 10 UNION
SELECT 10, 3, 3, 10 UNION
SELECT 11, 3, 4, 15 UNION
SELECT 12, 4, 1, 5
DECLARE #Sku TABLE (ID INT, SkuCode VARCHAR(10))
INSERT INTO #Sku
SELECT 1, 'SP1233' UNION
SELECT 2, 'SP2262' UNION
SELECT 3, 'SP1531' UNION
SELECT 4, 'SP4235'
DECLARE #SalesRep TABLE (ID INT, SalesRepName VARCHAR(20))
INSERT INTO #SalesRep
SELECT 1, 'John Doe' UNION
SELECT 2, 'Emma Doe'
-- filters for testing
DECLARE #StartDate DATETIME, #EndDate DATETIME
SELECT #StartDate = GETDATE(), #EndDate = GETDATE()
DECLARE #SkuIDList VARCHAR(8000), #SkuSumList VARCHAR(8000)
SELECT #SkuIDList = '', #SkuSumList = ''
--gether all sku IDs and Sum in two comma separated list
SELECT #SkuIDList = #SkuIDList + CONVERT(VARCHAR, OD.SkuID) + ',',
#SkuSumList = #SkuSumList + CONVERT(VARCHAR, SUM(OD.SkuCount)) + ','
FROM #Order O
INNER JOIN #OrderDetail OD ON O.ID = OD.OrderID
WHERE O.Date BETWEEN #StartDate AND #EndDate
GROUP BY OD.SkuID
-- remove last ','
SELECT #SkuIDList = SUBSTRING(#SkuIDList, 0, LEN(#SkuIDList)),
#SkuSumList = SUBSTRING(#SkuSumList, 0, LEN(#SkuSumList))
-- include thouse lists in the main select for your report dataset
SELECT O.ID, OD.SkuID, O.SalesRepID, SR.SalesRepName, S.SkuCode,
OD.SkuCount, #SkuIDList AS SkuIDs, #SkuSumList AS SkuSums
FROM #Order O
INNER JOIN #OrderDetail OD ON O.ID = OD.OrderID
INNER JOIN #Sku S ON OD.SkuID = S.ID
INNER JOIN #SalesRep SR ON O.SalesRepID = SR.ID
WHERE O.Date BETWEEN #StartDate AND #EndDate
Then you can use some custome code to retrieve sum value by sku ID (I have to write in C# currently, you easely convert it to VB):
public int GetSkuSum(string skuSumCSV, string skuIDCSV, int searchSkuID)
{
string[] strSkuSum = skuSumCSV.Split(',');
string[] strSkuID = skuIDCSV.Split(',');
for (int i = 0; i < strSkuID.Length; i++)
{
if (Convert.ToInt32(strSkuID[i].Trim()) == searchSkuID)
{
return Convert.ToInt32(strSkuSum[i]);
}
}
return 0;
}
Then use it in your textbox Value expression:
=Code.GetSkuSum(Fields!SkuIDs.Value,Fields!SkuSums.Value,Fields!SkuID.Value)
Related
SQL Server 2012-2017. Can this be done with a CTE? Trying to avoid using a cursor.
I have a report request to return the inventory for the first of each month between dateFrom and dateTo, excluding the inventory on the dateFrom and dateTo.
Inventory is tracked by status history for each item. Each status history is coded as either in-inventory or out-of-inventory.
There could be many status history entries with an in-inventory status to track process steps. There will be another status history entry when the item is shipped, broken, lost, etc, and those are coded as out-of-inventory.
For reporting, an item is in inventory if the most recent status in status history before the reporting date is one that we code as in-inventory.
DECLARE #dateFrom dateTime, #dateTo dateTime
SET #dateFrom = '2-Nov-2017'
SET #dateTo = '20-Feb-2018 23:59:59.9'
--this proves out the dates are calculating correctly
;WITH cteDateTest AS
(
SELECT
1 roundCount,
DATEADD(M, DATEDIFF(M, 0, #dateFrom), 31) invDate --returns first of the month following dateFrom
UNION ALL
SELECT
roundCount + 1,
DATEADD(M, 1, invDate) --this one would go into the row_number join
FROM
cteDateTest
WHERE
DATEADD(M, 1, invDate) < #DateTo
)
SELECT * FROM cteDateTest
I've simplified the tables, using temp tables to be explicit that these are NOT the issue but created for ease in others understanding the problem.
CREATE TABLE #tempItems
(
id INT PRIMARY KEY,
itemDesc NVARCHAR (15) NULL,
isActive BIT
)
INSERT INTO #tempItems (id, itemDesc, isActive)
SELECT 1, 'widget 1',1 UNION ALL
SELECT 2, 'toy 2',1 UNION ALL
SELECT 3, 'something 3',1 UNION ALL
SELECT 4, 'prize 4',1
CREATE TABLE #tempStatusHistory
(
historyID INT PRIMARY KEY,
itemId INT,
itemStatus NVARCHAR (25) NULL,
statusDate DATETIME,
statusIsInInventory BIT,
)
INSERT INTO #tempStatusHistory (historyID, itemId, itemStatus, statusDate, statusIsInInventory)
SELECT 1, 1, 'in receiving', '2017-10-10',1 UNION ALL
SELECT 2, 1, 'in test', '2017-10-11',1 UNION ALL
SELECT 3, 1, 'on shelves', '2017-10-31',1 UNION ALL
SELECT 4, 2, 'in receiving', '2017-11-15',1 UNION ALL
SELECT 5, 2, 'in test', '2017-11-16',1 UNION ALL
SELECT 6, 2, 'on shelves', '2017-12-17',1 UNION ALL
SELECT 7, 2, 'sold', '2017-12-24',0 UNION ALL
SELECT 8, 3, 'in test', '2017-11-18',1 UNION ALL
SELECT 9, 3, 'in repair', '2017-12-19',1 UNION
SELECT 10, 3, 'returned to vendor', '2018-02-03',0 UNION ALL
SELECT 11, 4, 'in receiving', '2018-01-20',1 UNION ALL
SELECT 12, 4, 'on shelves', '2018-01-21',1 UNION ALL
SELECT 13, 4, 'sold', '2018-03-20',0
--select * from #tempStatusHistory
/* Per above data:
widget 1 in inventory all these months. Toy 2 in Dec 1 inventory. something 3 in Dec1, Jan 1, Feb 1 inventory, prize 4 in Feb 1 and Mar 1 inventory
Dec 1 inventory = 3 (widget 1, toy 2, something 3)
Jan 1 inventory = 2 (widget 1, something 3)
Feb 1 inventory = 3 (widget 1, something 3, prize 4)
Mar 1 inventory = 2 (widget 1, prize 4)
*/
--Our normal way of getting inventory for #dateFrom
SELECT
SH.historyId historyId, I.itemDesc
FROM
#tempItems I
JOIN
(SELECT
ROW_NUMBER () OVER (PARTITION BY itemId ORDER BY statusDate DESC) AS [Index],
itemId, historyId, statusDate, statusIsInInventory
FROM
#tempStatusHistory
WHERE
statusDate < #dateFrom) SH ON I.id = SH.itemId AND SH.[Index] = 1
WHERE
SH.statusIsInInventory = 1
--trying to pull inventory for each month between #dateFrom and #dateTo (exclusive of the end dates)
--anchor part of cte
;WITH cteInv AS
(
SELECT
1 roundCount,
DATEADD(M, DATEDIFF(M, 0, #dateFrom), 31) invDate,
DATEADD(M, 1, #dateFrom) staticDate,
1 linkField,
SH.historyId historyId,
I.itemDesc
FROM
#tempItems I
JOIN
(SELECT
ROW_NUMBER () OVER (PARTITION BY itemId ORDER BY statusDate DESC) AS [Index],
itemId, historyId, statusDate, statusIsInInventory
FROM
#tempStatusHistory
WHERE
statusDate < DATEADD(M, DATEDIFF(M, 0, #dateFrom), 31)) SH ON I.id = SH.itemId AND SH.[Index] = 1
WHERE
SH.statusIsInInventory = 1
--recursive part
UNION ALL
SELECT
roundCount + 1
, DATEADD(M,1,invDate)
, DATEADD(M,1,#dateFrom) staticDate
, 1
, SH.historyId
, I.itemDesc
FROM #tempItems I
--invDate not happy below
JOIN (SELECT ROW_NUMBER () OVER (PARTITION BY itemId ORDER BY statusDate DESC) AS [Index], itemId, historyId, statusDate, statusIsInInventory
FROM #tempStatusHistory WHERE statusDate < DATEADD(M,1,invDate)) SH ON I.id = SH.itemId AND SH.[Index] = 1
JOIN cteInv C ON I.isActive = C.linkField
WHERE DATEADD(M,1,invDate)< #dateTo AND SH.statusIsInInventory = 1
)
SELECT * from cteInv order by roundCount, invDate, itemDesc
drop table #tempItems
drop table #tempStatusHistory
the reference to invDate in the status history link shows as an error "invalid column name invDate". I can't figure a way around this. I also suspect an issue because if I replace invDate with #dateFrom in the same spot, I had expected the same inventory result for each month calculation, but it started multiplying itself.
Is cte a good solution for this? Is there a better way?
Thanks for anyone helping me on my first post here.
Addition: Expected output would be:
roundCount invDate linkField historyId itemDesc
-----------------------------------------------------------
1 2017-12-01 1 8 something 3
1 2017-12-01 1 5 toy 2
1 2017-12-01 1 3 widget 1
2 2018-01-01 1 9 something 3
2 2018-01-01 1 5 toy 2
3 2018-02-01 1 12 prize 4
3 2018-02-01 1 9 something 3
3 2018-02-01 1 3 widget 1
You are actually very very closed. Just need one OUTER APPLY
-- this is your cteDateTest query
;WITH
cteDateTest AS
(
SELECT
1 roundCount
,DATEADD(M,DATEDIFF(M,0,#dateFrom),31) invDate --returns first of the month following dateFrom
UNION ALL
SELECT
roundCount + 1
,DATEADD(M,1,invDate) --this one would go into the row_number join
FROM cteDateTest
WHERE DATEADD(M,1,invDate)< #DateTo
)
SELECT *
from cteDateTest d
OUTER APPLY
(
-- this is your normal query of getting inventory for #dateFrom
SELECT SH.historyID
, I.itemDesc
FROM #tempItems I
INNER JOIN
(
SELECT ROW_NUMBER () OVER (PARTITION BY itemId ORDER BY statusDate DESC) AS [Index],
itemId, historyID, statusDate, statusIsInInventory
FROM #tempStatusHistory
WHERE statusDate < d.invDate -- change to invDate from cteDateTest
) SH ON I.id = SH.itemId
AND SH.[Index] = 1
WHERE SH.statusIsInInventory = 1
) h
I am working in SQL Server 2012. I have 3 tables. The first is a "schedule" table. Its structure is like:
CREATE TABLE schedule
(
JobID int
,BeginDate date
,EndDate date
)
Some sample data is:
INSERT INTO schedule
SELECT 1, '2017-01-01', '2017-07-31' UNION ALL
SELECT 2, '2017-02-01', '2017-06-30'
The second is a "frequency" table. Its structure is like:
CREATE TABLE frequency
(
JobID int
,RunDay varchar(9)
)
Some sample data is:
INSERT INTO frequency
SELECT 1, 'Sunday' UNION ALL
SELECT 1, 'Monday' UNION ALL
SELECT 1, 'Tuesday' UNION ALL
SELECT 1, 'Wednesday' UNION ALL
SELECT 1, 'Thursday' UNION ALL
SELECT 1, 'Friday' UNION ALL
SELECT 1, 'Saturday' UNION ALL
SELECT 2, 'Wednesday'
The third is a "calendar" table. Its structure is like:
CREATE TABLE calendar
(
CalendarFullDate date
,DayName varchar(9)
)
My goal is to "unpivot" the schedule table so that I create a row for each date spanning the date range in BeginDate and EndDate for each JobID. The rows must match the days in the frequency table per JobID.
Up until now, the frequencies of dates for each job are either daily or weekly. For this, I use the following SQL to generate my desired table:
SELECT
s.JobID
,c.CalendarFullDate
FROM
schedule AS s
INNER JOIN
calendar AS c
ON
c.CalendarFullDate BETWEEN s.BeginDate AND s.EndDate
INNER JOIN
frequency AS f
ON
f.JobID = s.JobID
AND f.RunDay = c.DayName
This doesn't work for frequencies that are higher than weekly (e.g., bi-weekly). To do so, I know that my frequency table would need to change structure. In particular, I would have to add a column that gives the frequency (e.g., daily, weekly, bi-weekly). And, I'm betting that I will need to add a week number column to the calendar table as well.
How can I generate my desired table to accommodate at least bi-weekly frequencies (if not higher frequencies)? For example, if JobID = 3 is a bi-weekly job that runs on Wednesday, and it's bound by BeginDate = '2017-06-01' and EndDate = '2017-07-31', then, for this job, I would expect the following in the result:
JobID Date
3 2017-06-07
3 2017-06-21
3 2017-07-05
3 2017-07-19
I have changed the schedule table instead of the frequency table. I have added a SkipWeeks field that should be set to 1 for bi-weekly, 2 to run the job every third week etc. I have used a table-valued function to return the right dates. I think this is what you wanted.
CREATE TABLE dbo.schedule
(
JobID int
,BeginDate date
,EndDate date
,SkipWeeks tinyint default 0
)
GO
INSERT INTO dbo.schedule
(JobID,BeginDate,EndDate)
SELECT 1, '2017-01-01', '2017-07-31' UNION ALL
SELECT 2, '2017-02-01', '2017-06-30'
GO
CREATE TABLE dbo.frequency
(
JobID int
,RunDay varchar(9),
primary key (
JobID,
RunDay
)
)
GO
INSERT INTO dbo.frequency
SELECT 1, 'Sunday' UNION ALL
SELECT 1, 'Monday' UNION ALL
SELECT 1, 'Tuesday' UNION ALL
SELECT 1, 'Wednesday' UNION ALL
SELECT 1, 'Thursday' UNION ALL
SELECT 1, 'Friday' UNION ALL
SELECT 1, 'Saturday' UNION ALL
SELECT 2, 'Wednesday'
GO
CREATE FUNCTION dbo.DateRangeTable(#pdStartDate date, #pdEndDate date, #piSkipWeeks tinyint)
RETURNS
#dates TABLE
(
[Date] date primary key,
DayName varchar(9)
)
AS
BEGIN
declare #ldDate date = #pdStartDate
declare #skipDecr smallint = #piSkipWeeks
while (#ldDate <= #pdEndDate) Begin
if #skipDecr = 0 Begin
insert into #dates
select #ldDate, format(#ldDate,'dddd')
End
--Start of New week? (% = MOD)
if datediff(d,#pdStartDate,#ldDate) % 7 = 0 Begin
if #skipDecr = 0 Begin
set #skipDecr = #piSkipWeeks
End else Begin
set #skipDecr = #skipDecr - 1
End
End
set #ldDate = dateadd(D,1, #ldDate)
End
RETURN
END
GO
INSERT INTO dbo.schedule
(JobID,BeginDate,EndDate,SkipWeeks)
SELECT 3, '2017-06-01','2017-07-31',1
GO
INSERT INTO dbo.frequency
SELECT 3, 'Wednesday'
GO
SELECT
s.JobID
,c.[Date]
FROM dbo.schedule AS s
cross apply dbo.DateRangeTable(s.BeginDate, s.EndDate, s.SkipWeeks) c
INNER JOIN dbo.frequency AS f
ON f.JobID = s.JobID
AND f.RunDay = c.DayName
GO
I have 2 tables:
COURSE
------
Id
Name
TEST
------
Id
CourseId (FK to `COURSE.ID`)
DATETIME
NUMBERS
Suppose COURSE table with ID 1,2 (only 2 columns) and TEST table with 8 numbers of data having different DATETIME and CourseId of 1 (3 columns) and 2 (6 columns).
I want to find the minimum DATETIME,CourseID and Name by joining these 2 tables. The below query is giving a 2 output:
(SELECT min([DATETIME]) as DATETIME ,[TEST].CourseID,Name
FROM [dbo].[TEST]
left JOIN [dbo].[COURSE]
ON [dbo].[TEST].CourseID=[COURSE].ID GROUP BY CourseID,Name)
I want a single column output i.e. a single output column (minimum datetime along with Name and ID)..HOW can i achieve??
With 2 courses you are always going to get 2 rows when joining like this. It will give you the minimum date value for each course. The first way you can get a single row is to use TOP 1 in your query, which will simply give you the course with the earliest test date. The other way is to use a WHERE clause to filter it by a single course.
Please run this sample code with some variations of what you can do, notes included in comments:
CREATE TABLE #course ( id INT, name NVARCHAR(20) );
CREATE TABLE #Test
(
id INT ,
courseId INT ,
testDate DATETIME -- you shouldn't use a keyword for a column name
);
INSERT INTO #course
( id, name )
VALUES ( 1, 'Maths' ),
( 2, 'Science' );
-- note I used DATEADD(HOUR, -1, GETDATE()) to simply get some random datetime values
INSERT INTO #Test
( id, courseId, testDate )
VALUES ( 1, 1, DATEADD(HOUR, -1, GETDATE()) ),
( 2, 1, DATEADD(HOUR, -2, GETDATE()) ),
( 3, 1, DATEADD(HOUR, -3, GETDATE()) ),
( 4, 2, DATEADD(HOUR, -4, GETDATE()) ),
( 5, 2, DATEADD(HOUR, -5, GETDATE()) ),
( 6, 2, DATEADD(HOUR, -6, GETDATE()) ),
( 7, 2, DATEADD(HOUR, -7, GETDATE()) ),
( 8, 2, DATEADD(HOUR, -8, GETDATE()) );
-- returns minumum date for each course - 2 rows
SELECT MIN(t.testDate) AS TestDate ,
t.courseId ,
c.name
FROM #Test t
-- used inner join as could see no reason for left join
INNER JOIN #course c ON t.courseId = c.id
GROUP BY courseId , name;
-- to get course with minimum date - 1 row
SELECT TOP 1
MIN(t.testDate) AS TestDate ,
t.courseId ,
c.name
FROM #Test t
-- used inner join as could see no reason for left join
INNER JOIN #course c ON t.courseId = c.id
GROUP BY t.courseId , c.name
ORDER BY MIN(t.testDate); -- requires order by
-- to get minimum date for a specified course - 1 row
SELECT MIN(t.testDate) AS TestDate ,
t.courseId ,
c.name
FROM #Test t
-- used inner join as could see no reason for left join
INNER JOIN #course c ON t.courseId = c.id
WHERE t.courseId = 1 -- requires you specify a course id
GROUP BY courseId , name;
DROP TABLE #course;
DROP TABLE #Test;
In my understanding, you want to return the minimum date from the entire table with the course details of that day.
Please try the below script
SELECT TOP 1 MIN(t.testDate) OVER (ORDER BY t.testDate) AS TestDate ,
t.courseId ,
c.name
FROM Test t
INNER JOIN course c ON t.courseId = c.id
ORDER BY t.testDate
I need to find the missing months in a table
for the earliest and latest start dates per ID_No. As an example:
create table #InputTable (ID_No int ,OccurMonth datetime)
insert into #InputTable (ID_No,OccurMonth)
select 10, '2007-11-01' Union all
select 10, '2007-12-01' Union all
select 10, '2008-01-01' Union all
select 20, '2009-01-01' Union all
select 20, '2009-02-01' Union all
select 20, '2009-04-01' Union all
select 30, '2010-05-01' Union all
select 30, '2010-08-01' Union all
select 30, '2010-09-01' Union all
select 40, '2008-03-01'
For the above table, the answer should be:
ID_No OccurMonth
----- ----------
20 2009-02-01
30 2010-06-01
30 2010-07-01
The other solutions posted on this site are similar, but:
1) don't include an ID column,
2) don't use the start date/end dates in the data or
3) use cursors, which are forbidden in my environment.
Try this:
;WITH
MonthRange AS
(
SELECT ID_No,
MinMonth = MIN(OccurMonth),
MaxMonth = MAX(OccurMonth)
FROM #InputTable
GROUP BY ID_No
),
AllMonths AS
(
SELECT ID_No,
OccurMonth = MinMonth
FROM MonthRange
UNION ALL
SELECT a.ID_No,
DATEADD(MONTH, 1, a.OccurMonth)
FROM AllMonths a
INNER JOIN MonthRange r ON a.ID_No = r.ID_No
WHERE a.OccurMonth < r.MaxMonth
)
SELECT a.*
FROM AllMonths a
LEFT JOIN #InputTable i ON a.ID_No = i.ID_No
AND a.OccurMonth = i.OccurMonth
WHERE i.ID_No IS NULL
OPTION (MAXRECURSION 0)
AllMonths is a recursive CTE that lists out all months between the min and max month for each ID_no. Then it's only a simple LEFT JOIN to find what month is missing in between.
I have a table which holds a lot of rows (Currently, 500K, expected to rise to 15 Million in the next 3 years). The table holds payments made for a certain event for a specific day. And event can have 1 or many payments for the same day, but the payments on the same day must have different PaymentTypes.
Below is a table variable creation that creates the basic data (Input), and then a hardcoded select which is the expected output. I need to group continuous date ranges for a payment type, for a placement, in one row, with a From and To date, and then when there's a break - no rows, and then for the next date range.
For example:
Placement 1 got payments from the 1st to the 2nd (2 days) for Payment type 5, and then from the 4th until the 6th for the same type. Therefore, two rows. Payment type 1 also got a payment for the 1st to the 3rd for payment type 10. So, that is another row.
DECLARE #Temp TABLE
(
Id INT NOT NULL IDENTITY(1,1),
PlacementId INT NOT NULL,
PaymentTypeId INT NOT NULL,
DateValue DATETIME NOT NULL,
Amount DECIMAL(16,2) NOT NULL
)
INSERT INTO #Temp (PlacementId, PaymentTypeId, DateValue, Amount)
SELECT 1, 5, '01-JAN-2015', 100 UNION
SELECT 1, 5, '02-JAN-2015', 150 UNION
SELECT 1, 5, '04-JAN-2015', 78 UNION
SELECT 1, 5, '05-JAN-2015', 89 UNION
SELECT 1, 5, '06-JAN-2015', 22 UNION
SELECT 1, 10, '01-JAN-2015', 10 UNION
SELECT 1, 10, '02-JAN-2015', 10 UNION
SELECT 1, 10, '03-JAN-2015', 15 UNION
SELECT 2, 5, '01-JAN-2015', 200 UNION
SELECT 2, 5, '02-JAN-2015', 5 UNION
SELECT 2, 5, '03-JAN-2015', 50 UNION
SELECT 3, 5, '01-JAN-2015', 80 UNION
SELECT 4, 5, '07-JAN-2015', 100 UNION
SELECT 4, 5, '08-JAN-2015', 12 UNION
SELECT 4, 5, '12-JAN-2015', 66 UNION
SELECT 4, 5, '14-JAN-2015', 4 UNION
SELECT 5, 10, '08-JAN-2015', 10
SELECT * FROM #Temp
SELECT 1 AS PlacementId, 5 AS PaymentTypeId, '2015-01-01' AS FromDate, '2015-01-02' AS ToDate, 250 AS Amount UNION
SELECT 1, 10, '2015-01-01', '2015-01-03', 35 UNION
SELECT 1, 5, '2015-01-04', '2015-01-06', 189 UNION
SELECT 2, 5, '2015-01-01', '2015-01-03', 255 UNION
SELECT 3, 5, '2015-01-01', '2015-01-01', 80 UNION
SELECT 4, 5, '2015-01-07', '2015-01-08', 112 UNION
SELECT 4, 5, '2015-01-12', '2015-01-12', 66 UNION
SELECT 4, 5, '2015-01-14', '2015-01-14', 4 UNION
SELECT 5, 10, '2015-01-08', '2015-01-08', 10
Note, there is a NCI on PlacementID and PaymentTypeID.
We're doing it at the moment with a crazy load of cursors, and are having extreme speed issues. (The 500K lines takes 4 minutes to process).
Is there an efficient method to achieve the desired output?
This is a problem called Grouping Islands of Contiguous Dates. Read this article be Jeff Moden for more info.
SQL Fiddle
;WITH Cte AS(
SELECT *,
RN = DATEADD(DAY, - ROW_NUMBER() OVER(PARTITION BY PlacementId, PaymentTypeId ORDER BY DateValue), DateValue)
FROM #Temp
)
SELECT
PlacementId,
PaymentTypeId,
FromDate = MIN(DateValue),
ToDate = MAX(DateValue),
Amount = SUM(Amount)
FROM Cte
GROUP BY PlacementId, PaymentTypeId, RN
ORDER BY PlacementId, PaymentTypeId, FromDate