开发者

How to retrieve rows from table 1 that have no related rows in table 2 in MySQL

I have an interesting question - I need the exact reverse of a JOIN statement.

I have a table called invoices, and another table called payments. I want to retrieve, in order, invoices that have NO payments attached to it whatsoever, but while followed by rows that have payments attached to it after that.

invoices table has these fields:

  • id
  • customerid
  • timestamp

payments table has these fields:

  • id
  • invoiceid
  • amount

Not all payments are full amounts, so each invoice could have multiple payments attached.

I want the unpaid invoi开发者_JAVA技巧ces to come up first (no payments) and then followed by invoices with partial payments but not full, and then followed by fully paid invoices.

I wish to perform this without having to run separate SQL queries if I can - I'd rather it be one query so I can spit out the once. Any ideas?

-- UPDATED --

Based on an answer from DCP, I've updated the query method. I had it working until I needed to add that JOIN statement to also order by my sub_to_inv table which looks like this:

id subscription invoice 0 12 18 1 13 18 2 14 19

etc.

So based on this I came up with this (added the LIMIT at the end ot limit by pages..)

SELECT i.id AS id, i.modify_date as modify_date, s.subscription as subscriptionid, 0 paidratio FROM invoices i
LEFT JOIN sub_to_inv s ON i.id=s.invoice
WHERE NOT EXISTS (SELECT id FROM payments p WHERE p.invoice=i.id) AND i.corporation='3' AND i.payer=1

UNION ALL
SELECT i.id AS id, i.modify_date as modify_date, s.subscription as subscriptionid, p.paid/i.total AS paidratio FROM invoices i,
(SELECT p.invoice, sum(amount) AS paid FROM payments p GROUP BY p.invoice) p
LEFT JOIN sub_to_inv s ON i.id=s.invoice
WHERE p.invoice=i.id AND i.corporation='3' AND i.payer=1

AND p.paid UNION ALL
SELECT i.id AS id, i.modify_date as modify_date, s.subscription as subscriptionid, p.paid/i.total AS paitratio FROM invoices i,
(SELECT p.invoice, sum(amount) AS paid FROM payments p WHERE p.invoice=id GROUP BY p.invoice) p
LEFT JOIN sub_to_inv s ON i.id=s.invoice
WHERE p.invoice = i.id AND i.corporation='3' AND i.payer=1

AND p.paid=i.total

ORDER BY paidratio DESC, modify_date DESC, subscriptionid ASC LIMIT 0,40

I get a "i.id column not found" error which I don't get. On each block of statements that are ultimately combined through teh UNION statement, I specify invoices i to ensure that i.id refers to the id of the invoices. tab.e


You didn't tell us how to determine the total invoice amount, but I assume you have some field on your invoice table for this. In the example below, I assumed it's called TOT_AMT.

The idea behind this approach is that we get the data in three separate steps, and we use a UNION ALL to glue the three pieces together. We use a pseudocolumn called PAY_TYPE to get the ordering you want, which is invoices with no payments first, then invoices with partial payments, and finally, invoices which are fully paid.

A complete working example is shown below (I used Oracle, but I think you can adapt it to MySQL easily).

CREATE TABLE invoices (ID number, tot_amt NUMBER);
CREATE TABLE payments (ID NUMBER, invoice_id NUMBER, amount NUMBER);

INSERT INTO invoices VALUES (1,100);
INSERT INTO invoices VALUES (2,200);
INSERT INTO invoices VALUES (3,300);

INSERT INTO payments VALUES (1,1,20);
INSERT INTO payments VALUES (2,1,40);

INSERT INTO payments VALUES (3,3,150);
INSERT INTO payments VALUES (4,3,120);
INSERT INTO payments VALUES (5,3,30);

/* get invoices with no payments */
SELECT i.id
     , i.TOT_AMT
     , 0               TOT_PAID
     , '0_NO_PAYMENTS' PAY_TYPE
  FROM invoices i
  WHERE NOT EXISTS (SELECT * FROM PAYMENTS P WHERE P.INVOICE_ID = I.ID)
UNION ALL
/* get invoices with partial payments */
SELECT i.id
     , i.TOT_AMT
     , p.tot_paid
     , '1_PARTIALLY_PAID' PAY_TYPE
  FROM invoices i
     , (SELECT P.INVOICE_ID
             , SUM(AMOUNT) tot_paid
          FROM PAYMENTS P
         GROUP BY P.INVOICE_ID) p
 WHERE P.INVOICE_ID = I.ID
   AND p.TOT_PAID < i.TOT_AMT
UNION ALL
/* get invoices that are fully paid */
SELECT i.id
     , i.TOT_AMT
     , p.tot_paid
     , '2_FULLY_PAID' PAY_TYPE
  FROM invoices i
     , (SELECT P.INVOICE_ID
             , SUM(AMOUNT) tot_paid
          FROM PAYMENTS P
         GROUP BY P.INVOICE_ID) p
 WHERE P.INVOICE_ID = I.ID
   AND p.TOT_PAID = i.TOT_AMT
ORDER BY PAY_TYPE   

Results:

    ID      TOT_AMT   TOT_PAID   PAY_TYPE
    2       200       0          0_NO_PAYMENTS
    1       100       60         1_PARTIALLY_PAID
    3       300       300        2_FULLY_PAID


If I'd assume that you have an invoice_details table which contains the total amount:

SELECT      i.id as invoiceid,
            case 
                when (select sum(amount) from payments where invoiceid = i.id group by invoiceid) = d.amount
                    then 3
                when (select sum(amount) from payments where invoiceid = i.id group by invoiceid) < d.amount
                    then 2
                else 1
            end 
            as paidstatus

FROM        invoices i
INNER JOIN  invoice_details d
ON          i.id = d.invoiceid
LEFT JOIN   payments p
ON          i.id = p.invoiceid
GROUP BY    i.id, d.amount
ORDER BY    paidstatus

Where paidstatus:

1 = unpaid
2 = partially paid
3 = paid


select * from table1 where not exists (select * from table2 where table2.foreignkey = table1.primarykey)

Does that work?


From your question, I don't see any way to tell if an invoice is fully paid, but the following will get you a list of invoices with no payments followed by a list of invoices with payments.

select *
from
 invoices
where
 not exists (
  select 1
  from payments
  where invoices.id = payments.invoiceid
 )

union

select *
from
 invoices
where
 exists (
  select 1
  from payments
  where invoices.id = payments.invoiceid
 )


SELECT i.id FROM invoices i
  LEFT JOIN PAYMENTS p ON i.id = p.invoice_id
  GROUP BY i.id, i.amount
  ORDER BY SUM(p.amount) / i.amount

If this works I'll be amazed.


select sub.*, paid/tot_amt as ordering_column
from (
 select
    i.id,
    i.customerid,
    i.timestamp,
    i.tot_amt,
    (select sum(ifnull(p.amount,0)) from payments p where p.invoiceid = i.id ) as paid
 from invoices i
) sub
order by ordering_column

this will give you the invoices sorted in way you wanted, and also a sum of the paid amount per invoice

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