30/60/90 Day Sumation Query - sql

I have the following query. It pulls invoice and time entries, and calculates the EHR (effective hourly rate) for each client, on a per month basis. What I need to get is:
company ,agreement ,lastMonthEHR,60dayEHR,90dayEHR,6MoEHR,12MoEHR,LifeEHR
CompanyA,AgreementB, 30.45, 27.76, 55.22, 30.75, 30.00, 25.00
EDIT:
I apologize for the format. I'll see if I can format it better. The following query returns monthly invoices, with EHR calculated.
SELECT a.AGR_Name, AGR_Type.AGR_Type_Desc, c.Company_Name, ap.InvoiceDate,ap.Revenue,ap.Hours,ap.EHR
FROM AGR_Header AS a INNER JOIN Company AS c ON a.Company_RecID = c.Company_RecID
LEFT JOIN AGR_Type ON a.AGR_Type_RecID = AGR_Type.AGR_Type_RecID
LEFT JOIN (
SELECT ar.AGR_Header_RecID,ar.Revenue,ac.InvoiceDate,ISNULL (ac.Hours, 0) AS Hours,
CASE
WHEN ac.Hours IS NULL THEN (ar.Revenue)
WHEN ac.Hours <= 1 THEN (ar.Revenue)
ELSE CAST (ar.Revenue / NULLIF (ac.Hours,0) as NUMERIC (9,2))
END AS 'EHR'
FROM (
SELECT ah.AGR_Header_RecID,
DATEADD(month,ai.Month_Nbr-1,dateadd(year,ai.Year_Nbr-2000,'2000-01-01')) as InvoiceDate,
CAST (ai.Monthly_Inv_Amt AS NUMERIC (9, 2)) AS Revenue
FROM
dbo.AGR_Header AS ah INNER JOIN
dbo.AGR_Invoice_Amt AS ai ON ah.AGR_Header_RecID = ai.AGR_Header_RecID
GROUP BY ah.AGR_Header_RecID, ai.Month_Nbr, ai.Year_Nbr) as ar
LEFT JOIN (
SELECT ah.AGR_Header_RecID,SUM(te.Hours_Actual) AS Hours, dateadd(month, datediff(month,0,te.Date_start),0) as InvoiceDate
FROM
dbo.Time_Entry AS te INNER JOIN
dbo.AGR_Header AS ah ON te.Agr_Header_RecID = ah.AGR_Header_RecID
WHERE (te.Agr_Header_RecID IS NOT NULL) AND (te.Agr_Hours IS NOT NULL)
GROUP BY ah.AGR_Header_RecID, dateadd(month, datediff(month,0,te.Date_Start),0)) AS ac ON ar.AGR_Header_RecID = ac.AGR_Header_RecID
AND ar.InvoiceDate = ac.InvoiceDate) AS ap ON ap.AGR_Header_RecID = a.AGR_Header_RecID
ORDER BY Company, Agreement, InvoiceDate

