代码之家  ›  专栏  ›  技术社区  ›  Nick

SQL连接,按三个表分组以获取总数

  •  10
  • Nick  · 技术社区  · 17 年前

    我继承了下面的数据库设计。表格包括:

    customers
    ---------
    customerid  
    customernumber
    
    invoices
    --------
    invoiceid  
    amount
    
    invoicepayments
    ---------------
    invoicepaymentid  
    invoiceid  
    paymentid
    
    payments
    --------
    paymentid  
    customerid  
    amount
    

    我的查询需要返回给定客户编号的invoiceid、发票金额(在invoices表中)和应付金额(发票金额减去已向发票支付的任何款项)。客户可能有多张发票。

    当对发票进行多次付款时,以下查询会提供重复的记录:

    SELECT i.invoiceid, i.amount, i.amount - p.amount AS amountdue
    FROM invoices i
    LEFT JOIN invoicepayments ip ON i.invoiceid = ip.invoiceid
    LEFT JOIN payments p ON ip.paymentid = p.paymentid
    LEFT JOIN customers c ON p.customerid = c.customerid
    WHERE c.customernumber = '100'
    

    我该怎么解决?

    5 回复  |  直到 12 年前
        1
  •  15
  •   Drew Noakes    13 年前

    我不确定我抓到你了,但这可能是你要找的:

    SELECT i.invoiceid, sum(case when i.amount is not null then i.amount else 0 end), sum(case when i.amount is not null then i.amount else 0 end) - sum(case when p.amount is not null then p.amount else 0 end) AS amountdue
    FROM invoices i
    LEFT JOIN invoicepayments ip ON i.invoiceid = ip.invoiceid
    LEFT JOIN payments p ON ip.paymentid = p.paymentid
    LEFT JOIN customers c ON p.customerid = c.customerid
    WHERE c.customernumber = '100'
    GROUP BY i.invoiceid
    

    如果每张发票有多个付款行,这将为您获得金额总和

        2
  •  6
  •   Nick Silberstein    17 年前

    非常感谢您的回复!

    Saggi Malachi,不幸的是,如果有不止一笔付款,这个查询将汇总发票金额。假设一张39美元的18美元和12美元的发票有两次付款。因此,与其最终得到一个看起来像是:

    1   39.00   9.00
    

    你最终会得到:

    1   78.00   48.00
    

    Charles Bretana,在将我的查询精简为尽可能简单的查询的过程中,我(愚蠢地)省略了一个额外的表customerinvoices,它提供了客户和发票之间的链接。这可用于查看尚未付款的发票。

    经过许多努力,我认为下面的查询将返回我需要的内容:

    SELECT DISTINCT i.invoiceid, i.amount, ISNULL(i.amount - p.amount, i.amount) AS amountdue
    FROM invoices i
    LEFT JOIN invoicepayments ip ON i.invoiceid = ip.invoiceid
    LEFT JOIN customerinvoices ci ON i.invoiceid = ci.invoiceid
    LEFT JOIN (
      SELECT invoiceid, SUM(p.amount) amount
      FROM invoicepayments ip 
      LEFT JOIN payments p ON ip.paymentid = p.paymentid
      GROUP BY ip.invoiceid
    ) p
    ON p.invoiceid = ip.invoiceid
    LEFT JOIN payments p2 ON ip.paymentid = p2.paymentid
    LEFT JOIN customers c ON ci.customerid = c.customerid
    WHERE c.customernumber='100'
    

    你们同意吗?

        3
  •  3
  •   Nicos Karalis    13 年前

    对于那些希望从同一个表中获取各种聚合值的人,我有一个提示。

    假设我有用户表和用户获取点表。所以它们之间的连接是1:N(一个用户,多个点记录)。

    现在,在“points”表中,我还存储了有关用户获得点数的信息(登录、单击横幅等)。我想列出 SUM(points) 然后 SUM(points WHERE type = x) . 也就是说,按用户拥有的所有点数排序,然后按用户为特定操作(例如登录)获得的点数排序。

    SQL将是:

    SELECT SUM(points.points) AS points_all, SUM(points.points * (points.type = 7)) AS points_login
    FROM user
    LEFT JOIN points ON user.id = points.user_id
    GROUP BY user.id
    

    它的美丽在于 SUM(points.points * (points.type = 7)) 其中,内圆括号的计算结果为0或1,从而将给定的点值乘以0或1,具体取决于它是否等于所需的点类型。

        4
  •  2
  •   Charles Bretana    17 年前

    首先,发票表中不应该有CustomerId吗?实际上,您无法对尚未付款的发票执行此查询。如果发票上没有付款,则该发票甚至不会显示在查询的输出中,即使它是外部连接。。。

    另外,当客户付款时,你如何知道要附在哪张发票上?如果唯一的方法是通过付款存根上的InvoiceId,那么您是(也许是不恰当地)将发票与付款的客户关联起来,而不是与订购它们的客户关联起来。。。(有时发票可以由订购服务的客户以外的人支付)

        5
  •  0
  •   Edward    8 年前

    我知道这很晚了,但它确实回答了你原来的问题。

    /*Read the comments the same way that SQL runs the query
        1) FROM 
        2) GROUP 
        3) SELECT 
        4) My final notes at the bottom 
    */
    SELECT 
            list.invoiceid
        ,   cust.customernumber 
        ,   MAX(list.inv_amount) AS invoice_amount/* we select the max because it will be the same for each payment to that invoice (presumably invoice amounts do not vary based on payment) */
        ,   MAX(list.inv_amount) - SUM(list.pay_amount)  AS [amount_due]
    FROM 
    Customers AS cust 
        INNER JOIN 
    Payments  AS pay 
        ON 
            pay.customerid = cust.customerid
    INNER JOIN  (   /* generate a list of payment_ids, their amounts, and the totals of the invoices they billed to*/
        SELECT 
                inpay.paymentid AS paymentid
            ,   inv.invoiceid AS invoiceid 
            ,   inv.amount  AS inv_amount 
            ,   pay.amount AS pay_amount 
        FROM 
        InvoicePayments AS inpay
            INNER JOIN 
        Invoices AS inv 
            ON  inv.invoiceid = inpay.invoiceid 
            INNER JOIN 
        Payments AS pay 
            ON pay.paymentid = inpay.paymentid
        )  AS list
    ON 
        list.paymentid = pay.paymentid
        /* so at this point my result set would look like: 
        -- All my customers (crossed by) every paymentid they are associated to (I'll call this A)
        -- Every invoice payment and its association to: its own ammount, the total invoice ammount, its own paymentid (what I call list) 
        -- Filter out all records in A that do not have a paymentid matching in (list)
         -- we filter the result because there may be payments that did not go towards invoices!
     */
    GROUP BY
        /* we want a record line for each customer and invoice ( or basically each invoice but i believe this makes more sense logically */ 
            cust.customernumber 
        ,   list.invoiceid 
    /*
        -- we can improve this query by only hitting the Payments table once by moving it inside of our list subquery, 
        -- but this is what made sense to me when I was planning. 
        -- Hopefully it makes it clearer how the thought process works to leave it in there
        -- as several people have already pointed out, the data structure of the DB prevents us from looking at customers with invoices that have no payments towards them.
    */