Reputation: 5913
The simplified table looks like that:
BillID|ProductID|CustomerID|Price|TypeID
------+---------+----------+-----+-------
111111|Product1 |Customer1 | 100| I
111112|Product1 |Customer1 | -100| C
111113|Product1 |Customer1 | 100| I
111114|Product1 |Customer1 | -100| C
111115|Product1 |Customer1 | 100| I
I need to find invoices (I) that have their matching credits (C) but not "odd" invoices without matching credits (the last record) - or the other way around (unmatched invoices without corresponding credits).
So far I've got this:
SELECT Invoices.billid, Credits.billid
FROM
(SELECT B1.billid
FROM billing B1
WHERE B1.typeid='I') Invoices
INNER JOIN
(SELECT B2.billid
FROM billing B2
WHERE B2.typeid='C') Credits
ON Invoices.customerid = Credits.customerid
AND Invoices.productid = Credits.productid
AND Invoices.price = -(Credits.price)
But it obviously doesn't work, as it returns something looking like:
billid | billid2
-------+ -------
111111 | 111112
111113 | 111114
111115 | 111114
What I would like to get is a list of unmatched invoices;
billid |
-------+
111115 |
Or alternatively only the matching invoices;
billid | billid2
-------+ -------
111111 | 111112
111113 | 111114
The invoice numbers (BillID) will not necessarily be consecutive of course, it's just a simplified view.
Any help would be appreciated.
Upvotes: 0
Views: 268
Reputation: 858
This should work. I tested by adding a few consecutive invoices before a credit. The query below shows all invoices with matching credit and shows NULL for the aliased "bar" part of the query if a match doesn't exist.
SELECT * FROM (
SELECT
ROW_NUMBER() OVER(Partition By TypeID, CustomerID, ProductID, Price ORDER BY BillID ASC) AS rownumber,
*
FROM Billing
) AS foo
LEFT JOIN
(SELECT
ROW_NUMBER() OVER(Partition By TypeID, CustomerID, ProductID, Price ORDER BY BillID ASC) AS rownumber,
*
FROM Billing
) AS bar
on foo.CustomerID = bar.CustomerID and
foo.ProductID = bar.ProductID and
foo.rownumber = bar.rownumber and
foo.Price = -1*bar.Price
where foo.Price > 1
Here's the updated data that I used:
And Here are what my results looked like:
Upvotes: 3
Reputation: 27249
I wrote this a long time ago so there may be better ways to solve it now. Also I've attempted to adapt it to your table structure, so apologies if its not 100% there. I also assume that your BillID is sequential in date order i.e. larger numbers were entered later. I've also assumed that invoices are always positive and credit notes always negative - so I don't bother checking the type.
Essentially the query filters out any matched items.
Anyway here goes:
select *
from billing X
/* If we are inside the number of unmatched entries then show it. e.g. if there are 3 unmatched entries, and we are in the top 3 then display */
where (
/* Number of later entries relating that match this account entry e.g. Price/Product/Customer */
select count(*)
from billing Z
where Z.Customer = X.Customer and Z.ProductID = X.ProductID
and Z.Price = X.Price
and Z.BillID >= X.BillId
) <=
(
/* Number of unmatched entries for this Price/Product/Customer there are, and whether they are negative or positive. */
select abs(Y.Number)
from (
-- Works out how many unmatched billing entries for this Price/Product/Customer there are, and whether they are negative or positive
select ProductID, CustomerID, abs(Price) Price, sum(case when Price < 0 then -1 else +1 end) Number
from billing
group by ProductID, CustomerID, abs(Price)
having sum(Price) <> 0
) as Y
where X.ProductID = Y.ProductID
and X.CustomerID = Y.CustomerID
and X.Price = case when Y.Number < 0 then -1*Y.Amount else Y.Amount end
)
Upvotes: 1
Reputation: 373
The odd/even thing concerns me a bit. But assuming this is an incremental key and your business logic is in place, try including this logic in the WHERE clause, the JOIN PREDICATE, or implementing a Lead/Lag function.
SELECT DISTINCT
Invoices.billid
,Credits.billid
FROM
(SELECT B1.billid
FROM billing B1
WHERE B1.typeid='I') Invoices
INNER JOIN (SELECT B2.billid
FROM billing B2
WHERE B2.typeid='C') Credits
ON Invoices.customerid = Credits.customerid
AND Invoices.productid = Credits.productid
AND Invoices.price = -(Credits.price)
AND (Invoices.Billid + 1) = Credits.Billid
Note: This is using your INNER JOIN, so we will get the cases where the invoices have a corresponding credit. You could also do a FULL OUTER JOIN instead, then include a WHERE CLAUSE that specifies WHERE Invoices.Billid IS NULL OR Credits.Billid IS NULL
. That scenario would give you the trailing case where you don't have a match.
Upvotes: 1