在sql join中使用列而不将其添加到group by子句中

时间:2014-05-09 19:11:10

标签: sql postgresql aggregate-functions

我的实际表结构要复杂得多,但以下是两个简化的表定义:

invoice

CREATE TABLE invoice (
  id integer NOT NULL,
  create_datetime timestamp with time zone NOT NULL,
  total numeric(22,10) NOT NULL
);

id   create_datetime   total    
----------------------------
100  2014-05-08        1000

payment_invoice

CREATE TABLE payment_invoice (
  invoice_id integer,
  amount numeric(22,10)
);

invoice_id  amount
-------------------
100         100
100         200
100         150

我想通过连接上面两个表来选择数据,所选数据应如下所示: -

month      total_invoice_count  outstanding_balance
05/2014    1                    550

我正在使用的查询:

select
to_char(date_trunc('month', i.create_datetime), 'MM/YYYY') as month,
count(i.id) as total_invoice_count,
(sum(i.total) - sum(pi.amount)) as outstanding_balance
from invoice i
join payment_invoice pi on i.id=pi.invoice_id
group by date_trunc('month', i.create_datetime)
order by date_trunc('month', i.create_datetime);

以上查询给出的结果不正确,因为sum(i.total) - sum(pi.amount)返回(1000 + 1000 + 1000) - (100 + 200 + 150)= 2550
我希望它返回(1000) - (100 + 200 + 150)= 550

我无法将其更改为i.total - sum(pi.amount),因为我被迫将i.total列添加到group by子句中并且我不想这样做。

3 个答案:

答案 0 :(得分:2)

每张发票需要一行,因此首先汇总payment_invoice - 最好在加入之前 选择整个表格后,aggregate first and join later通常最快:

SELECT to_char(date_trunc('month', i.create_datetime), 'MM/YYYY') AS month
     , count(*)                                   AS total_invoice_count
     , (sum(i.total) - COALESCE(sum(pi.paid), 0)) AS outstanding_balance
FROM   invoice i
LEFT   JOIN  (
    SELECT invoice_id AS id, sum(amount) AS paid
    FROM   payment_invoice pi
    GROUP  BY 1
    ) pi USING (id)
GROUP  BY date_trunc('month', i.create_datetime)
ORDER  BY date_trunc('month', i.create_datetime);

LEFT JOIN在这里至关重要。您不希望在payment_invoice(尚未)中放弃没有相应行的发票,这将发生在普通JOIN上。

因此,使用COALESCE()作为付款总额,可能为NULL。

SQL Fiddle改进了测试用例。

答案 1 :(得分:1)

请参阅sqlFiddle

SELECT TO_CHAR(invoice.create_datetime, 'MM/YYYY') as month,
       COUNT(invoice.create_datetime) as total_invoice_count,
       invoice.total - payments.sum_amount as outstanding_balance
FROM invoice
JOIN 
(
    SELECT invoice_id, SUM(amount) AS sum_amount
    FROM payment_invoice
    GROUP BY invoice_id
) payments
ON invoice.id = payments.invoice_id
GROUP BY TO_CHAR(invoice.create_datetime, 'MM/YYYY'), 
         invoice.total - payments.sum_amount

答案 2 :(得分:1)

分两步进行聚合。首先汇总为每张发票一行,然后每月汇总到一行:

select
  to_char(date_trunc('month', t.create_datetime), 'MM/YYYY') as month,
  count(*) as total_invoice_count,
  (sum(t.total) - sum(t.amount)) as outstanding_balance
from (
    select i.create_datetime, i.total, sum(pi.amount) amount
    from invoice i
    join payment_invoice pi on i.id=pi.invoice_id
    group by i.id, i.total
) t
group by date_trunc('month', t.create_datetime)
order by date_trunc('month', t.create_datetime);