The SQL you posted is pretty complicated, but I think it can be simplified. I think the key is to get the invoice data into a format that is similar to the following:
DECLARE #invoice AS TABLE(
[ID] INT,
[CompanyID] INT,
[InvoiceDate] DATE,
[Hours] DECIMAL(9,2),
[Revenue] DECIMAL(9,2))
From there, the calculations are pretty simple, and they can be done using CASE WHEN statements with minimal subselects (I used one just for clarity, but even that one could be eliminated). Here's a full working example for SQL Server:
--Setup table and dummy data
DECLARE #invoice AS TABLE(
[ID] INT,
[CompanyID] INT,
[InvoiceDate] DATE,
[Hours] DECIMAL(9,2),
[Revenue] DECIMAL(9,2))
INSERT INTO #invoice VALUES(1, 1, '2013-01-01', 5, 100)
INSERT INTO #invoice VALUES(2, 1, '2013-02-01', 6, 100)
INSERT INTO #invoice VALUES(3, 1, '2013-03-01', 7, 100)
INSERT INTO #invoice VALUES(4, 1, '2013-04-01', 8, 100)
INSERT INTO #invoice VALUES(5, 1, '2013-05-01', 9, 100)
INSERT INTO #invoice VALUES(6, 1, '2013-06-01', 10, 100)
INSERT INTO #invoice VALUES(7, 1, '2013-07-01', 11, 100)
INSERT INTO #invoice VALUES(8, 1, '2013-08-01', 12, 100)
INSERT INTO #invoice VALUES(9, 2, '2013-04-01', 5, 100)
INSERT INTO #invoice VALUES(10, 2, '2013-05-01', 6, 100)
INSERT INTO #invoice VALUES(11, 2, '2013-06-01', 7, 100)
INSERT INTO #invoice VALUES(12, 2, '2013-07-01', 8, 100)
--Calculate last month start and end dates
--Hardcoded here for brevity
DECLARE #lastMonthStartDate AS DATETIME
DECLARE #lastMonthEndDate AS DATETIME
SET #lastMonthStartDate = '2013-08-01'
SET #lastMonthEndDate = '2013-09-01'
--Calculate EHRs for different time periods
SELECT
A.CompanyID,
CASE WHEN A.LastMonthHours = 0 THEN 0 ELSE A.LastMonthRevenue / A.LastMonthHours END as [LastMonthEHR],
CASE WHEN A.Last60DaysHours = 0 THEN 0 ELSE A.Last60DaysRevenue / A.Last60DaysHours END as [Last60DaysEHR],
CASE WHEN A.Last90DaysHours = 0 THEN 0 ELSE A.Last90DaysRevenue / A.Last90DaysHours END as [Last90DaysEHR]
FROM (
SELECT
[CompanyID],
SUM(CASE WHEN [InvoiceDate] >= #lastMonthStartDate AND [InvoiceDate] < #lastMonthEndDate THEN [Hours] ELSE 0 END) as [LastMonthHours],
SUM(CASE WHEN [InvoiceDate] >= #lastMonthStartDate AND [InvoiceDate] < #lastMonthEndDate THEN [Revenue] ELSE 0 END) as [LastMonthRevenue],
SUM(CASE WHEN [InvoiceDate] >= DATEADD(d, -60, GETDATE()) THEN [Hours] ELSE 0 END) as [Last60DaysHours],
SUM(CASE WHEN [InvoiceDate] >= DATEADD(d, -60, GETDATE()) THEN [Revenue] ELSE 0 END) as [Last60DaysRevenue],
SUM(CASE WHEN [InvoiceDate] >= DATEADD(d, -90, GETDATE()) THEN [Hours] ELSE 0 END) as [Last90DaysHours],
SUM(CASE WHEN [InvoiceDate] >= DATEADD(d, -90, GETDATE()) THEN [Revenue] ELSE 0 END) as [Last90DaysRevenue]
FROM #invoice
GROUP BY [CompanyID]
) A
I think this approach should work for you. I know I had to simply the problem to illustrate the way I would approach a query like this in my answer, so if you need me to expand on it please let me know.

Related

SQL - combined SELECT queries and getting a % output

I am using SQLiteStudio and I am trying to run the following query. However, it isn't returning a value. Can anyone help, please?
SELECT
(SELECT COUNT(t_record.LocationID)
FROM t_record,
t_location
WHERE t_record.LocationID = t_location.LocationID AND
Y_AXIS >= 0 AND
Goal_for = 1)
/
(SELECT COUNT(GoalID)
FROM t_record
WHERE t_record.Goal_for)
* 100
I have been asked to provide some of the data. Below I have included how I created my 3 tables and then some sample data for each section. Hopefully, this is enough but feel free to ask for more.
These are the 3 tables I have created
CREATE TABLE t_location (
LocationID INT PRIMARY KEY,
X_Axis INT NOT NULL,
Y_AXIS INT NOT NULL
);
CREATE TABLE t_method (
MethodID INT PRIMARY KEY,
Body_Part VARCHAR(45) NOT NULL
);
CREATE TABLE t_record (
GoalID INT PRIMARY KEY,
LocationID [INT FORIEIGN KEY] REFERENCES t_location (LocationID),
MethodID [INT FORIEIGN KEY] REFERENCES t_method (MethodID),
Time INT NOT NULL,
Goal_for BOOLEAN NOT NULL
Data for the method table
INSERT INTO t_method
VALUES (1,'Left Foot');
INSERT INTO t_method
VALUES (2,'Right Foot');
INSERT INTO t_method
VALUES (3,'Head');
Data for the location table - only included 3 locations.
INSERT INTO t_location
VALUES (1, 0, -1);
INSERT INTO t_location
VALUES (2, 0, 0);
INSERT INTO t_location
VALUES (3, 0, 1);
Data for the record table, it is very pland but I've varied the location.
INSERT INTO t_record
VALUES (1, 1, 1, 28, 1);
INSERT INTO t_record
VALUES (2, 1, 1, 6, 1);
INSERT INTO t_record
VALUES (3, 2, 1, 28, 1);
INSERT INTO t_record
VALUES (4, 2, 1, 28, 1);
INSERT INTO t_record
VALUES (5, 2, 1, 28, 1);
INSERT INTO t_record
VALUES (6, 2, 1, 28, 1);
INSERT INTO t_record
VALUES (7, 3, 1, 28, 1);
INSERT INTO t_record
VALUES (8, 3, 1, 28, 1);
INSERT INTO t_record
VALUES (9, 3, 1, 28, 1);
INSERT INTO t_record
VALUES (10, 3, 1, 28, 1);
INSERT INTO t_record
VALUES (11, 3, 1, 28, 1);
INSERT INTO t_record
VALUES (12, 3, 1, 28, 1);
INSERT INTO t_record
VALUES (1, 3, 1, 28, 1);
Thanks
Actually you had just a few syntax errors and correcting them plus adding something like * 1.0 to count() would do what you want (otherwise count() results are integer and in SQL server integer / integer would yield an integer which is 0 in your case).
This is your code, slightly modified to work:
SELECT
(SELECT COUNT(t_record.LocationID)
FROM t_record,
t_location
WHERE t_record.LocationID = t_location.LocationID AND
Y_AXIS >= 0 AND
Goal_for = 1) * 1.0
/
(SELECT COUNT(GoalID)
FROM t_record
WHERE t_record.Goal_for=1)
* 100;
(I agree with preventing old style joins etc but was not the problem here).
You could make it a little better:
SELECT
(SELECT COUNT(t_record.LocationID)
FROM t_record,
t_location
WHERE t_record.LocationID = t_location.LocationID AND
Y_AXIS >= 0 AND
Goal_for = 1) * 100.0
/
(SELECT COUNT(GoalID)
FROM t_record
WHERE t_record.Goal_for=1) as Pct;
While the above code works, I think there is a better and cleaner way:
SELECT SUM(case when Y_AXIS >= 0 then 1 end) * 100.0 / COUNT(*)
FROM t_record
inner join t_location ON t_record.LocationID = t_location.LocationID
where Goal_for = 1;
You can also check what is really going on there with adding those fields:
SELECT
SUM(case when Y_AXIS >= 0 then 1 end) as YAXIS_GTE_ZERO,
COUNT(*) as Total,
SUM(case when Y_AXIS >= 0 then 1 end) * 100.0 / COUNT(*) as Pct
FROM t_record
inner join t_location ON t_record.LocationID = t_location.LocationID
where Goal_for = 1;
And here is DBFiddle demo using your example data
EDIT: And this is for YAXIS >= 0 and YAXIS < 0 as in your comment:
SELECT
SUM(CASE WHEN Y_AXIS>=0 THEN 1 END) AS Opp_half_goals
, SUM(CASE WHEN Y_AXIS<0 THEN 1 END) AS Own_half_goals
, COUNT(*) AS Total
, SUM(CASE WHEN Y_AXIS>=0 THEN 1 END)* 100.0 / COUNT(*) AS Opp_half_Pct
, SUM(CASE WHEN Y_AXIS<0 THEN 1 END) * 100.0 / COUNT(*) AS Own_half_Pct
FROM t_record
INNER JOIN t_location ON t_record.LocationID=t_location.LocationID
WHERE Goal_for=1;
I Tried this; using 1.0 instead of 1 in the CASE WHEN expression, to avoid an integer division by integer, which would have resulted in a truncated integer as the result, namely 0:
SELECT
SUM(CASE WHEN y_axis >= 0 THEN 1.0 END)
/ COUNT(*)
* 100
AS result
FROM t_record
JOIN t_location USING(locationid)
;
-- out result
-- out ------------------------
-- out 84.6153846153846153800

How to sum and subtract one column value based on percentage in SQL Server 2008

DECLARE #BalanceTblRec TABLE
(
NetAmount decimal(18, 3),
Percentage int,
[Description] nvarchar(max)
)
DECLARE #BalanceTblPay TABLE
(
NetAmount decimal(18, 3),
Percentage int,
[Description] nvarchar(max)
)
INSERT INTO #BalanceTblRec
VALUES (21, 11, 'ReceiveReceipt'),
(20, 11, 'ReceiveReceipt'),
(20, 10, 'ReceiveReceipt'),
(20, 20, 'ReceiveReceipt'),
(10, 10, 'ReceiveReceipt')
INSERT INTO #BalanceTblPay
VALUES (10, 11, 'PayReceipt'),
(10, 11, 'PayReceipt'),
(10, 2, 'PayReceipt'),
(5, 15, 'PayReceipt'),
(30, 10, 'PayReceipt'),
(20, 10, 'PayReceipt')
;WITH MaPercentage AS
(
SELECT
Percentage,
SUM(NetAmount) AS Net,
'Receive' AS Flag
FROM
#BalanceTblRec
GROUP BY
Percentage
UNION ALL
SELECT
Percentage,
SUM(NetAmount) AS Net,
'Pay' AS Flag
FROM
#BalanceTblPay
GROUP BY
Percentage
)
SELECT * FROM MaPercentage
Now here I want subtract net from net based on falg, receive - pay based on percentage.
Like this:
Per Net Flag
-----------------------
10 30.000 - 50 Receive
11 41.000 - 20 Receive
20 20.000 Receive
2 10.000 Pay
15 5.000 Pay
I think this is what you want:
DECLARE #BalanceTblRec TABLE (NetAmount decimal(18,3), Percentage int, [Description] nvarchar(max))
DECLARE #BalanceTblPay TABLE (NetAmount decimal(18,3), Percentage int, [Description] nvarchar(max))
insert into #BalanceTblRec values (21, 11, 'ReceiveReceipt'),(20, 11, 'ReceiveReceipt'),(20, 10, 'ReceiveReceipt'),(20, 20, 'ReceiveReceipt'), (10, 10, 'ReceiveReceipt')
insert into #BalanceTblPay values (10, 11, 'PayReceipt'),(10, 11, 'PayReceipt'),(10, 2, 'PayReceipt'),(5, 15, 'PayReceipt'),(30, 10, 'PayReceipt') ,(20, 10, 'PayReceipt')
;WITH MaPercentage as (
select Percentage, sum(NetAmount) as Net, 'Receive' as Flag from #BalanceTblRec group by Percentage
union all
select Percentage, -sum(NetAmount) as Net, 'Pay' as Flag from #BalanceTblPay group by Percentage
)
select
Percentage,
abs(sum(net)) as SumNet,
case when sum(net) > 0 then 'Receive'
else 'Pay'
end as Flag
from MaPercentage
group by Percentage
Just changed the sign in the Pays and sum groupping by percentage.
Another way is to FULL JOIN the receivements with the payments.
;WITH RCV AS (
select Percentage, sum(NetAmount) as Net
from #BalanceTblRec
group by Percentage
)
, PAY AS (
select Percentage, sum(NetAmount) as Net
from #BalanceTblPay
group by Percentage
)
SELECT
COALESCE(r.Percentage, p.Percentage) AS Percentage,
ABS(COALESCE(r.Net, 0) - COALESCE(p.Net, 0)) AS Net,
(CASE
WHEN (COALESCE(r.Net, 0) - COALESCE(p.Net, 0)) < 0 THEN 'Pay'
ELSE 'Receive'
END) AS Flag
FROM RCV r
FULL JOIN PAY p ON p.Percentage = r.Percentage

How can I get the next record date from a date and the last record date from a date?

I create table Appointments with this structure:
CREATE TABLE Appointments
(
[Id] bigint,
[Name] varchar(250),
[DateInit] date
);
INSERT INTO Appointments ([Id], [Name], [DateInit])
values
(1000, 'Lorena', '03/06/2016'),
(1000, 'Lorena', '01/06/2016'),
(1000, 'Lorena', '08/06/2016'),
(1000, 'Lorena', '10/06/2016'),
(1000, 'Lorena', '02/06/2016'),
(1000, 'Lorena', '20/06/2016'),
(7000, 'Susan', '04/06/2016'),
(7000, 'Susan', '08/06/2016'),
(7000, 'Susan', '09/06/2016'),
(7000, 'Susan', '01/06/2016');
This is the final result:
I need to get the result for the next day and the day before, for example if today is '03/06/2016' I need to get result for the last appointment inserted in the table from today and the next appointment inserted in the table from today, the result I need is something like this:
Name Last Visit Next Visit
----- ---------- -----------
Lorena 2016-06-02 2016-06-08
Susan 2016-06-01 2016-06-04
How can I get this result?
Thanks
Do a GROUP BY, use case expressions to pick max previous appointment, and min future appointment:
select name,
max(case when DateInit < CONVERT(DATE,GETDATE()) then DateInit end) as LastVisit,
min(case when DateInit > CONVERT(DATE,GETDATE()) then DateInit end) as NextVisit
from Appointments
group by name
I'd do this as joins to the previous and next visit, something like this;
SELECT DISTINCT
a.ID
,a.NAME
,l.LastVisit
,n.NextVisit
FROM Appointments a
LEFT JOIN (
SELECT ID
,MIN(DateInit) NextVisit
FROM Appointments
WHERE DateInit > GETDATE()
GROUP BY ID
) n ON a.ID = n.ID
LEFT JOIN (
SELECT ID
,MAX(DateInit) LastVisit
FROM Appointments
WHERE DateInit < GETDATE()
GROUP BY ID
) l ON a.ID = l.ID
DECLARE #Appointments TABLE
(
[Id] bigint,
[Name] varchar(250),
[DateInit] date
);
INSERT INTO #Appointments ([Id], [Name], [DateInit])
values
(1000, 'Lorena','2016/06/03'),
(1000, 'Lorena','2016/06/01'),
(1000, 'Lorena','2016/06/08'),
(1000, 'Lorena','2016/06/10'),
(1000, 'Lorena','2016/06/02'),
(1000, 'Lorena','2016/06/20'),
(7000, 'Susan', '2016/06/04'),
(7000, 'Susan', '2016/06/08'),
(7000, 'Susan', '2016/06/09'),
(7000, 'Susan', '2016/06/01');
DECLARE #Today DATE = GETDATE();
WITH CTE
AS (
SELECT A.NAME
,ROW_NUMBER() OVER (
PARTITION BY ID ORDER BY ID
) RN
,(
SELECT TOP 1 DateInit
FROM #Appointments B
WHERE B.ID = A.ID
AND DateInit < #TODAY
ORDER BY DateInit DESC
) [Last Visit]
,(
SELECT TOP 1 DateInit
FROM #Appointments B
WHERE B.ID = A.ID
AND DateInit > #TODAY
ORDER BY DateInit
) [Next Visit]
FROM #Appointments A
--GROUP BY ID
)
SELECT C.NAME
,C.[Last Visit]
,C.[Next Visit]
,RN
FROM CTE C
WHERE RN = 1

Summing up the records as per given conditions

I have a table like below, What I need that for any particular fund and up to any particular date logic will sum the amount value. Let say I need the sum for 3 dates as 01/28/2015,03/30/2015 and 04/01/2015. Then logic will check for up to first date how many records are there in table . If it found more than one record then it'll sum the amount value. Then for next date it'll sum up to the next date but from the previous date it had summed up.
Id Fund Date Amount
1 A 01/20/2015 250
2 A 02/28/2015 300
3 A 03/20/2015 400
4 A 03/30/2015 200
5 B 04/01/2015 500
6 B 04/01/2015 600
I want result to be like below
Id Fund Date SumOfAmount
1 A 02/28/2015 550
2 A 03/30/2015 600
3 B 04/01/2015 1100
Based on your question, it seems that you want to select a set of dates, and then for each fund and selected date, get the sum of the fund amounts from the selected date to the previous selected date. Here is the result set I think you should be expecting:
Fund Date SumOfAmount
A 2015-02-28 550.00
A 2015-03-30 600.00
B 2015-04-01 1100.00
Here is the code to produce this output:
DECLARE #Dates TABLE
(
SelectedDate DATE PRIMARY KEY
)
INSERT INTO #Dates
VALUES
('02/28/2015')
,('03/30/2015')
,('04/01/2015')
DECLARE #FundAmounts TABLE
(
Id INT PRIMARY KEY
,Fund VARCHAR(5)
,Date DATE
,Amount MONEY
);
INSERT INTO #FundAmounts
VALUES
(1, 'A', '01/20/2015', 250)
,(2, 'A', '02/28/2015', 300)
,(3, 'A', '03/20/2015', 400)
,(4, 'A', '03/30/2015', 200)
,(5, 'B', '04/01/2015', 500)
,(6, 'B', '04/01/2015', 600);
SELECT
F.Fund
,D.SelectedDate AS Date
,SUM(F.Amount) AS SumOfAmount
FROM
(
SELECT
SelectedDate
,LAG(SelectedDate,1,'1/1/1900') OVER (ORDER BY SelectedDate ASC) AS PreviousDate
FROM #Dates
) D
JOIN
#FundAmounts F
ON
F.Date BETWEEN DATEADD(DAY,1,D.PreviousDate) AND D.SelectedDate
GROUP BY
D.SelectedDate
,F.Fund
EDIT: Here is alternative to the LAG function for this example:
FROM
(
SELECT
SelectedDate
,ISNULL((SELECT TOP 1 SelectedDate FROM #Dates WHERE SelectedDate < Dates.SelectedDate ORDER BY SelectedDate DESC),'1/1/1900') AS PreviousDate
FROM #Dates Dates
) D
If i change your incorrect sample data to ...
CREATE TABLE TableName
([Id] int, [Fund] varchar(1), [Date] datetime, [Amount] int)
;
INSERT INTO TableName
([Id], [Fund], [Date], [Amount])
VALUES
(1, 'A', '2015-01-28 00:00:00', 250),
(2, 'A', '2015-01-28 00:00:00', 300),
(3, 'A', '2015-03-30 00:00:00', 400),
(4, 'A', '2015-03-30 00:00:00', 200),
(5, 'B', '2015-04-01 00:00:00', 500),
(6, 'B', '2015-04-01 00:00:00', 600)
;
this query using GROUP BY works:
SELECT MIN(Id) AS Id,
MIN(Fund) AS Fund,
[Date],
SUM(Amount) AS SumOfAmount
FROM dbo.TableName t
WHERE [Date] IN ('01/28/2015','03/30/2015','04/01/2015')
GROUP BY [Date]
Demo
Initially i have used Row_number and month function to pick max date of every month and in 2nd cte i did sum of amounts and joined them..may be this result set matches your out put
declare #t table (Id int,Fund Varchar(1),Dated date,amount int)
insert into #t (id,Fund,dated,amount) values (1,'A','01/20/2015',250),
(2,'A','01/28/2015',300),
(3,'A','03/20/2015',400),
(4,'A','03/30/2015',200),
(5,'B','04/01/2015',600),
(6,'B','04/01/2015',500)
;with cte as (
select ID,Fund,Amount,Dated,ROW_NUMBER() OVER
(PARTITION BY DATEDIFF(MONTH, '20000101', dated)ORDER BY dated desc)AS RN from #t
group by ID,Fund,DATED,Amount
),
CTE2 AS
(select SUM(amount)Amt from #t
GROUP BY MONTH(dated))
,CTE3 AS
(Select Amt,ROW_NUMBER()OVER (ORDER BY amt)R from cte2)
,CTE4 AS
(
Select DISTINCT C.ID As ID,
C.Fund As Fund,
C.Dated As Dated
,ROW_NUMBER()OVER (PARTITION BY RN ORDER BY (SELECT NULL))R
from cte C INNER JOIN CTE3 CC ON c.RN = CC.R
Where C.RN = 1
GROUP BY C.ID,C.Fund,C.RN,C.Dated )
select C.R,C.Fund,C.Dated,cc.Amt from CTE4 C INNER JOIN CTE3 CC
ON c.R = cc.R
declare #TableName table([Id] int, [Fund] varchar(1), [Date] datetime, [Amount] int)
declare #Sample table([SampleDate] datetime)
INSERT INTO #TableName
([Id], [Fund], [Date], [Amount])
VALUES
(1, 'A', '20150120 00:00:00', 250),
(2, 'A', '20150128 00:00:00', 300),
(3, 'A', '20150320 00:00:00', 400),
(4, 'A', '20150330 00:00:00', 200),
(5, 'B', '20150401 00:00:00', 500),
(6, 'B', '20150401 00:00:00', 600)
INSERT INTO #Sample ([SampleDate])
values ('20150128 00:00:00'), ('20150330 00:00:00'), ('20150401 00:00:00')
-- select * from #TableName
-- select * from #Sample
;WITH groups AS (
SELECT [Fund], [Date], [AMOUNT], MIN([SampleDate]) [SampleDate] FROM #TableName
JOIN #Sample ON [Date] <= [SampleDate]
GROUP BY [Fund], [Date], [AMOUNT])
SELECT [Fund], [SampleDate], SUM([AMOUNT]) FROM groups
GROUP BY [Fund], [SampleDate]
Explanation:
The CTE groups finds the earliest SampleDate which is later than (or equals to) your
data's date and enriches your data accordingly, thus giving them the group to be summed up in.
After that, you can group on the derived date.

Query by user, by month including a default value for not existing records

Ok, my title looks a little bit weird but I could find a better way to title my question.
My current SQL statement looks like this:
SELECT
ActionBy,
DATEADD(MONTH, DATEDIFF(MONTH, 0, ActionCreateDate), 0) AS 'dateStart',
CONVERT(CHAR(3), (DATEADD(MONTH, DATEDIFF(MONTH, 0, ActionCreateDate), 0)), 100) AS 'Month' ,
YEAR(ActionCreateDate) AS 'YEAR',
SUM([TimeTakenToComplete]) AS TOTAL
FROM
[myTable]
WHERE
[TimeTakenToComplete] IS NOT NULL
AND DeleteDate IS NULL
AND ActionBy IS NOT NULL
GROUP BY
ActionBy, DATEADD(MONTH, DATEDIFF(MONTH, 0, ActionCreateDate), 0),
YEAR(ActionCreateDate)
ORDER BY
DATEADD(MONTH, DATEDIFF(MONTH, 0, ActionCreateDate), 0) ASC
The end result looks something like this:
user1 , 2013-06-01 00:00:00.000 , Jun , 2013 , 1000
user2 , 2013-06-01 00:00:00.000 , Jun , 2013 , 998
user3 , 2013-06-01 00:00:00.000 , Jun , 2013 , 600
user1 , 2013-07-01 00:00:00.000 , Jul , 2013 , 1110
user3 , 2013-07-01 00:00:00.000 , Jul , 2013 , 2330
My problem is that I want to have a record for all users, for all months with a default value of 0.
So my desired result would look something like this:
user1 , 2013-06-01 00:00:00.000 , Jun , 2013 , 1000
user2 , 2013-06-01 00:00:00.000 , Jun , 2013 , 998
user3 , 2013-06-01 00:00:00.000 , Jun , 2013 , 600
user1 , 2013-07-01 00:00:00.000 , Jul , 2013 , 1110
user2 , 2013-07-01 00:00:00.000 , Jul , 2013 , 0
user3 , 2013-07-01 00:00:00.000 , Jul , 2013 , 2330
Is there any way that I can achieve this result or should I go and solve this issue programmatically?
Check this solution out. It gives the desired output.
Also I have used table variables to simulate your need. I have also used #Users table to consider that you have a users table. If you do not want to use the users table then you can use mytable as well but there should be atleast 1 entry of the missing user for some other month.
--Simulation of your myTable
DECLARE #myTable TABLE
(
ActionBy VARCHAR(10),
ActionCreateDate DATETIME,
TimeTakenToComplete INT
)
--Simulation of users table
DECLARE #Users TABLE
(
ActionBy VARCHAR(10)
)
--Dummy data for testing
INSERT INTO #myTable VALUES ('user1' , '2013-06-01', 100);
INSERT INTO #myTable VALUES ('user1' , '2013-06-01', 100);
INSERT INTO #myTable VALUES ('user1' , '2013-06-01', 100);
INSERT INTO #myTable VALUES ('user1' , '2013-06-01', 100);
INSERT INTO #myTable VALUES ('user1' , '2013-06-01', 100);
INSERT INTO #myTable VALUES ('user2' , '2013-06-01', 100);
INSERT INTO #myTable VALUES ('user2' , '2013-06-01', 100);
INSERT INTO #myTable VALUES ('user2' , '2013-06-01', 100);
INSERT INTO #myTable VALUES ('user2' , '2013-06-01', 100);
INSERT INTO #myTable VALUES ('user2' , '2013-06-01', 100);
INSERT INTO #myTable VALUES ('user2' , '2013-06-01', 100);
INSERT INTO #myTable VALUES ('user3' , '2013-06-01', 100);
INSERT INTO #myTable VALUES ('user3' , '2013-06-01', 100);
INSERT INTO #myTable VALUES ('user3' , '2013-06-01', 100);
INSERT INTO #myTable VALUES ('user3' , '2013-06-01', 100);
INSERT INTO #myTable VALUES ('user1' , '2013-07-01', 100);
INSERT INTO #myTable VALUES ('user1' , '2013-07-01', 100);
INSERT INTO #myTable VALUES ('user1' , '2013-07-01', 100);
INSERT INTO #myTable VALUES ('user3' , '2013-07-01', 100);
INSERT INTO #myTable VALUES ('user3' , '2013-07-01', 100);
INSERT INTO #myTable VALUES ('user3' , '2013-07-01', 100);
INSERT INTO #myTable VALUES ('user3' , '2013-07-01', 100);
INSERT INTO #myTable VALUES ('user3' , '2013-07-01', 100);
INSERT INTO #Users VALUES ('user1');
INSERT INTO #Users VALUES ('user2');
INSERT INTO #Users VALUES ('user3');
--Actual solution starts from here
; WITH MYCTE AS
(SELECT
ActionBy,
DATEADD(MONTH, DATEDIFF(MONTH, 0, ActionCreateDate), 0) AS 'dateStart',
CONVERT(CHAR(3), (DATEADD(MONTH, DATEDIFF(MONTH, 0, ActionCreateDate), 0)), 100) AS 'Month' ,
YEAR(ActionCreateDate) AS 'YEAR',
SUM([TimeTakenToComplete]) AS TOTAL
FROM
#myTable
WHERE
[TimeTakenToComplete] IS NOT NULL
--AND DeleteDate IS NULL --uncomment this on using with your code
AND ActionBy IS NOT NULL
GROUP BY
ActionBy, DATEADD(MONTH, DATEDIFF(MONTH, 0, ActionCreateDate), 0),
YEAR(ActionCreateDate)
)
SELECT
ISNULL(M1.ActionBy, L1.ActionBy) ,
ISNULL(M1.dateStart, L1.dateStart),
ISNULL(M1.Month, L1.Month),
ISNULL(M1.YEAR, L1.YEAR),
ISNULL(M1.TOTAL, 0)
FROM MYCTE M1
RIGHT OUTER JOIN
(
SELECT * FROM
(
(SELECT DISTINCT [Month], dateStart, YEAR FROM MYCTE) m
CROSS JOIN (SELECT ActionBy FROM #Users) U
)
)L1
ON M1.[Month] = L1.[Month]
AND M1.ActionBy = L1.ActionBy
Hope this helps
The trick is to start with cross-joining all distinct values for users with all distinct values for months in the table, and then left-join the result to the table:
SELECT
c.ActionBy, m.mon, SUM(h.TimeTakenToComplete) AS TOTAL
FROM
(select distinct ActionBy from myTable) c
cross join (select distinct dateadd(month, datediff(month, 0, ActionCreateDate), 0) as mon from myTable) m
left outer join myTable h
on h.ActionBy=c.ActionBy and dateadd(month, datediff(month, 0, ActionCreateDate), 0)=m.mon
WHERE
TimeTakenToComplete IS NOT NULL
AND DeleteDate IS NULL
AND ActionBy IS NOT NULL
GROUP BY
c.ActionBy, m.mon
ORDER BY
m.mon ASC
Replace (select distinct ActionBy from myTable) with select on users table if any, and (select distinct dateadd(month, datediff(month, 0, ActionCreateDate), 0) as mon from myTable) with a select on the Calendar table, if you have one.
actually I was waiting for #Hamlet's solution..
but here is what I have in mind (I haven't tried to run it)..
and if there isn't any data in a month of any user, there will be no data at all (for that particular month)..
SELECT a.username,
b.dateStart,
b.Month ,
b.YEAR,
ISNULL(result.TOTAL,0)
FROM
(SELECT username FROM tableUser) a CROSS JOIN
(SELECT DISTINCT dateStart, CONVERT(CHAR(3), dateStart, 100) AS 'Month', 'YEAR' FROM result) b
LEFT JOIN result
ON a.username=result.ActionBy AND b.dateStart=result.dateStart
note: I called the table from your original query as result, and in your original query, you can just have this selection:
SELECT
ActionBy,
DATEADD(MONTH, DATEDIFF(MONTH, 0, ActionCreateDate), 0) AS 'dateStart',
YEAR(ActionCreateDate) AS 'YEAR',
SUM([TimeTakenToComplete]) AS TOTAL
...