SQL Server map payments to products - sql

We have shopping carts that might have included products and payments.
Since these payments will be made to the carts, there will be no relation between the products and the payments except that they are in the same cart.
There are cases that these products will be invoiced individually even though they are in the same cart.
To create the invoices for the products we need the payment details, so we have to map the products to the payments.
These are our tables:
create table Products
(
ItemId int primary key,
CartId int not null,
ItemAmount smallmoney not null
)
create table Payments
(
PaymentId int primary key,
CartId int not null,
PaymentAmount smallmoney not null
)
create table MappedTable
(
ItemId int not null,
PaymentId int not null,
Amount smallmoney not null
)
INSERT INTO Products (ItemId, CartId, ItemAmount)
VALUES (1, 1, 143.49), (2, 1, 143.49), (3, 1, 143.49), (4, 2, 50.00), (5, 3, 75.00), (6, 3, 75.00)
INSERT INTO Payments (PaymentId, CartId, PaymentAmount)
VALUES (1, 1, 376.47), (2, 1, 54.00), (3, 2, 60.00), (4, 3, 140.00)
--select * from Products
--select * from Payments
--DROP TABLE Products
--DROP TABLE Payments
--DROP TABLE MappedTable
Products
ItemId | CartId | ItemAmount
------ | ------ | ----------
1 | 1 | 143.49
2 | 1 | 143.49
3 | 1 | 143.49
4 | 2 | 50.00
5 | 3 | 75.00
6 | 3 | 75.00
Payments
PaymentId | CartId | PaymentAmount
--------- | ------ | -------------
1 | 1 | 376.47
2 | 1 | 54.00
3 | 2 | 60.00
4 | 3 | 140.00
The order of the products and the payments may differ.
We need the output to look like this:
MappingTable
ItemId | PaymentId | MappedAmount
------ | --------- | ------------
1 | 1 | 143.49
2 | 1 | 143.49
3 | 1 | 89.49
3 | 2 | 54.00
4 | 3 | 50.00 (Remaining 10.00 from Payment 3 will be ignored)
5 | 4 | 75.00
6 | 4 | 65.00 (Missing 10.00 from Payment 4 will be ignored)
Cart 1: Sum of payments = sum of product costs
Cart 2: Sum of payments > sum of product costs. Only take the total product cost. Ignore the remaining 10.00
Cart 3: Sum of payments < sum of product costs. Take all the payments, ignore the fact that the payment is 10.00 short.
I thought that a query like the one below may solve the problem, but no luck.
insert into MappedTable
select
prd.ItemId, pay.PaymentId,
(Case
when prd.ItemAmount - isnull((select sum(m.Amount)
from MappedTable m
where m.ItemId = prd.ItemId), 0) <= pay.PaymentAmount - isnull((select sum(m.Amount) from MappedTable m where m.PaymentId = pay.PaymentId), 0)
then prd.ItemAmount - isnull((select sum(m.Amount) from MappedTable m where m.ItemId = prd.ItemId), 0)
else pay.PaymentAmount - isnull((select sum(m.Amount) from MappedTable m where m.PaymentId = pay.PaymentId), 0)
end)
from
Products prd
inner join
Payments pay on pay.CartId = prd.CartId
where
prd.ItemAmount > isnull((select sum(m.Amount) from MappedTable m where m.ItemId = prd.ItemId), 0)
and pay.PaymentAmount > isnull((select sum(m.Amount) from MappedTable m where m.PaymentId = pay.PaymentId), 0)
I've read about CTE (Common Table Expressions) and set-based approaches but I couldn't handle the issue.
So is this possible without using a cursor or a while loop?

