【问题标题】:Group related records, but pick certain fields from only the first record对相关记录进行分组,但仅从第一条记录中选择某些字段
【发布时间】:2010-10-23 17:40:44
【问题描述】:

我正在对多个记录执行聚合函数,这些记录按公共 ID 分组。问题是,我还想导出一些其他字段,这些字段在分组记录中可能不同,但我想从其中一个记录中获取这些特定字段(第一个,根据查询的 ORDER BY)。

起点示例:

SELECT
  customer_id,
  sum(order_total),
  referral_code
FROM order
GROUP BY customer_id
ORDER BY date_created

我需要查询推荐代码,但在聚合函数之外执行此操作意味着我也必须按该字段进行分组,这不是我想要的 - 在此示例中,我需要每个客户恰好一行。我真的只关心第一个订单的推荐代码,我很乐意丢弃任何以后的推荐代码。

这是在 PostgreSQL 中,但可能来自其他数据库的语法可能足够相似。

被拒绝的解决方案:

  • 不能使用 max() 或 min(),因为顺序很重要。
  • 子查询起初可能有效,但无法扩展;这是一个极其简化的例子。我的实际查询有几十个字段,比如我只想要第一个实例的 refer_code,还有几十个 WHERE 子句,如果在子查询中重复,这将成为维护的噩梦。

【问题讨论】:

    标签: sql postgresql group-by aggregate-functions


    【解决方案1】:
    SELECT  customer_id, order_sum,
            (first_record).referral, (first_record).other_column
    FROM    (
            SELECT  customer_id,
                    SUM(order_total) AS order_sum,
                    (
                    SELECT  oi
                    FROM    order oi
                    WHERE   oi.customer_id = o.customer_id
                    LIMIT 1
                    ) AS first_record
            FROM    order o
            GROUP BY
                    customer_id
            ) q
    

    【讨论】:

      【解决方案2】:

      这样的东西可以解决问题吗?

      SELECT
        customer_id,
        sum(order_total),
        (SELECT referral_code 
         FROM order o 
         WHERE o.customer_id = order.customer_id 
         ORDER BY date_created 
         LIMIT 1) AS customers_referral_code
      FROM order
      GROUP BY customer_id, customers_referral_code
      ORDER BY date_created
      

      这不需要您在两个地方维护 WHERE 子句并保持顺序重要性,但是如果您需要“数十个字段”,例如referral_code,则会变得非常麻烦。它也相当慢(至少在 MySQL 上)。

      对我来说,这听起来像referral_code,并且像它这样的几十个字段应该在客户表中,而不是订单表中,因为它们在逻辑上与客户 1:1 相关联,而不是订单。将它们移到那里将使查询更简单。

      这也可以解决问题:

      SELECT
        o.customer_id,
        sum(o.order_total),
        c.referral_code, c.x, c.y, c.z
      FROM order o LEFT JOIN (
          SELECT referral_code, x, y, z
          FROM orders c 
          WHERE c.customer_id = o.customer_id 
          ORDER BY c.date_created
          LIMIT 1
      ) AS c
      GROUP BY o.customer_id, c.referral_code
      ORDER BY o.date_created
      

      【讨论】:

      • 当前,您的查询包含两个名为 refer_code 的字段(一个是子查询),这两个字段均未列在 GROUP BY 中。
      • 第一个referral_code确实是一个错误。 GROUP BY 中缺少它仅仅是因为某些 SQL 方言不需要它。感谢您指出这一点,已修复。
      【解决方案3】:

      可能是这样的:

      SELECT
           O1.customer_id,
           O1.referral_code,
           SQ.total
      FROM
           Orders O1
      LEFT OUTER JOIN Orders O2 ON
           O2.customer_id = O1.customer_id AND
           O2.date_created < O1.date_created
      INNER JOIN (
           SELECT
                customer_id,
                SUM(order_total) AS total
           FROM
                Orders
           GROUP BY
                customer_id
           ) SQ ON SQ.customer_id = O1.customer_id
      WHERE
           O2.customer_id IS NULL
      

      【讨论】:

      • 您需要在子查询末尾添加“GROUP BY customer_id”。然后您的查询给出了最后一个referral_code。将加入条件的大于号更改为小于号,它将获得第一个推荐代码。
      • 谢谢,看来我把 GROUP BY 留在了剪切粘贴中
      【解决方案4】:

      嗯,其实很简单。

      首先,让我们编写一个用于聚合的查询:

      select customer_id, sum(order_total)
      from order
      group by customer_id
      

      现在,让我们编写一个查询,该查询将返回给定 customer_id 的第一个推荐代码和 date_created:

      select distinct on (customer_id) customer_id, date_created, referral_code
      from order
      order by customer_id, date_created
      

      现在,您可以简单地加入 2 个选择:

      select
          x1.customer_id,
          x1.sum,
          x2.date_created,
          x2.referral_code
      from
          (
              select customer_id, sum(order_total)
              from order
              group by customer_id
          ) as x1
          join
          (
              select distinct on (customer_id) customer_id, date_Created, referral_code
              from order
              order by customer_id, date_created
          ) as x2 using ( customer_id )
      order by x2.date_created
      

      我没有测试它,所以可能有错别字,但通常它应该可以工作。

      【讨论】:

      • +1,但这仍然需要在 2 个地方更新任何额外的 WHERE 子句。
      • 好吧,没有这个要求也可以完成,但它需要自定义聚合(首先)。并不难。
      【解决方案5】:

      如果 date_created 保证每个 customer_id 都是唯一的,那么您可以这样做:

      [简单表]

      create table ordertable (customer_id int, order_total int, referral_code char, date_created datetime)
      insert ordertable values (1,10, 'a', '2009-01-01')
      insert ordertable values (2,15, 'b', '2009-01-02')
      insert ordertable values (1,35, 'c', '2009-01-03')
      

      [用更好的东西替换我蹩脚的表名:)]

      SELECT
        orderAgg.customer_id,
        orderAgg.order_sum,
        referral.referral_code as first_referral_code
      FROM (
              SELECT
                customer_id,
                sum(order_total) as order_sum
              FROM ordertable
              GROUP BY customer_id
          ) as orderAgg join (
              SELECT
                customer_id,
                min(date_created) as first_date
              FROM ordertable
              GROUP BY customer_id
          ) as dateAgg on orderAgg.customer_id = dateAgg.customer_id
          join ordertable as referral 
              on dateAgg.customer_id = referral.customer_id
                  and dateAgg.first_date = referral.date_created
      

      【讨论】:

        【解决方案6】:

        您将需要window functions。 这是一种 GROUP BY,但您仍然可以访问各个行。 不过只使用了 Oracle 的等价物。

        【讨论】:

        • 有趣...看起来像是 8.4 的新功能?不幸的是,一旦新版本发布,我们需要一段时间才能迁移到新版本,现在我们仍然停留在 8.2 上(尽管希望不会太久......):\
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多