How to obtain the actual running balance within this query - sql

Following on from the question I originally asked yesterday (here), I was able to construct the following sql query that producded a running list of invoices and payments.
SELECT
'Invoice' AS TransactionType,
i.InvoiceNumber AS Description,
i.InvoiceDate AS TransactionDate,
CAST(ROUND(i.OutstandingBalance, 2) AS DECIMAL(12, 2)) AS TransactionAmount
FROM
Invoices i
WHERE
i.CustomerId = 12
AND i.InvoiceDate BETWEEN '20150601' AND '20160229'
AND i.OutstandingBalance > 0.02
UNION
SELECT
'Payment' AS TransactionType,
ip.InvoicePaymentId AS Description,
ip.InvoicePaymentDate AS TransactionDate,
- ip.Amount AS TransactionAmount
FROM
InvoicePayments ip
WHERE
ip.CustomerId = 12
AND ip.InvoicePaymentDate BETWEEN '20150601' AND '20160229'
ORDER BY
TransactionDate
What I would now like to do is produce one extra column that is in effect the running balance on the account. I figured that if I started with a variable it should then be possible to add (or subtract from it to give me what I wanted). To that end I tried the following;
DECLARE #OutstandingBalance MONEY = 0
SELECT
'Invoice' AS TransactionType, i.InvoiceNumber AS Description,
i.InvoiceDate AS TransactionDate,
CAST(ROUND(i.OutstandingBalance, 2) AS DECIMAL(12, 2)) AS TransactionAmount,
#OutstandingBalance + CAST(ROUND(i.OutstandingBalance, 2) AS DECIMAL(12, 2)) AS Balance
FROM
Invoices i
WHERE
i.CustomerId = 12
AND i.InvoiceDate BETWEEN '20150601' AND '20160229'
AND i.OutstandingBalance > 0.02
Which produced the results below.
However trying to modify the query by making it #OutstandingBalance += like so;
DECLARE #OutstandingBalance MONEY = 0
SELECT
'Invoice' AS TransactionType, i.InvoiceNumber AS Description,
i.InvoiceDate AS TransactionDate,
CAST(ROUND(i.OutstandingBalance, 2) AS DECIMAL(12, 2)) AS TransactionAmount,
#OutstandingBalance += CAST(ROUND(i.OutstandingBalance, 2)AS DECIMAL(12,2)) AS Balance
FROM
Invoices i
WHERE
i.CustomerId = 12
AND i.InvoiceDate BETWEEN '20150601' AND '20160229'
AND i.OutstandingBalance > 0.02
Throws an error telling me that the syntax is incorrect near the Keyword AS (which I presume refers to AS Balance. I suspect that I should probably be 'setting' the value of #OutstandingBalance but adding a set statement within the select also throws errors.
Is it possible to create a running balance in this sort of query and if so how does one accommodate setting the #OutstandingBalance to achieve it?
In response to the answer below this is the result set I get:
EDIT
Revised query to accommodate both invoices and payments:
SELECT 'Invoice' AS TransactionType,
i.InvoiceNumber AS Description,
i.InvoiceDate AS TransactionDate,
CAST(ROUND(i.OutstandingBalance,2)AS DECIMAL(12,2)) AS TransactionAmount ,
SUM(CAST(ROUND(i.OutstandingBalance,2)AS DECIMAL(12,2))) OVER(ORDER BY i.InvoiceDate, i.InvoiceNumber) AS Balance
FROM Invoices i
WHERE i.CustomerId = 12
AND i.InvoiceDate BETWEEN '20150601' AND '20160229'
AND i.OutstandingBalance > 0.02
UNION
SELECT
'Payment' AS TransactionType,
ip.InvoicePaymentId AS Description,
ip.InvoicePaymentDate AS TransactionDate,
- ip.Amount AS TransactionAmount,
SUM(CAST(ROUND(-ip.Amount,2) AS DECIMAL(12,2))) OVER(ORDER BY ip.InvoicePaymentDate,ip.InvoicePaymentId) AS Balance
FROM InvoicePayments ip
WHERE ip.CustomerId = 12
AND ip.InvoicePaymentDate BETWEEN '20150601' AND '20160229'
ORDER BY TransactionDate, Description
Which produces the following:

You can use SUM with an OVER clause like this:
SELECT 'Invoice' AS TransactionType,
i.InvoiceNumber AS Description,
i.InvoiceDate AS TransactionDate,
CAST(ROUND(i.OutstandingBalance,2)AS DECIMAL(12,2)) AS TransactionAmount ,
SUM(CAST(ROUND(i.OutstandingBalance,2)AS DECIMAL(12,2))) OVER(ORDER BY i.InvoiceDate, i.InvoiceNumber) AS Balance
FROM Invoices i
WHERE i.CustomerId = 12
AND i.InvoiceDate BETWEEN '20150601' AND '20160229'
AND i.OutstandingBalance > 0.02
ORDER BY TransactionDate, Description
You can also use a cte to save one cast:
;WITH cte AS
(
SELECT 'Invoice' AS TransactionType,
i.InvoiceNumber AS Description,
i.InvoiceDate AS TransactionDate,
CAST(ROUND(i.OutstandingBalance,2)AS DECIMAL(12,2)) AS TransactionAmount
FROM Invoices i
WHERE i.CustomerId = 12
AND i.InvoiceDate BETWEEN '20150601' AND '20160229'
AND i.OutstandingBalance > 0.02
)
SELECT TransactionType,
Description,
TransactionDate,
TransactionAmount,
SUM(TransactionAmount) OVER(ORDER BY TransactionDate, Description) AS Balance
FROM cte
ORDER BY TransactionDate, Description

Related

SQL Rollup to Sum Totals of a Grouped Table

I have a table that is grouped by 'Group Type'. What I need is for the last row to display the sum of the values in the above rows in the Total row.
Below is the code currently but I am not sure how to sum multiple fields at once.
Actual
Expected Result
Do I need to add another CASE statement to
select
CASE when GROUP_TYPE is null then 'Total' ELSE GROUP_TYPE END as 'Group Type',
Sum_Amount, TotalER, [Discount Report]
from
(select GROUP_TYPE, sum(cast(AMOUNT as float)) as Sum_Amount FROM [dbo].[a]
where cast(ACTIVITY_DATE as date) between #startdate and #enddate
group by GROUP_TYPE with rollup) a
left join
(select [Charge Group] , sum(cast([Earned Revenue] as float)) as
TotalER, sum(cast(Discount as float)) as 'Discount Report'
from [dbo].[er] group by [Charge Group]) er
on
a.GROUP_TYPE = er.[Charge Group]
select sum(cast([er] as float)) from [dbo].[er]
I would do a union all before the aggregation. This makes it easier to specify the multiple aggregation sets:
select coalesce(group_type, 'Total') as group_type, -- the easy way
sum(amount) as sum_amount, sum(er) as totaler, sum(discount) as discount
from ((select group_type, cast(amount as float) as Amount, 0 as ER, 0 as discount
from [dbo].a
where cast(ACTIVITY_DATE as date) between #startdate and #enddate
) union all
(select [Charge Group], 0, cast([Earned Revenue] as float) as er, cast(Discount as float)
from [dbo].er
where cast(ACTIVITY_DATE as date) between #startdate and #enddate
)
) aer
group by grouping sets ( (group_type), () );

Add a Total Row after each Unique CustomerName

I am trying to add a 'Total' row after each Unique CustomerName. I have tried ROLLUP and it does not seem to work properly because of the amount of fields i am trying to group by. An example of what i am looking for would pseudo be
(
FlavorName('Total'), 2016Sales (Sum of total sales), 2017Sales (Sum of total sales), 2016TotalPounds (Sum of total pounds), 2017TotalPounds (Sum of total pounds))
Please find my current code below.
WITH cte AS (SELECT CustName AS CustomerName, ItemKey AS CICode, Description AS FlavorName, CASE WHEN InvoiceDate BETWEEN '2016-01-01' AND
'2016-12-31' THEN SUM(LineNet) ELSE 0 END AS [2016TotalSales], CASE WHEN InvoiceDate BETWEEN '2017-01-01' AND getdate() THEN SUM(LineNet)
ELSE 0 END AS [2017TotalSales], CASE WHEN InvoiceDate BETWEEN '2016-01-01' AND '2016-12-31' THEN ROUND(SUM(QtyOrd), 2)
ELSE 0 END AS [2016TotalPounds], CASE WHEN InvoiceDate BETWEEN '2017-01-01' AND getdate() THEN ROUND(SUM(QtyOrd), 2)
ELSE 0 END AS [2017TotalPounds], BasePrice, SUBSTRING(CAST(InvoiceDate AS nvarchar(50)), 8, 5) AS year, UOM
FROM dbo.ABC
GROUP BY CustName, ItemKey, Description, BasePrice, InvoiceDate, UOM)
SELECT TOP (100) PERCENT CustomerName, CASE WHEN CICode IS NULL THEN 'ALL' ELSE CICode END AS CICode, CASE WHEN BasePrice IS NULL
THEN 'TOTALS' ELSE FlavorName END AS FlavorName, SUM([2016TotalSales]) AS [2016Sales], SUM([2017TotalSales]) AS [2017Sales], SUM([2016TotalPounds])
AS [2016TotalPounds], ROUND(SUM([2017TotalPounds]), 2) AS [2017TotalPounds], UOM, ISNULL(ROUND((SUM([2017TotalPounds]) - SUM([2016TotalPounds]))
/ NULLIF (SUM([2016TotalPounds]), 0) * 100, 2), 100) AS [%Change], BasePrice
FROM cte AS cte_1
GROUP BY CustomerName, CICode, FlavorName, BasePrice, UOM
HAVING (SUM([2016TotalSales]) + SUM([2017TotalSales]) > 0)
Try GROUPING SETS:
GROUP BY GROUPING SETS ( (CustomerName, CICode, FlavorName, BasePrice, UOM), (CustomerName) )
You may have to play with ORDER BY to get the results in the particular order that you want.

how to sum tow field at get inline result?

i have tow field for example credit an debit in one table.
and i need to sum them and get result at each line for example :
date debit credit amount
2015/01/01 20 0 20
2015/01/02 0 5 15
2015/01/03 0 30 -15
i hope you help me to get the amount by a query
thanks
With SQL-Server 2012 or newer you can use this:
SELECT [date], debit, credit, amount,
SUM(debit-credit) OVER(ORDER BY [date] ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS amount
FROM TableName
ORDER BY [date]
Read: OVER-clause, especially the ROWS | RANGE part
With other versions you have to use a correlated subquery:
SELECT [date], debit, credit, amount,
(SELECT SUM(debit-credit)
FROM TableName t2
WHERE [date] <= t1.[date]) AS amount
FROM TableName t1
ORDER BY [date]
I agree with Tim's answer, I added some extra lines:
declare #credit as table (
[date] datetime,
amount int
)
declare #debit as table (
[date] datetime,
amount int
)
insert into #debit values
('2015-01-01', 20)
insert into #credit values
('2015-01-02', 5),
('2015-01-03', 30)
select
[date], debit, credit, SUM(debit-credit) OVER(ORDER BY [date] ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS amount
from(
select
[date], sum(debit) debit, sum(credit) credit
from
(
select
[date], 0 credit, d.amount debit
from
#debit d
union all
select
[date], c.amount credit, 0 debit
from
#credit c
) j group by j.date
) x

Using Subquery And Ordering The Value By Date

I'm Trying to Get the date , Discount , Total , Net Total ... ordered by date , the discount is showing the real amount but when I select multiple dates the total will be summed in those dates I've selected and it will be ordered by date
DECLARE #pR FLOAT = (SELECT SUM(CAST(Price AS FLOAT)) AS Price
FROM Orders WHERE isPaid = 1
AND PaidDate BETWEEN '8/17/2015' AND '8/18/2015' ) ;
SELECT Orders.PaidDate
, #pR AS Total
,sum(theorderids.Discount) As Discount
,(#pR - sum(theorderids.Discount)) AS [Net Total]
From
(SELECT OrderId, PaidDate
FROM Orders
WHERE Orders.PaidDate BETWEEN '8/17/2015' AND'8/18/2015'
GROUP BY Orders.OrderId, Orders.PaidDate) AS Orders
INNER JOIN theorderids ON Orders.OrderId = theorderids.ID
GROUP BY Orders.PaidDate ;
Example Data :
Row 1
"PaidDate": "17-08-2015",
"Total": 7388.0,
"Discount": 38.0,
"NetTotal": 7363.0
Row 2
"PaidDate": "18-08-2015",
"Total": 7388.0,
"Discount": 2.0,
"NetTotal": 7363.0
This will work.
SELECT TheOrderids.PaidDate, MAX(Price) AS Total
,sum(theorderids.Discount) As Discount
,(MAX(Price) - sum(theorderids.Discount)) AS [Net Total]
From
(SELECT PaidDate ,SUM(Price ) AS Price
FROM Orders
WHERE Orders.PaidDate BETWEEN '8/17/2015' AND'8/19/2015'
GROUP BY Orders.PaidDate
) AS Orders
INNER JOIN theorderids ON Orders.PaidDate = theorderids.PaidDate
GROUP BY theorderids.PaidDate
ORDER BY theorderids.PaidDate ;
Try this way
SELECT Orders.PaidDate
, #pR AS Total
,sum(theorderids.Discount) As Discount
,(#pR - sum(theorderids.Discount)) AS [Net Total]
From
(SELECT ROW_NUMBER() OVER(ORDER BY Orders.PaidDate ) AS Row, OrderId, PaidDate
FROM Orders
WHERE Orders.PaidDate BETWEEN '8/17/2015' AND'8/18/2015'
GROUP BY Orders.OrderId, Orders.PaidDate) AS Orders
INNER JOIN theorderids ON Orders.OrderId = theorderids.ID
GROUP BY Orders.PaidDate ;

SQL function that returns 0 when no records returned from query

I have created the below function to return Total Sales for a particular year, month and Territory entered as inputs. But I need to return 0 if there are no sales for the input parameters. I tried the IFNULL function but it does not seem to work.
Create Function GetTotalSales(#Year int,#month int,#TerritoryID int)
Returns Table
As
Return (Select Sum(TotalDue) as TotalSales, Cast(OrderDate AS DATE) as OrderDate, TerritoryID
From AdventureWorks2008R2.Sales.SalesOrderheader
Where #Year=DATEPART(Year,OrderDate) and #month=DATEPART(Month,OrderDate) and #TerritoryID=TerritoryID
Group By OrderDate, TerritoryID
);
Go
You can express this in a single query as:
with r as (
Select Sum(TotalDue) as TotalSales, Cast(OrderDate AS DATE) as OrderDate, TerritoryID
From AdventureWorks2008R2.Sales.SalesOrderheader
Where #Year = DATEPART(Year, OrderDate) and #month = DATEPART(Month, OrderDate) and #TerritoryID = TerritoryID
Group By OrderDate, TerritoryID
)
select *
from r
union all
select TotalSales, OrderDate, TerritoryId
from (select 0 as TotalSales, NULL as OrderDate, #TerritoryId as TerritoryId
) x
where not exists (select 1 from r);
You could also consider an approach like the following. By constructing a table with a single "default" row and then unconditionally left-joining to it, we ensure that the results always contain at least one row.
CREATE FUNCTION GetTotalSales(#year INT, #month INT, #territoryID INT)
RETURNS TABLE
AS
RETURN
(
SELECT
ISNULL(TotalSales, 0) AS TotalSales
,OrderDate
,TerritoryID
FROM
(VALUES (0)) AS default_values(default_value)
LEFT JOIN
(
SELECT
SUM(TotalDue) AS TotalSales
,CAST(OrderDate AS DATE) AS OrderDate
,TerritoryID
FROM
AdventureWorks2008R2.Sales.SalesOrderheader
WHERE
(#year=DATEPART(Year,OrderDate))
AND
(#month=DATEPART(Month,OrderDate))
AND
(#territoryID=TerritoryID)
GROUP BY
OrderDate, TerritoryID
) AS source_data
ON (1=1)
);