Generally, this kind of task is referred to as a "knapsack problem", which is known to have no solution more efficient than brute forcing all possible combinations. In your case, however, you have additional conditions, namely ordered sets of both items and payments, so it is actually solvable using "overlapping intervals" technique.
The idea is to generate ranges of items and payments (1 pair of ranges per cart) and then look which payments overlap with which items, sequentially.
For any item-payment combination, there are 3 possible scenarios:
Payment covers the beginning of the item range (possibly completely covering the item);
Payment is completely inside the item;
Payment covers the end of item, thus "closing" it.
So, all that is needed is, for every item, find all suitable payments that match the aforementioned criteria, and order them by their identifiers. Here is a query that does it:
with cte as (
-- Project payment ranges, per cart
select pm.*, sum(pm.PaymentAmount) over(partition by pm.CartId order by pm.PaymentId) as [RT]
from #Payments pm
)
select q.ItemId, q.PaymentId,
-- Calculating the amount from payment that goes for this item
case q.Match
when 1 then q.PaymentRT - (q.ItemRT - q.ItemAmount)
when 2 then q.PaymentAmount
when 3 then case
-- Single payment spans over several items
when q.PaymentRT >= q.ItemRT and q.PaymentRT - q.PaymentAmount <= q.ItemRT - q.ItemAmount then q.ItemAmount
-- Payment is smaller than item
else q.ItemRT - (q.PaymentRT - q.PaymentAmount)
end
end as [Amount]
--, q.*
from (
select
sq.ItemId, pm.PaymentId, sq.ItemAmount, sq.RT as [ItemRT],
pm.PaymentAmount, pm.RT as [PaymentRT],
row_number() over(partition by sq.CartId, sq.ItemId, pm.PaymentId order by pm.RT) as [RN],
pm.Match
--, sq.CartId
from (
select pr.*, sum(pr.ItemAmount) over(partition by pr.CartId order by pr.ItemId) as [RT]
from #Products pr
) sq
outer apply (
-- First payment to partially cover this item
select top (1) c.*, 1 as [Match] from cte c where c.CartId = sq.CartId
and c.RT > sq.RT - sq.ItemAmount and c.RT < sq.RT
order by sq.RT
union all
-- Any payments that cover this item only
select c.*, 2 as [Match] from cte c where c.CartId = sq.CartId
and c.RT - c.PaymentAmount > sq.RT - sq.ItemAmount
and c.RT < sq.RT
union all
-- Last payment that covers this item
select top (1) c.*, 3 as [Match] from cte c where c.CartId = sq.CartId
and c.RT >= sq.RT
order by sq.RT
) pm
) q
where q.RN = 1;
The outer apply section is where I get payments related to each item. The only problem is, if a payment covers an item in its entirety, it will be listed several times. In order to remove these duplicates, I have ordered matches using row_number() and added an additional wrapping level - subquery with the q alias - where I cut off any duplicated rows by filtering the row number value.
P.S. If your SQL Server version is prior to 2012, you will need to calculate running totals using one of many approaches available on the Internet, because sum() over(order by ...) is only available on 2012 and later versions.

Related

Calculate future inventory based an sales and production in SQL

I have 4 Tables: Storage, Sales, Prodcution, Production-cost
Storage example
Day
ProductID
Amount
2022-03-27
1
900
2022-03-27
2
1200
2022-03-28
1
950
2022-03-28
2
1200
Sales example
Date
ProductID
Amount
2022-04-2
1
50
2022-04-15
1
20
Production example
Date
ProductID
Amount
2022-04-1
1
70
2022-04-20
1
10
Production-Cost example
ProductID
1
2
glue
1
0
1
1
2
0
0
1
Production cost describes if and how much of a certain product you need to produce something. In this example you need 1 product 2 and 1 glue to produce product1.
I want to calculate my future inventory based on the future sales and production in SQL. I had this working with some golang code, but would prefer if the SQL query did it alone.
I tried different joins but i could not figure out how to subtract the values correctly and form new rows.
Here we get the most recent stock figures from storage, sum up the quantites produced and sold since (ignoring previous lines) and calculate the quantity in stock today.
I have included the table Production_cost in the join but have not used it. Should all product 2's be converted into 1's?
create table Products(
id int primary key);
insert into Products values
(1),
(2);
create table storage(
Day date,
ProductID int ,
Amount int,
foreign key fk_storage_productID(ProductID) references Products(id));
insert into storage values
('2022-03-27', 1,900),
('2022-03-27', 2,1200),
('2022-03-28', 1,950),
('2022-03-28', 2,1200);
create table Sales (
Day Date,
ProductID int,
Amount int,
foreign key fk_sales_productID(ProductID) references Products(id));
insert into Sales values
('2022-04-02',1,50),
('2022-04-15',1,20);
create table Production(
Day Date,
ProductID int,
Amount int,
foreign key fk_production_productID(ProductID) references Products(id));
insert into Production values
('2022-04-1', 1,70),
('2022-04-20', 1,10);
create table Production_Cost(
ProductID int,
p1 int,
p2 int,
glue int,
foreign key fk_production_cost_productID(ProductID) references Products(id));
insert into Production_Cost values
(1, 0, 1, 1),
(2, 0, 0, 1);
select
p.id as product,
st.Amount as latest_count,
coalesce(sum(sa.Amount),0) as sold,
coalesce(sum(pr.Amount),0) as produced,
st.Amount -
coalesce(sum(sa.Amount),0) +
coalesce(sum(pr.Amount),0) as calculated_stock
from Products p
left join (
select ProductID, Day, amount,
rank() over (partition by ProductID order by Day desc) rn
from storage )st on p.id = st.productID
left join Sales sa on p.id = sa.productID
left join Production pr on p.id = pr.productID
left join Production_Cost pc on p.id = pc.productID
where st.rn = 1
and (sa.Day > st.Day or sa.Day is null or st.Day is null)
and (pr.Day > st.Day or pr.Day is null or st.Day is null)
group by
p.id,
st.Amount
product | latest_count | sold | produced | calculated_stock
------: | -----------: | ---: | -------: | ---------------:
1 | 950 | 140 | 160 | 970
2 | 1200 | 0 | 0 | 1200
db<>fiddle here

