I have this table and sample data and I want to calculate the opening and closing balance. I want it to be in 6 column
i.e CreditOpening, DebitOpening, Credit, Debit, CreditClosing, DebitClosing
Here is the table structure and sample data
DROP TABLE Transactions;
CREATE TABLE Transactions
(
ID INT,
COATitle VARCHAR(35),
ConfigurationCode INT,
DebitAmount NUMERIC,
CreditAmount NUMERIC,
TransactionDate Date
)
INSERT INTO Transactions VALUES (1, 'Sales', 24, '2400', NULL, '2018-08-24');
INSERT INTO Transactions VALUES (2, 'Items', 24, NULL, '1200', '2018-08-24');
INSERT INTO Transactions VALUES (3, 'Bank', 24, NULL, '1200', '2018-08-24');
INSERT INTO Transactions VALUES (4, 'Meezan', 24, '1500', NULL, '2018-08-25');
INSERT INTO Transactions VALUES (5, 'Items', 24, NULL, '1500', '2018-08-25');
INSERT INTO Transactions VALUES (6, 'Bank', 24, NULL, '1200', '2018-08-26');
INSERT INTO Transactions VALUES (7, 'Sales', 24, '5400', NULL, '2018-08-26');
INSERT INTO Transactions VALUES (8, 'Items', 24, NULL, '1200', '2018-08-26');
INSERT INTO Transactions VALUES (9, 'Bank', 24, NULL, '3000', '2018-08-26');
I have this query and it's output as below:
;WITH CTE AS (
SELECT *
FROM
Transactions
)
SELECT
COATitle, SUM([D].[DebitAmount]) DrAmount, SUM([D].[CreditAmount]) CrAmount
FROM(
SELECT *,
SUM(ISNULL(NULLIF(DebitAmount, 0), 0)-ISNULL(NULLIF(CreditAmount, 0), 0)) OVER (PARTITION BY CONFIGURATIONCODE ORDER BY ID) as Amount
FROM CTE
WHERE [TransactionDate] BETWEEN CAST('Aug 25 2018 11:21AM' AS DATE) AND CAST('Aug 25 2018 11:21AM' AS DATE)
)D
GROUP BY COATitle
OutPut :
COATitle DrAmount CrAmount
Items NULL 1500
Meezan 1500 NULL
Now the data should look like this
COATitle OpeningDebit OpeningDebit DrAmount CrAmount ClosingDebit ClosingCredit
Bank 0 0 NULL 1200 0 1200
Items 0 0 NULL 1200 0 1200
Sales 0 0 2400 NULL 2400 0
But once I run the query between dates 25 and 26 the result should be something like this
COATitle OpeningDebit OpeningCredit DrAmount CrAmount ClosingDebit ClosingCredit
Bank 0 1200 NULL 4200 0 5400
Items 0 1200 NULL 2700 0 3900
Sales 0 0 5400 NULL 7800 0
Meezan 0 0 1500 NULL 1500 0
Meezan will not have opening balance as there was no in previous date. Now in case if there is Debit amount is given for any COATitle which was given Credit in previous dates it will subtract Credit From Debit.
You don't need the CTE in this case. It is redundant.
You don't need to convert the date in string to date data type. Just specify the date in ISO format YYYY-MM-DD will do.
I am may be wrong but your expected data does not matches with the sample data.
Note : i excluded the ConfigurationCode in the query as i am not sure how that column play a part in your requirement.
DECLARE #Date_Fr DATE = '2018-08-25',
#Date_To DATE = '2018-08-25'
SELECT t.COATitle,
OpeningDebit = ISNULL(o.OpeningDebit, 0),
OpeningCredit = ISNULL(o.OpeningCredit, 0),
t.DrAmount, t.CrAmount,
ClosingDebit = ISNULL(o.OpeningDebit, 0) + t.DrAmount,
ClosingCredit = ISNULL(o.OpeningCredit, 0) + t.CrAmount
FROM (
SELECT COATitle,
DrAmount = SUM(CASE WHEN [TransactionDate] >= #Date_Fr THEN DebitAmount ELSE 0 END),
CrAmount = SUM(CASE WHEN [TransactionDate] >= #Date_Fr THEN CreditAmount ELSE 0 END)
FROM Transactions t
WHERE [TransactionDate] <= #Date_To
GROUP BY COATitle
) t
OUTER APPLY
(
SELECT OpeningDebit = SUM(DebitAmount), OpeningCredit = SUM(CreditAmount)
FROM Transactions x
WHERE x.COATitle = t.COATitle
AND x.[TransactionDate] < #Date_Fr
) o
WHERE o.OpeningDebit IS NOT NULL
OR o.OpeningCredit iS NOT NULL
This may help. I put additional check, because I don't know if situation when opening balance is debit and closing is credit for example, is valid.
DECLARE
#from date = '25-08-2018',
#to date = '26-08-2018'
;
WITH Items (COATitle) AS (
SELECT DISTINCT COATitle
FROM dbo.[Transactions]
), OpeningBalance (COATitle, OpeningAmount) AS (
SELECT COATitle, SUM(ISNULL(DebitAmount, 0)) - SUM(ISNULL(CreditAmount, 0))
FROM dbo.[Transactions]
WHERE TransactionDate < #from
GROUP BY COATitle
), DebitCredit(COATitle, DebitAmount, CreditAmount) AS (
SELECT COATitle, SUM(ISNULL(DebitAmount, 0)), SUM(ISNULL(CreditAmount, 0))
FROM dbo.[Transactions]
WHERE (#from <= TransactionDate) AND (TransactionDate <= #to)
GROUP BY COATitle
)
SELECT
i.COATitle,
OpeningDebitAmount = (CASE WHEN SUM(ISNULL(ob.OpeningAmount, 0)) < 0 THEN 0 ELSE SUM(ISNULL(ob.OpeningAmount, 0)) END),
OpeningCreditAmount = (CASE WHEN SUM(ISNULL(ob.OpeningAmount, 0)) < 0 THEN -SUM(ISNULL(ob.OpeningAmount, 0)) ELSE 0 END),
DebitAmount = SUM(ISNULL(dc.DebitAmount, 0)),
CreditAmount = SUM(ISNULL(dc.CreditAmount, 0)),
ClosingDebitAmount = (CASE WHEN SUM(ISNULL(ob.OpeningAmount,0)+ISNULL(dc.DebitAmount,0)-ISNULL(dc.CreditAmount, 0)) < 0 THEN 0 ELSE SUM(ISNULL(ob.OpeningAmount,0)+ISNULL(dc.DebitAmount,0)-ISNULL(dc.CreditAmount, 0)) END),
ClosingCreditAmount = (CASE WHEN SUM(ISNULL(ob.OpeningAmount,0)+ISNULL(dc.DebitAmount,0)-ISNULL(dc.CreditAmount, 0)) < 0 THEN -SUM(ISNULL(ob.OpeningAmount,0)+ISNULL(dc.DebitAmount,0)-ISNULL(dc.CreditAmount, 0)) ELSE 0 END)
FROM Items i
LEFT JOIN OpeningBalance ob ON (i.COATitle = ob.COATitle)
LEFT JOIN DebitCredit dc ON (i.COATitle = dc.COATitle)
GROUP BY i.COATitle
Here's a possible solution for you. First, sample data:
declare #Transactions table
(
ID int,
COATitle varchar(35),
ConfigurationCode int,
DebitAmount money,
CreditAmount money,
TransactionDate date
);
insert #Transactions values
(1, 'Sales', 24, 2400, NULL, '20180824'),
(2, 'Items', 24, NULL, 1200, '20180824'),
(3, 'Bank', 24, NULL, 1200, '20180824'),
(4, 'Meezan', 24, 1500, NULL, '20180825'),
(5, 'Items', 24, NULL, 1500, '20180825'),
(6, 'Bank', 24, NULL, 1200, '20180826'),
(7, 'Sales', 24, 5400, NULL, '20180826'),
(8, 'Items', 24, NULL, 1200, '20180826'),
(9, 'Bank', 24, NULL, 3000, '20180826');
A couple of things to note here. There's no need to use quotes to delimit numeric literals as you've done in the original question, and I would generally advise against using the numeric data type without specifying the precision and scale explicitly. I've chosen the money type for debit and credit amounts instead.
Next, I'll use a couple of local variables to control the operation of the query. For your first test case we'll use:
declare #BeginDate date = '20180801';
declare #EndDate date = '20180824';
Here's the implementation:
with RawDataCTE as
(
select
T.COATitle,
OpeningDebit = coalesce(sum(case when T.TransactionDate < #BeginDate then T.DebitAmount end), 0),
OpeningCredit = coalesce(sum(case when T.TransactionDate < #BeginDate then T.CreditAmount end), 0),
DrAmount = sum(case when T.TransactionDate between #BeginDate and #EndDate then T.DebitAmount end),
CrAmount = sum(case when T.TransactionDate between #BeginDate and #EndDate then T.CreditAmount end)
from
#Transactions T
group by
T.COATitle
)
select
R.COATitle,
R.OpeningDebit,
R.OpeningCredit,
R.DrAmount,
R.CrAmount,
ClosingDebit = R.OpeningDebit + coalesce(R.DrAmount, 0),
ClosingCredit = R.OpeningCredit + coalesce(R.CrAmount, 0)
from
RawDataCTE R
where
R.OpeningDebit > 0 or
R.OpeningCredit > 0 or
R.DrAmount > 0 or
R.CrAmount > 0;
The CTE groups everything by COATitle, and as I've assumed from your desired results, produces non-null results for the opening balances but may produce null results for the sum of debits and credits that fall within the desired time frame. The CTE doesn't try to decide which COATitle records should be included or excluded because we need to have the data aggregated before making that decision.
The query outside the CTE builds the closing balances from the CTE data and omits any COATitle for which there are no opening balances and no entries made during the time period (and therefore no closing balances). When I run the query for 2018-08-01 through 2018-08-24, the result is:
And for 2018-08-25 through 2018-08-26, the result set is:
Technically you could do this without a CTE if you wanted; you'd just have to use a HAVING clause instead of a WHERE to determine which accounts are to be included, and you'd have to define ClosingDebit and ClosingCredit independently rather than as sums of the other fields in the result set. For instance, this will work:
select
T.COATitle,
OpeningDebit = coalesce(sum(case when T.TransactionDate < #BeginDate then T.DebitAmount end), 0),
OpeningCredit = coalesce(sum(case when T.TransactionDate < #BeginDate then T.CreditAmount end), 0),
DrAmount = sum(case when T.TransactionDate between #BeginDate and #EndDate then T.DebitAmount end),
CrAmount = sum(case when T.TransactionDate between #BeginDate and #EndDate then T.CreditAmount end),
ClosingDebit = coalesce(sum(case when T.TransactionDate <= #EndDate then T.DebitAmount end), 0),
ClosingCredit = coalesce(sum(case when T.TransactionDate <= #EndDate then T.CreditAmount end), 0)
from
#Transactions T
group by
T.COATitle
having
sum(case when T.TransactionDate <= #EndDate and (T.DebitAmount > 0 or T.CreditAmount > 0) then 1 else 0 end) > 0;
I find the CTE version a little easier to read and understand, but your mileage may vary.
Related
I found a warehouse aging inventory example online (see modified code below).
Everything works fine if the entry type for purchase (0) is positive and for sales (1) is negative. But if the values are inverse (because of cancellation) then the results will be wrong.
Example: There are four entries, three of them are purchase entries but as you can see the second one has been canceled that's why the quantity is negative.
The total sum of column RemainingQty must be 0 in that case but result is 1699.
What do I have to change in my SQL query?
Thanks for any advice.
DECLARE #ItemLedgerEntry TABLE
(
id INT IDENTITY(1, 1) NOT NULL PRIMARY KEY ,
ItemNo INT NOT NULL, --references another item
Qty FLOAT NOT NULL, --quantity
EntryType INT NOT NULL, --type=0 bought, type=1 sold
PostingDate DATETIME NOT NULL -- transaction date
);
INSERT #ItemLedgerEntry
( ItemNo, qty, EntryType, PostingDate )
VALUES ( 1999, 1700, 0, '10-06-2021'),
( 1999, -1700, 0, '29-06-2021'),
( 1999, 1, 0, '03-08-2021'),
( 1999, - 1, 1, '09-08-2021');
WITH Sold
AS ( SELECT IT.[ItemNo] ,
SUM(IT.Qty) AS TotalSoldQty
FROM #ItemLedgerEntry IT
WHERE It.[EntryType] =1
GROUP BY ItemNo
),
Bought
AS ( SELECT IT.* ,
(
SELECT SUM(RS.Qty)
FROM #ItemLedgerEntry RS
WHERE RS.[EntryType] =0 AND RS.[ItemNo] = IT.[ItemNo] AND RS.[PostingDate] <= IT.[PostingDate]
) AS RunningBoughtQty
FROM #ItemLedgerEntry IT
WHERE IT.[EntryType] = 0
)
SELECT
B.[ItemNo],
B.[PostingDate],
B.[EntryType],
S.TotalSoldQty,
B.RunningBoughtQty,
B.RunningBoughtQty + S.TotalSoldQty AS RunningDifferenceQty,
CASE WHEN (B.RunningBoughtQty) + (S.TotalSoldQty) <0
THEN 0
ELSE B.RunningBoughtQty + S.TotalSoldQty
END AS RunningRemainingQty,
CASE WHEN B.RunningBoughtQty + S.TotalSoldQty < 0 THEN 0
WHEN B.RunningBoughtQty + S.TotalSoldQty > B.Qty THEN B.Qty
ELSE B.RunningBoughtQty + S.TotalSoldQty
END AS RemainingQty
FROM Bought B
inner JOIN Sold S ON B.[ItemNo] = S.[ItemNo]
Sql Fiddle example
I have this table structure:
CREATE TABLE IF NOT EXISTS `client` (
`id` int(6) unsigned NOT NULL,
`name` varchar(200),
`balance` decimal NOT NULL,
PRIMARY KEY (`id`)
) DEFAULT CHARSET=utf8;
INSERT INTO `client` (`id`, `name`, `balance`) VALUES
('1', 'Pepito', '500');
CREATE TABLE IF NOT EXISTS `balance_Movements` (
`id` int(6) unsigned NOT NULL,
`clientId` int(6),
`movementType` varchar(200),
`import` decimal NOT NULL,
`dateMovement` datetime,
PRIMARY KEY (`id`)
) DEFAULT CHARSET=utf8;
INSERT INTO `balance_Movements` (`id`, `clientid`, `movementType`, `import`, `dateMovement`) VALUES
('1', '1', 'payment', '50', '2018/05/11'),
('2', '1', 'refund', '25.05', '2018/05/10'),
('3', '1', 'refund', '60', '2018/04/06'),
('4', '1', 'payment', '100', '2018/04/03');
Client has at the start : 500€ --> so the declare variable will be like this:
declare #total_balance as decimal;
set #total_balance = (select balance from client where id = 1);
Result
------
500
I need to change after the result of #total_balance, taking the value for the last row:
Example:
Table_balance_Movements
------------------------
Total
-----
450
475.05
535.05
435.05
Explanation:
450 --> The client start with 500, so if the type of movement is payment I need to substract the balance of client to the import movement, and save the data in #total_balance to use it later = 500 - 50 = 450
475.05 --> I get the value for #total_balance and sum 25.05 because in this line the type of movement is refund = 450 + 25.05 = 475.05
535.05 --> the same thing, I get the value from the #total_balance variable and see what type of movement is and substract or sum the import = 475.05 + 60 = 535.05
435.05 --> 535.05 - 100 = 435.05
I want to do something similar to this concept:
declare #total_balance as decimal;
set #total_balance = (select balance from client where id = 1);
select (case when movementType = 'payment' then (#total_balance = #total_balance - import)
when movementType = 'refund' then (#total_balance = #total_balance + import) end) as total
from balance_Movements;
it would be possible? thanks
You can solve this with the cumulative sum window functions. The following will work in SQL Server and MySQL 8+
select c.*,
(c.balance +
sum(case when bm.movement_type = 'payment' then - import
when bm.movement_type = 'refund' then import
end) over (partition by c.id order by bm.datemovement)
) as net_balance
from client c join
balance_movements bm
on bm.clientid = c.id
I think the simplest way is using SUM with condition aggregate function window function to get accumulate the add with balance
SELECT c.balance +SUM(CASE WHEN movementType = 'payment' THEN - import
WHEN movementType = 'refund' THEN import
ELSE 0 END
) OVER(ORDER BY b.id) Total
FROM client c
JOIN balance_Movements b on c.id = b.clientid
But if your dbms didn't support window function, you can try to use correctly subquery in select
SELECT c.balance +
(
SELECT SUM(CASE WHEN movementType = 'payment' THEN - import
WHEN movementType = 'refund' THEN import
ELSE 0 END)
FROM balance_Movements bb
WHERE bb.id <= b.id and b.clientid = bb.clientid
) Total
FROM client c
JOIN balance_Movements b on c.id = b.clientid
sqlfiddle
You could just add the sum of refunds and subtract the sum of payments like this:
SELECT 500 +
(SELECT SUM(import) FROM balance_Movements WHERE movementType = 'refund')
- (SELECT SUM(import) FROM balance_Movements WHERE movementType = 'payment') AS total
Result: 435.05
Give this a shot...
IF OBJECT_ID('tempdb..#balance_Movements', 'U') IS NOT NULL
BEGIN DROP TABLE #balance_Movements; END;
CREATE TABLE #balance_Movements (
id INT NOT NULL,
clientId INT,
movementType VARCHAR (200),
import DECIMAL (9, 2) NOT NULL,
dateMovement DATETIME,
PRIMARY KEY (id)
);
INSERT INTO #balance_Movements (id, clientid, movementType, import, dateMovement) VALUES
('1', '1', 'payment', '50', '2018/05/11'),
('2', '1', 'refund', '25.05', '2018/05/10'),
('3', '1', 'refund', '60', '2018/04/06'),
('4', '1', 'payment', '100', '2018/04/03');
--=============================================================
DECLARE #_start DECIMAL(9,2) = 500;
SELECT
*,
running_total = #_start - SUM(CASE WHEN bm.movementType = 'refund' THEN -1 * bm.import ELSE bm.import END) OVER (PARTITION BY bm.clientId ORDER BY bm.dateMovement desc)
FROM
#balance_Movements bm;
Results...
id clientId movementType import dateMovement running_total
----------- ----------- ------------ ---------- ----------------------- --------------
1 1 payment 50.00 2018-05-11 00:00:00.000 450.00
2 1 refund 25.05 2018-05-10 00:00:00.000 475.05
3 1 refund 60.00 2018-04-06 00:00:00.000 535.05
4 1 payment 100.00 2018-04-03 00:00:00.000 435.05
I would like to retrieve the oldest date under the FirstScanned Column. My issue is that a product can be registered again at some point as a new product, when this happens, I'd like the FirstScanned to be retrieve the oldest scanned date based on the newest scan on Location 1
In the following example I'm trying to retrieve the correct FirstScanned Date without success:
CREATE TABLE [dbo].[Products123](
[ID] [int] NOT NULL,
[GTIN] [varchar](50) NULL,
[LocationID] [int] NULL,
[UserID] [int] NULL,
[Created] [datetime] )
insert into Products123(ID, GTIN, LocationID, UserID, Created)
Values(1, '12345678910', 1, 3, '2017-06-30 14:58:07.693'), -- Location "1" is when products was registered/scanned for this first time
(2, '12345678910', 5, 3, '2017-06-30 15:25:12.287'), -- The product is scanned into a new location
(3, '12345678910', 17, 3, '2017-06-30 14:58:07.693'), -- The product is now scanned into the "end" location and is considered to be closed
(4, '12345678910', 1, 7, '2017-08-01 11:34:16.347'), -- A month later the same productID has been registered,
(5, '12345678910', 4, 7, '2017-08-01 11:36:16.460') -- etc
DECLARE #Prev8workingdays date = CASE
WHEN datepart(dw, getdate()) IN (2,3,4) THEN dateadd(day,-14, getdate())
WHEN datepart(dw, getdate()) IN (1) THEN dateadd(day,-13, getdate())
ELSE dateadd(day,-12, getdate())
END
DECLARE #Pre6WorkingDay date = CASE
WHEN datepart(dw, getdate()) IN (2) THEN dateadd(day,-9, getdate())
WHEN datepart(dw, getdate()) IN (1) THEN dateadd(day,-8, getdate())
ELSE dateadd(day,-7, getdate())
END
select p.GTIN, p.FirstScanned as FirstScan, p.Created As lastScan
from
(
select p.GTIN
,p.LocationID
,p.Created
,min(p.Created) over (partition by p.GTIN) as FirstScanned
,max(p.Created) over (partition by p.GTIN) as LastScanned
from Products123 p
) p
where p.LastScanned = p.Created and LocationID not in (15,16,17) AND p.FirstScanned < #Prev8workingdays
Order by FirstScanned
My result looks like this:
GTIN | FirstScanned | LastScanned
12345678910 | 2017-06-30 14:58:07.693 |2017-08-01 11:36:16.460
But it Should be:
GTIN | FirstScanned | LastScanned
12345678910 | 2017-08-01 11:34:16.347 |2017-08-01 11:36:16.460
You can get there just using a simple case statement. Take the max date for location 1, and that will be the latest date
select p.GTIN, p.FirstScanned as FirstScan, p.Created As lastScan
from
(
select p.GTIN
,p.LocationID
,p.Created
,max(case when p.LocationID=1 then p.Created else null end) over (partition by p.GTIN) as FirstScanned
,max(p.Created) over (partition by p.GTIN) as LastScanned
from Products123 p
) p
where p.LastScanned = p.Created and LocationID not in (15,16,17) AND p.FirstScanned < #Prev8workingdays
CREATE TABLE [T]
(
CreatedOn DATETIME NOT NULL
,Name NVARCHAR(20) NOT NULL
,Code NVARCHAR(20) NOT NULL
,R FLOAT NULL
,C1 FLOAT NULL
,C2 FLOAT NULL
,C3 FLOAT NULL
);
INSERT INTO [T] VALUES
('2013-01-01', N'A', N'', 13, NULL, NULL, NULL)
,('2013-01-07', N'A', N'1', NULL, 5, NULL, NULL)
,('2013-01-31', N'A', N'2', NULL, 4, NULL, NULL)
,('2013-02-01', N'A', N'1', NULL, NULL, 6, NULL)
,('2013-02-15', N'A', N'2', NULL, NULL, NULL, 3)
,('2013-03-01', N'A', N'1', NULL, 1, NULL, NULL)
,('2013-03-05', N'A', N'', 8, NULL, NULL, NULL)
,('2013-03-22', N'A', N'2', NULL, NULL, NULL, 1)
,('2013-05-01', N'A', N'1', NULL, 2, NULL, NULL);
In [T]
1. One and only one non-null value per row for [R], [C1], [C2] and [C3]
2. [Code] contains a non-empty value if [C1], [C2] or [C3] contains a non-null value
3. There is an index on [Name]
4. Contains millions of rows
5. Few unique values of [Code], typically less than 100
6. Few unique values of [Name], typically less than 10000
7. Is actually a complex view containing several inner joins
How does one select from [T] ([DateMonth], [P]) where [CreatedOn] >= #Start AND [CreatedOn] <= #End AND [Name] = #Name AND [P] = Sum([R]) - (Sum(MaxOf(Sum([C1]), Sum([C2]), Sum([C3]), per unique [Code])))? (See the expected output below for a more accurate 'explanation'.) There should be a row in the resultset for each month #Start - #End regardless of whether there are rows for that month in [T]. Temporary table use is acceptable.
Expected Output
#Name = N'A'
#Start = '2012-12-01'
#End = '2013-07-01'
DateMonth P
'2012-12-01' 0
'2013-01-01' 4 -- 4 = SUM([R])=13 - (MaxForCode'1'(SUM(C1)=5, SUM(C2)=0, SUM(C3)=0)=5 + MaxForCode'2'(SUM(C1)=4, SUM(C2)=0, SUM(C3)=0)=4)
'2013-02-01' 3 -- 3 = SUM([R])=13 - (MaxForCode'1'(SUM(C1)=5, SUM(C2)=6, SUM(C3)=0)=6 + MaxForCode'2'(SUM(C1)=4, SUM(C2)=0, SUM(C3)=3)=4)
'2013-03-01' 11 -- 11 = SUM([R])=13+8=21 - (MaxForCode'1'(SUM(C1)=5+1=6, SUM(C2)=6, SUM(C3)=0)=6 + MaxForCode'2'(SUM(C1)=4, SUM(C2)=0, SUM(C3)=3+1=4)=4)
'2013-04-01' 11
'2013-05-01' 9 -- 9 = SUM([R])=13+8=21 - (MaxForCode'1'(SUM(C1)=5+1=6, SUM(C2)=6+2=8, SUM(C3)=0)=8 + MaxForCode'2'(SUM(C1)=4, SUM(C2)=0, SUM(C3)=3+1=4)=4)
'2013-06-01' 9
'2013-07-01' 9
Here is one solution. There are certainly some performance improvements that could be made, but I'll leave that up to you and your specific situation. Note that the CTE usage is certainly not necessary and adding CreatedOn to the index would be very helpul. A temp table may also be better than a table variable, but you'll need to evaluate that.
Since I think what you're looking for are running totals, this article may be helpful in improving the performance of my suggested solution.
Personally, I would first consider not using the view, as working directly with the sql that creates the view may be more performant.
Here is the SQL and a SQLFiddle link.
DECLARE #Name NVARCHAR(1) = N'A',
#Start DATETIME = '2012-12-01',
#End DATETIME = '2013-07-01'
--get the date for the first of the start and end months
DECLARE #StartMonth DATETIME = DATEADD(month, DATEDIFF(month, 0, #Start), 0)
DECLARE #EndMonth DATETIME = DATEADD(month, DATEDIFF(month, 0, #End), 0)
DECLARE #tt TABLE
(
DateMonth DATETIME,
sum_r FLOAT,
code NVARCHAR(20),
max_c FLOAT
)
--CTE to create a simple table with an entry for each month (and nxt month)
;WITH Months
as
(
SELECT #StartMonth as 'dt', DATEADD(month, 1, #StartMonth) as 'nxt'
UNION ALL
SELECT DATEADD(month, 1, dt) as 'dt', DATEADD(month, 2, dt) as 'nxt'
FROM Months
WHERE dt < #EndMonth
)
--SELECT * FROM Months OPTION(MAXRECURSION 9965) --for the CTE, you could also select dates into a temp table/table var first
INSERT INTO #tt (DateMonth, sum_r, code, max_c)
SELECT M.dt DateMonth,
ISNULL(t.sum_r,0) sum_r,
ISNULL(t.code,'') code,
ISNULL(t.max_c,0) max_c
--sum_c1, sum_c2, sum_c3, cnt
FROM Months M
OUTER APPLY (
SELECT sum_r,
code,
CASE WHEN sum_c1 >= sum_c2 AND sum_c1 >= sum_c3 THEN sum_c1
WHEN sum_c2 >= sum_c1 AND sum_c2 >= sum_c3 THEN sum_c2
ELSE sum_c3
END max_c
--sum_c1, sum_c2, sum_c3, cnt
FROM ( --use a sub select here to improve case statement performance getting max_c
SELECT SUM(ISNULL(r,0)) sum_r,
code,
sum(ISNULL(c1,0)) sum_c1,
sum(ISNULL(c2,0)) sum_c2,
SUM(ISNULL(c3,0)) sum_c3
FROM T
WHERE CreatedOn >= #Start AND CreatedOn < M.nxt
AND CreatedOn <= #End
AND Name = #Name
GROUP BY code
) subselect
) t
OPTION (MAXRECURSION 999)
SELECT DateMonth, SUM(sum_r) - SUM(max_c) p
FROM #tt
GROUP BY DateMonth
I have a table containing device movements.
MoveID DeviceID Start End
I want to find out if there is a way to sum up the total movement days for each device to the present. However if there is a gap 6 weeks bewtween an end date and the next start date then the time count is reset.
MoveID DeviceID Start End
1 1 2011-1-1 2011-2-1
2 1 2011-9-1 2011-9-20
3 1 2011-9-25 2011-9-28
The total for device should be 24 days as because there is a gap of greater than 6 weeks. Also I'd like to find out the number of days since the first movement in the group in this case 28 days as the latest count group started on the 2011-9-1
I thought I could do it with a stored proc and a cursor etc (which is not good) just wondered if there was anything better?
Thanks
Graeme
create table #test
(
MoveID int,
DeviceID int,
Start date,
End_time date
)
--drop table #test
insert into #test values
(1,1,'2011-1-1','2011-2-1'),
(2,1,'2011-9-1','2011-9-20'),
(3,1,'2011-9-25','2011-9-28')
select
a.DeviceID,
sum(case when datediff(dd, a.End_time, isnull(b.Start, a.end_time)) > 42 /*6 weeks = 42 days*/ then 0 else datediff(dd,a.Start, a.End_time)+1 /*we will count also the last day*/ end) as movement_days,
sum(case when datediff(dd, a.End_time, isnull(b.Start, a.end_time)) > 42 /6 weeks = 42 days/ then 0 else datediff(dd,a.Start, a.End_time)+1 /we will count also the last day/ end + case when b.MoveID is null then datediff(dd, a.Start, a.End_time) + 1 else 0 end) as total_days
from
#test a
left join #test b
on a.DeviceID = b.DeviceID
and a.MoveID + 1 = b.MoveID
group by
a.DeviceID
Let me know if you need some explanation - there can be more ways to do that...
DECLARE #Times TABLE
(
MoveID INT,
DeviceID INT,
Start DATETIME,
[End] DATETIME
)
INSERT INTO #Times VALUES (1, 1, '1/1/2011', '2/1/2011')
INSERT INTO #Times VALUES (2, 1, '9/1/2011', '9/20/2011')
INSERT INTO #Times VALUES (3, 1, '9/25/2011', '9/28/2011')
INSERT INTO #Times VALUES (4, 2, '1/1/2011', '2/1/2011')
INSERT INTO #Times VALUES (5, 2, '3/1/2011', '4/20/2011')
INSERT INTO #Times VALUES (6, 2, '5/1/2011', '6/20/2011')
DECLARE #MaxGapInWeeks INT
SET #MaxGapInWeeks = 6
SELECT
validTimes.DeviceID,
SUM(DATEDIFF(DAY, validTimes.Start, validTimes.[End]) + 1) AS TotalDays,
DATEDIFF(DAY, MIN(validTimes.Start), MAX(validTimes.[End])) + 1 AS TotalDaysInGroup
FROM
#Times validTimes LEFT JOIN
#Times timeGap
ON timeGap.DeviceID = validTimes.DeviceID
AND timeGap.MoveID <> validTimes.MoveID
AND DATEDIFF(WEEK, validTimes.[End], timeGap.Start) > #MaxGapInWeeks
WHERE timeGap.MoveID IS NULL
GROUP BY validTimes.DeviceID