I'm having a problem allocating payments to invoice lines.
Data looks like this:
Invoice lines table (sales):
lineId invoiceId value
1 1 100
2 1 -50
3 1 40
4 2 500
Payments table (payments):
paymentId invoiceId amount
1 1 50
2 1 40
3 2 300
Now, I want to know for each invoice line the payment details. The payments shall be allocated first to the smallest values (i.e. line 2, -50)
The output should look like this:
lineId invoiceId value paymentId valuePaid valueUnpaid
2 1 -50 1 -50 0
3 1 40 1 40 0
1 1 100 1 60 40
1 1 100 2 40 0
4 2 500 3 300 200
The problem is solved in the post below, but the solution does not work if you have negative invoice values or if you have to split an invoice line in two payments.
https://dba.stackexchange.com/questions/58474/how-can-i-use-running-total-aggregates-in-a-query-to-output-financial-accumulati/219925?noredirect=1#comment431486_219925
This is what I've done so far based on the article above:
drop table dbo.#sales
drop table dbo.#payments
CREATE TABLE dbo.#sales
( lineId int primary key, -- unique line id
invoiceId int not null , -- InvoiceId foreign key
itemValue money not null ) -- value of invoice line.
CREATE TABLE dbo.#payments
( paymentId int primary key, -- Unique payment id
InvoiceId int not null, -- InvoiceId foreign key
PayAmount money not null
)
-- Example invoice, id #1, with 3 lines, total ammount = 90; id #2, with one line, value 500
INSERT dbo.#sales VALUES
(1, 1, 100),
(2, 1, -50),
(3, 1, 40),
(4, 2, 500) ;
-- Two payments paid towards invoice id#1, 50+40 = 90
-- One payment paid towards invoice id#2, 300
INSERT dbo.#Payments
VALUES (1, 1, 50),
(2, 1, 40),
(3, 2, 300);
-- Expected output should be as follows, for reporting purposes.
/* lineId, invoiceId, value, paymentId, valuePaid, valueUnpaid
2, 1, -50, 1, -50, 0
3, 1, 40, 1, 40, 0
1, 1, 100, 1, 60, 40
1, 1, 100, 2, 40, 0
4, 2, 500, 3, 300, 200 */
WITH inv AS
( SELECT lineId, invoiceId,
itemValue,
SumItemValue = SUM(itemValue) OVER
(PARTITION BY InvoiceId
ORDER BY ItemValue Asc
ROWS BETWEEN UNBOUNDED PRECEDING
AND CURRENT ROW)
FROM dbo.#Sales
)
, pay AS
( SELECT
PaymentId, InvoiceId, PayAmount as PayAmt,
SumPayAmt = SUM(PayAmount) OVER
(PARTITION BY InvoiceId
ORDER BY PaymentId
ROWS BETWEEN UNBOUNDED PRECEDING
AND CURRENT ROW)
FROM dbo.#payments
)
SELECT
inv.lineId,
inv.InvoiceId,
inv.itemValue,
pay.PaymentId,
PaymentAllocated =
CASE WHEN SumPayAmt <= SumItemValue - itemValue
OR SumItemValue <= SumPayAmt - PayAmt
THEN 0
ELSE
CASE WHEN SumPayAmt <= SumItemValue THEN SumPayAmt
ELSE SumItemValue END
- CASE WHEN SumPayAmt-PayAmt <= SumItemValue-itemValue
THEN SumItemValue-itemValue
ELSE SumPayAmt-PayAmt END
END
FROM inv JOIN pay
ON inv.InvoiceId = pay.InvoiceId
ORDER BY
inv.InvoiceId,
pay.PaymentId;
The current output is:
lineId InvoiceId itemValue PaymentId PaymentAllocated
2 1 -50.00 1 0.00
3 1 40.00 1 0.00
1 1 100.00 1 50.00
2 1 -50.00 2 0.00
3 1 40.00 2 0.00
1 1 100.00 2 40.00
4 2 500.00 3 300.00
Any direction will be appreciated. Thank you.
More info on the allocation rules:
Allocating first payment to the smallest sale (i.e. -50) was just a
convention to insure all sales lines get payments. If I’d allocate
arbitrary or with another rule, and line 1 (value 100) would get the
first payment, I’d use all the payment for this line and the rest of
the invoice would remain unallocated.
As I said, it’s just an convention. If someone else comes with a
different rule that works, it’s ok. Actually, the structure is
simplified compared with the production tables: payments also have a
payment date, type, … and a correct distribution should tell us what
invoice lines were paid at each payment time.
Payments are restricted by the logic of the system to be smaller then
the sum of the invoice lines. Well, it might be a case when payments
are greater: the total invoice is negative (ie: -100). In this case
we can insert in the payments table amounts in the range of -100: 0
and Total Payments are restricted to -100
In the end I found quite a simple and natural sollution - to allocate payments based on the percentage of each payment in the total value of the invoice.
drop table dbo.#sales
drop table dbo.#payments
CREATE TABLE dbo.#sales
( lineId int primary key, -- unique line id
invoiceId int not null , -- InvoiceId foreign key
itemValue money not null ) -- value of invoice line.
CREATE TABLE dbo.#payments
( paymentId int primary key, -- Unique payment id
InvoiceId int not null, -- InvoiceId foreign key
PayAmount money not null
)
-- Example invoice, id #1, with 3 lines, total ammount = 90; id #2, with one line, value 500
INSERT dbo.#sales VALUES
(1, 1, 100),
(2, 1, -50),
(3, 1, 40),
(4, 2, 500) ;
-- Two payments paid towards invoice id#1, 50+40 = 90
-- One payment paid towards invoice id#2, 300
INSERT dbo.#Payments
VALUES (1, 1, 50),
(2, 1, 40),
(3, 2, 300);
SELECT
s.lineId,
s.InvoiceId,
s.itemValue,
p.PayAmount,
p.PaymentId,
round(p.PayAmount / ts.SumItemValue,3) as PaymentPercent,
s.ItemValue * round(p.PayAmount / ts.SumItemValue,3) as AllocatedPayment
FROM dbo.#sales s
LEFT JOIN dbo.#payments p
ON s.InvoiceId = p.InvoiceId
LEFT JOIN (SELECT invoiceId, sum(itemValue) as SumItemValue FROM dbo.#sales GROUP BY invoiceId) ts
ON s.invoiceId = ts.invoiceId
ORDER BY
s.InvoiceId,
p.PaymentId;
And the resunt looks like this:
lineId InvoiceId itemValue PayAmount PaymentId PaymentPercent AllocatedPayment
1 1 100.00 50.00 1 0.556 55.60
2 1 -50.00 50.00 1 0.556 -27.80
3 1 40.00 50.00 1 0.556 22.24
3 1 40.00 40.00 2 0.444 17.76
2 1 -50.00 40.00 2 0.444 -22.20
1 1 100.00 40.00 2 0.444 44.40
4 2 500.00 300.00 3 0.60 300.00
Related
I have the following record set output:
ID Name Pay_Type Paid_Amnt Interest_Amnt
1 John Smith Benefit 1075 0
1 John Smith Interest 1.23 0
2 Tom Ryder Benefit 1123 0
3 Mark Thompson Benefit 1211 0
3 Mark Thompson Interest 1.34 0
What I'd like is for values with the Pay_Type = Interest to be placed in the Interest column.
Desired output:
ID Name Pay_Type Pay_Type 2 Paid_Amnt Interest_Amnt
1 John Smith Benefit Interest 1075 1.23
2 Tom Ryder Benefit NULL 1123 0
3 Mark Thompson Benefit Interest 1211 1.34
I tried something like the following:
Select row_number()over(partition by id, case when pay_type = 'Interest' then interest_amnt = paid_amnt
when pay_type = 'Interest' then paid_amnt = 0 end) as new_interest
Does anyone know how to get the desired results?
Thank you
declare #t table(id int, pay_type varchar(25), name varchar(100), paid_amnt float, interest_amnt float)
insert into #t values(1, 'Benefit', 'John Smith', 1075, 0),
(1, 'Interest', 'John Smith',1.23, 0),
(2, 'Benefit', 'Tom Ryder', 1123, 0),
(3, 'Benefit', 'Mark Thompson', 1211, 0),
(4, 'Interest', 'Mark Thompson', 1.34, 0)
select * from #t
Just in case you can have more than 2 records per person, I believe this will give you what you want, it utilizes a couple of subqueries and group by,
subquery x groups your records so you get the interest sums and benefits sums in a row per user,
subquery y uses CASE expressions to place the summed amounts into their proper columns or zero in case of it being Benefit/Interest and adds the pay type columns of pay_type1 and pay_type2 with values of Benefit and Interest respectively,
outer query groups everything together into 1 row per user, and sums their interest and benefit columns respectively:
SELECT y.[id] AS [ID], y.[name] AS [Name],
y.[pay_type1] AS [Pay_Type], y.[Pay_Type2], SUM(y.[Paid_Amnt]) AS [Paid_Amnt],
SUM(y.[Interest_Amnt]) AS [Interest_Amnt]
FROM
(
SELECT id, name, 'Benefit' AS [pay_type1], 'Interest' AS [pay_type2],
CASE WHEN pay_type = 'Benefit' THEN x.Amount ELSE 0 END AS [Paid_Amnt],
CASE WHEN pay_type = 'Interest' THEN x.Amount ELSE 0 END AS [Interest_Amnt]
FROM
(
SELECT id, pay_type, name, SUM(paid_amnt) AS [Amount]
FROM table as t
GROUP BY id, pay_type, name
) AS x
) AS y
GROUP BY y.[id], y.[name], y.[pay_type1], y.[pay_type2]
I have created the following table to illustrate what is happening
create table weather (
WDate varchar(10),
ItemCode varchar(8),
ItemValue int,
ItemUnits varchar(8))
insert into Weather values
('2020-02-10', 'MAXTEMP', 6, 'degC'),
('2020-02-10', 'MINTEMP', 2, 'degC'),
('2020-02-10', 'RAIN', 0, 'mm'),
('2020-02-11', 'MAXTEMP', 5, 'degC'),
('2020-02-11', 'RAIN', 20, 'mm'),
('2020-02-11', 'MINTEMP', 1, 'degC'),
('2020-02-12', 'RAIN', 5, 'mm'),
('2020-02-12', 'MAXTEMP', 8, 'degC'),
('2020-02-12', 'MINTEMP', 2, 'degC')
The data is not always in the same order because it can come from equipment that may not be time sync'ed. When I run the following query
SELECT
[wdate] as 'Date',
[MINTEMP] as 'Min Temp',
[MAXTEMP] as 'Max Temp',
[RAIN] as 'Rain'
FROM
(
SELECT
*
FROM
weather
) rawdata
PIVOT
(
min(ItemValue)
FOR ItemCode IN ([MINTEMP], [MAXTEMP], [RAIN])
) pitem
ORDER BY WDate
I get
WDate Min Temp Max Temp Rain
2020-02-10 2 6 NULL
2020-02-10 NULL NULL 0
2020-02-11 1 5 NULL
2020-02-11 NULL NULL 20
2020-02-12 2 8 NULL
2020-02-12 NULL NULL 5
I can't figure out why the Rain data doesn't end up on the same row as the Min and Max Temp. I was expecting
WDate Min Temp Max Temp Rain
2020-02-10 2 6 0
2020-02-11 1 5 20
2020-02-12 2 8 5
You must "FEED" the pivot with the minimum number of columns. Notice the ItemUnits is missing from the sub-select rawdata
Example
SELECT
[wdate] as 'Date',
[MINTEMP] as 'Min Temp',
[MAXTEMP] as 'Max Temp',
[RAIN] as 'Rain'
FROM
(
Select WDate
,ItemCode
,ItemValue
from Weather
) rawdata
PIVOT
(
min(ItemValue)
FOR ItemCode IN ([MINTEMP], [MAXTEMP], [RAIN])
) pitem
ORDER BY WDate
Returns
Date Min Temp Max Temp Rain
2020-02-10 2 6 0
2020-02-11 1 5 20
2020-02-12 2 8 5
I have a SQL Server 2012 table which looks like this:
Date Product Cost AvgCost
----------------------------------
4/7/2019 ProdA 3 NULL
4/9/2019 ProdA 2 NULL
4/10/2019 ProdA 4 NULL
4/24/2019 ProdA 4 NULL
4/30/2019 ProdA 1 NULL
I am trying to Calculate the value for AvgCost based on the below conditions:
If there are rows for the last 7 days or less then take simple average of "Cost" for those days
If no rows exist for last 7 days or less then simply input 1 for AvgCost
Code:
SELECT [Date], Product, Cost, oa.AvgCost
FROM [Table1] A
OUTER APPLY
(SELECT AVG(A1.Cost) as AvgCost
FROM [Table1] A1
WHERE A1.Product = A.Product
AND A1.date BETWEEN DATEADD (Day, -6, DATEADD(DAY, -1, A.date))
AND DATEADD(DAY, -1, A.date)) oa
WHERE
a.date BETWEEN '04/1/2019' AND '04/30/2019'
The code only seems to work only if there are rows for exactly 7 days prior
(For ex: I am able to get the correct value for 4/8/2019 if I have rows from date 4/1/2019 - 4/7/2019)
Expected Result:
Date Product Cost AvgCost
4/7/2019 ProdA 3 1 -- no rows exist for last 7 days or
-- less then 1
4/9/2019 ProdA 2 3 -- Only 1 rows exists for last 7 days or
-- less then average for that day
4/10/2019 ProdA 4 2.5 -- Average cost for 4/7 and 4/9
4/24/2019 ProdA 4 1 -- no rows exist for last 7 days or
-- less then 1
4/30/2019 ProdA 1 4 --Only 1 rows exists for last 7 days or
-- less then average for that day
Actual Result
Date Product Cost AvgCost
4/7/2019 ProdA 3 NULL
4/9/2019 ProdA 2 NULL
4/10/2019 ProdA 4 NULL
4/24/2019 ProdA 4 NULL
4/30/2019 ProdA 1 NULL
So I wound up rewriting your query a bunch to make sense to me, as well as adding in the actual code to get your example. I also added the code to get the 1 where the average is missing or less than 0. Somewhere in there I did something that made it work exactly as you specify, but I don't know where sorry.
drop table if exists #table1
create table #table1 (d date, Product nvarchar(max), Cost float, AvgCost FLOAT)
insert into #table1 values ('20190407', 'ProdA', 3, null)
insert into #table1 values ('20190409', 'ProdA', 2, null)
insert into #table1 values ('20190410', 'ProdA', 4, null)
insert into #table1 values ('20190424', 'ProdA', 4, null)
insert into #table1 values ('20190430', 'ProdA', 1, null)
SELECT [d], Product, Cost, iif(isnull(oa.AvgCost, 0) < 1, 1, oa.AvgCost) as AvgCost
FROM [#table1] A
OUTER APPLY
(
SELECT AVG(A1.Cost) as AvgCost
FROM [#table1] as A1
WHERE A1.Product = A.Product
AND A1.d BETWEEN DATEADD(Day, -7, A.d)
AND DATEADD(DAY, -1, A.d)
) as oa
WHERE a.d BETWEEN '20190104' AND '20190430'
Results:
d Product Cost AvgCost
2019-04-07 ProdA 3 1
2019-04-09 ProdA 2 3
2019-04-10 ProdA 4 2.5
2019-04-24 ProdA 4 1
2019-04-30 ProdA 1 4
I have two tables.
1.Invoice
invoice_Id invoice_no client_id date total_price
-----------------------------------------------------------------------------
2 INV00001 9 2014-10-15 200.00
7 INV00002 9 2014-10-16 560.00
8 INV00003 9 2014-10-21 100.00
11 INV00004 9 2014-10-27 101.00
2.Invoice_payment
InvPayment_id client_id Invoice_Id receipt_no payment_date amount_received discount
--------------------------------------------------------------------------------------------
6 9 8 REC00002 2014-10-31 5.00 0.00
Now I want to get Total Amount Due by Client by totalling the invoice amounts and subtracting any amounts received.
Expected Result:
client_id Total_price Due_Amount
-----------------------------------------------------------------------------
9 961.00 956.00
NOTES:
There will be zero rows if no payments are taken to date.
There could be multiple rows if more than one payment is taken.
Here is what I have tried:
;WITH cte (clientid, invoiceid, paid, disc)
As
(
Select client_id clientId, invoice_Id invoiceId, sum(amount_received) paid, sum(discount) disc
From tbl_Invoice_Payment
Group by invoice_id, client_id
)
Select I.client_id, invoice_Id, invoice_no, I.due_date
,SUM(I.total_price), Isnull(SUM(paid), 0) Paid, (SUM(Total_price) - Isnull(SUM(paid),0) - Isnull(SUM(disc),0)) Balance
--,I.total_price, Isnull(paid, 0) Paid, (Total_price - Isnull(paid,0) - Isnull(disc,0)) Balance
From tbl_invoice I Left join cte On I.client_id = cte.clientId
And I.invoice_id = cte.invoiceid
left join tbl_client C on C.client_id = I.client_id
group by I.client_id, invoice_Id, invoice_no, due_date, account_type, company_name, total_price, paid, disc
order by company_name
But it's not working as intended.
Instead of using a CTE, you can simply add a subquery to return the result of payments made and subtract that value from the total:
SQL Fiddle Demo
Schema Setup:
CREATE TABLE Invoice
([invoice_Id] int, [invoice_no] varchar(8), [client_id] int,
[date] datetime, [total_price] decimal(5,2));
INSERT INTO Invoice
([invoice_Id], [invoice_no], [client_id], [date], [total_price])
VALUES
(2, 'INV00001', 9, '2014-10-15 00:00:00', 200.00),
(7, 'INV00002', 9, '2014-10-16 00:00:00', 560.00),
(8, 'INV00003', 9, '2014-10-21 00:00:00', 100.00),
(11, 'INV00004', 9, '2014-10-27 00:00:00', 101.00);
CREATE TABLE Invoice_Payment
([InvPayment_id] int, [client_id] int, [Invoice_Id] int, [receipt_no] varchar(8),
[payment_date] datetime, [amount_received] decimal(5,2), [discount] int);
INSERT INTO Invoice_Payment
([InvPayment_id], [client_id], [Invoice_Id], [receipt_no], [payment_date],
[amount_received], [discount])
VALUES
(6, 9, 8, 'REC00002', '2014-10-31 00:00:00', 5.00, 0.00);
Query to generate output::
SELECT i.client_id , SUM(i.total_price) AS Total_price,
SUM(i.total_price) - ( SELECT SUM(ip.amount_received)
FROM dbo.Invoice_Payment ip
WHERE i.client_id = ip.client_id
) AS DueAmount
FROM dbo.Invoice i
WHERE client_id = 9
GROUP BY client_id
Results:
| CLIENT_ID | TOTAL_PRICE | DUEAMOUNT |
|-----------|-------------|-----------|
| 9 | 961 | 956 |
Please select the values you want. The problem is related to grouping.
In the group by clause I.client_id, invoice_Id, invoice_no, due_date, account_type, company_name, total_price, paid, disc this much fields are specified. invoice_Id, invoice_no prevent grouping which you need. Remove both invoice_Id and invoice_no from select and group by and try again.
suppose i have a customer balance details table. For Example :
CustID CustName CustDate Amt Bal
1 DP 1/5/2013 1000 1000
1 DP 5/5/2013 100 1100
1 Dhara 10/5/2013 1000 1000
1 DP 10/5/2013 1000 2100
now user insert a record CustName = DP , CustDate = 7/5/2013 , Amt = 400 then that record should be added after date 5/5/2013 and before 10/5/2013 and respective balance should be change also as follows :
CustID CustName CustDate Amt Bal
1 DP 1/5/2013 1000 1000
1 DP 5/5/2013 100 1100
1 DP 7/5/2013 400 1500
1 Dhara 10/5/2013 1000 1000
1 DP 10/5/2013 1000 2500
How do i achieve when inserting record in database ? How do i check when date lies between already inserted records ? Please Help
I think, you can get what you want if you use an ORDER BY clause to access the data in the desired order. Please also refer to your DB documentation for the use of indices.
Here is one suggestion on how do do it: http://www.sqlfiddle.com/#!3/d341b/9
CREATE TABLE Customer
(
CustId INT,
CustName NVARCHAR(150),
CustDate DATE,
Amt INT,
)
INSERT INTO Customer
(CustID, CustName, CustDate, Amt)
VALUES
(1, 'DP', '2013-01-05 00:00:00', 1000),
(1, 'DP', '2013-05-05 00:00:00', 100),
(1, 'DP', '2013-07-05 00:00:00', 400),
(1, 'Dhara', '2013-10-05 00:00:00', 1000),
(1, 'DP', '2013-10-05 00:00:00', 1000)
;
;WITH RankedCustomer AS
(
SELECT
CustId
, CustName
, CustDate
, Amt
, ROW_NUMBER() OVER(ORDER BY CustDate) Row
FROM
Customer
)
SELECT
Customer.CustId
, Customer.CustName
, Customer.CustDate
, Customer.Amt
, SUM(PreviousCustomer.Amt)
FROM
RankedCustomer Customer
LEFT JOIN RankedCustomer PreviousCustomer
ON PreviousCustomer.CustId = Customer.CustId
AND PreviousCustomer.CustName = Customer.CustName
AND PreviousCustomer.Row <= Customer.Row
GROUP BY
Customer.CustId
, Customer.CustName
, Customer.CustDate
, Customer.Amt
ORDER BY
Customer.CustDate