Insert only up to a maximum number?

I have an existing stored procedure that returns shopping cart items as individual rows, with one of the fields being Quantity of the said item.
In the case of a promotional offer, for example buy 2 and get another 2 free, then the same product will be returned as two rows, with a separate price and a quantity for each. In this case, 2 rows with a quantity of 2 in each row.
The procedure then checks each row against the quantity in stock, and alters the row if required by inserting into a new table for the updated order. Normally there are many items of each product in stock at a time, but in rare situations (normally for end run products) there could be fewer items in stock than the cart total should allow.
In this example, lets say there's 3 left. The procedure would compare each row as normal and see that there are 3 in stock and the row is only looking for 2. So each row passes the check even though we're one short.
The problem I have is, how can I keep a running total of how many items are in the cart when each row is compared individually? Can I declare and update an int value for every insert?
Edit Sample Data to illustrate issue:
Lets say that order number 1 returns the same product on 3 rows. Full price, discounted and free (probably not a realistic situation but one that my boss will want me to account for anyway)
LineID | ProductID | Price | Quantity | Note
001 | 00001 | 100 | 2 |
002 | 00001 | 50 | 2 |
003 | 00001 | 0 | 2 |
These rows are in a temp table item which is joined to the products products table by ProductID which would look something like this:
ProductID | BasePrice | QuantityAvailable
00001 | 100 | 3
Then the products are checked with this query:
UPDATE item
SET
note =
CASE WHEN product.quantityAvailable <= 0 THEN 'This item is no longer available'
WHEN item.quantity > product.quantityAvailable THEN 'Not Enough Stock'
WHEN product.quantityAvailable > item.Quantity THEN REPLACE(note, 'Not Enough Stock', '')
ELSE 'Not Enough Stock' END
, quantity =
CASE WHEN product.quantityAvailable < item.Quantity THEN product.quantityAvailable
ELSE item.Quantity END
OUTPUT inserted.ID, deleted.note, inserted.note, deleted.quantity, inserted.quantity,
INTO #modifiedItems
FROM item
INNER JOIN product ON product.ProductID = item.ID
The end goal is for the item table to be updated to reflect the maximum number available across all rows, with the result being:
LineID | ProductID | Price | Quantity | Note
001 | 00001 | 100 | 2 |
002 | 00001 | 50 | 1 | Not enough stock
003 | 00001 | 0 | 0 | Not enough stock
edit 2: electric boogaloo
I have attempted to use a local variable to calculate a running total, but this seems to jump straight to the total value. Example below:
DECLARE #runningTotalQuantity int = 0
UPDATE item
SET
note =
CASE WHEN product.quantityAvailable <= 0 THEN 'This item is no longer available'
WHEN item.quantity > product.quantityAvailable THEN 'Not Enough Stock'
WHEN product.quantityAvailable > item.Quantity THEN REPLACE(note, 'Not Enough Stock', '')
ELSE 'Not Enough Stock' END
, quantity =
CASE WHEN #runningTotalQuantity != 0 AND #runningTotalQuantity <= ItemLimits.limitedQty AND (#runningTotalQuantity + Item.Quantity) <= ItemLimits.limitedQty then Item.Quantity
WHEN (#runningTotalQuantity + Item.quantity) >= ItemLimits.limitedQty THEN (ItemLimits.limitedQty - #runningTotalQuantity) WHEN product.quantityAvailable < item.Quantity THEN product.quantityAvailable
ELSE item.Quantity END
, #runningTotalQuantity = #runningTotalQuantity + item.Quantity
OUTPUT inserted.ID, deleted.note, inserted.note, deleted.quantity, inserted.quantity,
INTO #modifiedItems
FROM item
INNER JOIN product ON product.ProductID = item.ID
But this has the following result:
LineID | ProductID | Price | Quantity | Note
001 | 00001 | 100 | 2 |
002 | 00001 | 50 | 6 |
003 | 00001 | 0 | 6 |
It is easy to calculate running total in SQL Server 2014, just use SUM() OVER (...). The ordering of results is important for the running total, I used LineID to order the rows. You can choose some other ordering that suits you.
The first CTE in the query below calculates running total for each product and the difference DiffQuantity tells us at which row the product is depleted.
The new value of Quantity is one of three possibilities:
1) If there is still enough stock, the Quantity doesn't change.
2) If there is no stock at all, the Quantity is zero.
3) There can be one row in between where Quantity decreases partially.
Finally, the source table is updated with new values of Quantity and Notes.
Run this query CTE-by-CTE and examine intermediate results to understand how it works.
Sample data
DECLARE #Items TABLE (LineID int PRIMARY KEY, ProductID int, Price money, Quantity int, Note nvarchar(4000));
INSERT INTO #Items (LineID, ProductID, Price, Quantity, Note) VALUES
(001, 00001, 100, 2, ''),
(002, 00001, 50, 2, ''),
(003, 00001, 0, 2, '');
DECLARE #Products TABLE (ProductID int PRIMARY KEY, BasePrice money, QuantityAvailable int);
INSERT INTO #Products (ProductID, BasePrice, QuantityAvailable) VALUES
(00001, 100, 3);
Query
WITH
CTE
AS
(
SELECT
I.LineID
,I.ProductID
,I.Price
,I.Quantity
,I.Note
,P.QuantityAvailable
,SUM(I.Quantity) OVER (PARTITION BY I.ProductID ORDER BY I.LineID
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS SumQuantity
,SUM(I.Quantity) OVER (PARTITION BY I.ProductID ORDER BY I.LineID
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)
- P.QuantityAvailable AS DiffQuantity
FROM
#Items AS I
INNER JOIN #Products AS P ON P.ProductID = I.ProductID
)
,CTE2
AS
(
SELECT
LineID
,ProductID
,Price
,Quantity
,Note
,QuantityAvailable
,DiffQuantity
,CASE WHEN DiffQuantity > 0 THEN 'Not enough stock' ELSE '' END AS NewNote
,CASE WHEN DiffQuantity > 0
THEN
CASE WHEN Quantity > DiffQuantity
THEN DiffQuantity
ELSE 0 END
ELSE Quantity END AS NewQuantity
FROM CTE
)
UPDATE CTE2
SET
Quantity = NewQuantity
,Note = NewNote
;
Result
SELECT * FROM #Items;
+--------+-----------+--------+----------+------------------+
| LineID | ProductID | Price | Quantity | Note |
+--------+-----------+--------+----------+------------------+
| 1 | 1 | 100.00 | 2 | |
| 2 | 1 | 50.00 | 1 | Not enough stock |
| 3 | 1 | 0.00 | 0 | Not enough stock |
+--------+-----------+--------+----------+------------------+
Have a look at the below query , it may help you
update item set note = case when precedingSum> t.QuantityAvailable then 'Not Enough Stock' Else '' End
from
(
select item.*,Product.QuantityAvailable,sum(Quantity) over(partition by item.ProductId order by item.LineId rows UNBounded Preceding)precedingSum
from Item inner join Product on item.ProductId=Product.ProductId
) t where t.LineId = Item.LineId

get continuosly increasing sale records from table sql server

I have 2 tables
Product
ProdId, ProdName
1 A
2 B
and
Sale
SaleId, ProdId, Sale, Year
1, 1, 100, 2012
2, 1, 130, 2013
3, 2, 100, 2012,
4, 1, 150, 2014,
5, 1, 180, 2015
6, 2, 120, 2013,
7, 2, 90, 2014,
8, 2, 130, 2015
I want the name of product whose sale is continuosly increasing.
Like Product "A" has sale record like in year 2012 - 100 Units,2013 - 130 Units,2014 - 150 Units,2015 - 180 Units, So this product A is having continuous increase in sale. Another case of non-continuous record is, product "B" having sale record 2012 - 100 Units,2013 - 120 Units,2014 - 90 Units, 2015 - 130 Units, So for product "B", it is not continuous.
I want records like product "A", who is having continuous increasing sale.
Help appreciated.
You can do this using row_number() twice:
select prod_id
from (select s.*,
row_number() over (partition by s.prod_id order by sale) as seqnum_s,
row_number() over (partition by s.prod_id order by year) as seqnum_y
from sales s
) s
group by prod_id
having sum( case when seqnum_s = seqnum_y then 1 else 0 end) = count(*);
That is, order by the year and the sales. When all row numbers are the same, then the sales are increasing.
Note: There are some cases where tied sales might be considered increasing. This can be handled by the logic -- either by excluding or including such situations. I have not included logic for this, because your question is not clear what to do in that situation.
Use cross apply to get the previous year's sale amount and check with conditional aggregation for the increasing amount condition.
select prodid
from sale s1
cross apply (select sale as prev_sale
from sale s2
where s1.prodid=s2.prodid and s2.year=s1.year-1) s2
group by prodid
having sum(case when sale-prev_sale<0 then 1 else 0 end) = 0
To get the all the rows for such prodId's, use
select * from sale
where prodid in (select prodid
from sale s1
cross apply (select sale as prev_sale
from sale s2
where s1.prodid=s2.prodid and s2.year=s1.year-1) s2
group by prodid
having sum(case when sale-prev_sale<0 then 1 else 0 end) = 0
)
Here's a way with a CTE
declare #sale table (SaleID int, ProdId int, Sale int, Year int)
insert into #sale
values
(1,1,100,2012),
(2,1,130,2013),
(3,2,100,2012),
(4,1,150,2014),
(5,1,180,2015),
(6,2,120,2013),
(7,2,90,2014),
(8,2,130,2015)
declare #product table (ProdID int, ProdName char(1))
insert into #product
values
(1,'A'),
(2,'B')
;with cte as(
select
row_number() over (partition by ProdId order by Year) as RN
,*
from #sale)
select
p.ProdName
,cte.*
from cte
inner join
#product p on
p.ProdID=cte.ProdId
where cte.ProdId IN
(select distinct
c1.ProdId
from cte c1
left join
cte c2 on c2.RN = c1.rn+1 and c2.ProdId = c1.ProdId
group by c1.ProdId
having min(case when c1.Sale < isnull(c2.Sale,999999) then 1 else 0 end) = 1)
RETURNS
+----------+----+--------+--------+------+------+
| ProdName | RN | SaleID | ProdId | Sale | Year |
+----------+----+--------+--------+------+------+
| A | 1 | 1 | 1 | 100 | 2012 |
| A | 2 | 2 | 1 | 130 | 2013 |
| A | 3 | 4 | 1 | 150 | 2014 |
| A | 4 | 5 | 1 | 180 | 2015 |
+----------+----+--------+--------+------+------+

Grouping in SQL Table [closed]

Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 5 years ago.
Improve this question
Suppose I have a Table such that:
|ID | product |orderid | brand |number of product cust ord|
|----|---------|--------|-------|--------------------------|
| 1 | 123 | 111 | br | 1 |
|----|---------|--------|-------|--------------------------|
| 1 | 234 | 111 | br | 1 |
|----|---------|--------|-------|--------------------------|
| 1 | 345 | 333 | br | 1 |
|----|---------|--------|-------|--------------------------|
| 2 | 123 | 211 | br | 1 |
|----|---------|--------|-------|--------------------------|
| 2 | 456 | 212 | br | 2 |
|----|---------|--------|-------|--------------------------|
| 3 | 567 | 213 | br | 1 |
|----|---------|--------|-------|--------------------------|
What I'd like to do is group them as:
|ID | brand |number of product cust ord|
|----|---------|--------------------------|
| 1 | br | 3 |
|----|---------|--------------------------|
| 2 | br | 4 |
|----|---------|--------------------------|
further to that i'd like to classify them and tried a case...when but can't seem to get it right.
if ID purchases more than 3 unique products and orders more than twice- i'd like to call them a frequent buyer (in the above example, ID '1' would be a 'frequent buyer'), if the average number of products they purchase is higher than the average number of that product sold - i'd like to call them a 'merchant', else just a purchaser.
I've renamed the last field to qty for brevity and called the table test1.
To get frequent flyers use below query. Note that I used >= instead of >. I changed this based on your example where ID 1 is a "frequent flyer" even though he only bought 3 products, not more than 3.
SELECT ID, count(distinct product) as DistinctProducts, count(distinct orderid) DistinctOrders
FROM test1
GROUP BY ID
HAVING count(distinct product) >= 3 and count(distinct orderid) >= 2
Not sure if I understood the merchant logic correctly. Below is the query which will give you customers that on average purchased more than overall average of product for any given product. There are none in the data.
SELECT DISTINCT c.ID
FROM
(select ID, product, avg(qty) as AvgQty
FROM test1
GROUP BY ID, product) as c
FULL OUTER JOIN
(select product, avg(qty) as AvgQty
FROM test1
GROUP BY product) p ON p.product = c.product
WHERE c.AvgQty > p.AvgQty;
To get "purchasers" do EXCEPT between all customer and the UNION of merchants and frequent buyers:
select distinct ID from test1
EXCEPT
(SELECT ID FROM (
select ID, count(distinct product) as DistinctProducts, count(distinct orderid) DistinctOrders
FROM test1
GROUP BY ID
HAVING count(distinct product) >= 3 and count(distinct orderid) >= 2) t
UNION
SELECT DISTINCT c.ID
FROM
(select ID, product, avg(qty) as AvgQty
FROM test1
GROUP BY ID, product) as c
FULL OUTER JOIN
(select product, avg(qty) as AvgQty
FROM test1
GROUP BY product) p ON p.product = c.product
WHERE c.AvgQty > p.AvgQty
);
This is one way that you could do it. Note that according to the description you gave, buyers could be constantly being reclassified between 'Merchant' and 'Purchaser' as the average goes up and down. That might not be what you want.
With cte As (
Select ID,
Brand,
DistinctOrders = Count(Distinct OrderID), -- How many separate orders by this customer for the brand?
DistinctProducts = Count(Distinct Product), -- How many different products by this customer for the brand?
[number of product cust ord] = Sum(CountOfProduct), -- Total number of items by this customer for the brand.
AverageCountOfProductPerBuyer =
Sum(Sum(CountOfProduct)) Over () * 1.0 / (Select Count(*) From (Select Distinct ID, Brand From #table) As tbl)
-- Average number of items per customer (for all customers) for this brand
From #table
Group By ID, Brand)
Select ID, Brand, DistinctOrders, DistinctProducts, [number of product cust ord],
IsFrequentBuyer = iif(DistinctOrders > 1 And DistinctProducts > 2, 'Frequent Buyer', NULL),
IsMerchant = iif(AverageCountOfProductPerBuyer < [number of product cust ord], 'Merchant', 'Purchaser')
From cte;
This query could be written without the common-table expression, but was written this way to avoid defining expressions multiple times.
Note that I have the first ID as a 'Frequent Buyer' based on your description, so I'm assuming that when you say 'more than 3 unique products' you mean 3 or more. Likewise with two or more distinct orders.

SQL - Knocking off Accordingly

I have an issue in SQL where all transaction just come into one giant messy tables.
Example:
1 | Invoice | $300
2 | Invoice | $250
3 | Payment | $100
4 | Invoice | $200
5 | Payment | $300
So i will have 3 invoices and 2 paymentsbut
the Payment at line 3 can only be knocking off the Invoice on line 1
and Payment on line 5 is for the Invoice in line 2.
I want to Net Off the Payment and Find rather it is an Overpayment or Underpayment or it is knocking off entire invoice.
How can i do this?
This is easy using ROW_NUMBER(). Assume there must be an invoice before payment.
SELECT
i.Id AS InvoiceId, p.Id AS PaymentId,
CASE
WHEN RefAmount = 0 THEN 'Settled'
WHEN RefAmount > 0 THEN 'Underpayment'
ELSE 'Overpayment'
END AS State
FROM
(
SELECT ROW_NUMBER() OVER (ORDER BY Id) AS GroupId, *
FROM messy WHERE Type = 'Invoice'
) i
LEFT OUTER JOIN
(
SELECT ROW_NUMBER() OVER (ORDER BY Id) AS GroupId, *
FROM messy WHERE Type = 'Payment'
) p ON i.GroupId = p.GroupId
CROSS APPLY (SELECT i.Amount - ISNULL(p.Amount, 0) AS RefAmount) r
SQL Fiddle