【问题标题】:PostgreSQL SELECT the last order per customer per date rangePostgreSQL 选择每个客户每个日期范围的最后一个订单
【发布时间】:2010-12-08 09:50:35
【问题描述】:

在 PostgreSQL 中: 我有一个包含 3 列的表格:

CustomerNum, OrderNum, OrderDate

每个日期范围内每个客户可能有(也可能没有)很多订单。我需要的是位于提供的日期范围内的每个客户的最后一个 OrderNum。 我一直在做的是获取客户的 ResultSet 并分别查询每个客户,但这需要太多时间。

有没有办法使用子选择来选择客户,然后获取每个客户的最后一个 OrderNum?

【问题讨论】:

    标签: sql postgresql sql-order-by greatest-n-per-group subquery


    【解决方案1】:
    -- generate some data
    DROP TABLE tmp.orders;
    CREATE TABLE tmp.orders
        ( id INTEGER NOT NULL
        , odate DATE NOT NULL
        , payload VARCHAR
        )
        ;
    ALTER TABLE tmp.orders ADD PRIMARY KEY (id,odate);
    
    INSERT INTO tmp.orders(id,odate,payload) VALUES
      (1, '2011-10-04' , 'one' )
    , (1, '2011-10-24' , 'two' )
    , (1, '2011-10-25' , 'three' )
    , (1, '2011-10-26' , 'four' )
    , (2, '2011-10-23' , 'five' )
    , (2, '2011-10-24' , 'six' )
        ;
    
    -- CTE to the rescue ...
    WITH sel AS (
        SELECT * FROM tmp.orders
        WHERE odate BETWEEN '2011-10-23' AND '2011-10-24'
        )
    SELECT * FROM sel s0
    WHERE NOT EXISTS (
        SELECT * FROM sel sx
        WHERE sx.id = s0.id
        AND sx.odate > s0.odate
        )
        ;
    

    结果:

    DROP TABLE
    CREATE TABLE
    NOTICE:  ALTER TABLE / ADD PRIMARY KEY will create implicit index "orders_pkey" for table "orders"
    ALTER TABLE
    INSERT 0 6
     id |   odate    | payload 
    ----+------------+---------
      1 | 2011-10-24 | two
      2 | 2011-10-24 | six
    (2 rows)
    

    【讨论】:

      【解决方案2】:

      在 postgres 上,您还可以使用非标准的 DISTINCT ON 子句:

      SELECT DISTINCT ON (CustomerNum) CustomerNum, OrderNum, OrderDate
        FROM Orders
        WHERE OrderDate BETWEEN 'yesterday' AND 'today'
        ORDER BY CustomerNum, OrderDate DESC;
      

      http://www.postgresql.org/docs/current/static/sql-select.html#SQL-DISTINCT

      【讨论】:

      • 看起来是这个问题的最佳答案,即使它不是标准 SQL。谢谢
      • 比其他解决方案更快,它在 2600 万行表上为我提供了 35 秒的结果。这也适用于不在分组中的字段,例如:SELECT DISTINCT ON (field(s)) * FROM...
      【解决方案3】:
      SELECT t1.CustomerNum, t1.OrderNum As LastOrderNum, t1.LastOrderDate
        FROM table1 As t1
       WHERE t1.OrderDate = (SELECT MAX(t2.OrderDate)
                               FROM table1 t2
                              WHERE t1.CustomerNum = t2.CustomerNum
                                AND t2.OrderDate BETWEEN date1 AND date2)
         AND t1.OrderDate BETWEEN date1 AND date2
      

      【讨论】:

      • 这将导致带有外部全表扫描和内部子查询的嵌套循环,因为 Postgresql 无法通过子查询中的聚合来解除 OrderDate 范围约束。在 date1 AND date2 之间添加 AND t1.OrderDate 将允许它使用 OrderDate 上的索引来限制结果集。
      • @Ants:Postgre 真的不够聪明,无法在嵌套循环连接中使用子查询作为外部表吗?
      【解决方案4】:

      如果最后一个订单号是指最大的订单号,那么您可以只使用您的选择作为客户编号的谓词,对结果进行分组并选择最大值:

      SELECT CustomerNum, MAX(OrderNum) AS LastOrderNum
          FROM Orders
          WHERE 
              CustomerNum IN (SELECT CustomerNum FROM ...)
                  AND
              OrderDate BETWEEN :first_date AND :last_date
          GROUP BY CustomerNum
      

      如果最后一个订单号不一定是最大订单号,那么您需要找到每个客户的最大订单日期,并将其与其余订单合并以找到相应的订单号:

      SELECT O.CustomerNum, O.OrderNum AS LastOrderNum
          FROM
              (SELECT CustomerNum, MAX(OrderDate) AS OrderDate
                   FROM Orders
                   WHERE
                       OrderDate BETWEEN :first_date AND :last_date
                           AND
                       CustomerNum IN (SELECT CustomerNum FROM ...)
                   GROUP BY CustomerNum
              ) AS CustLatest
                  INNER JOIN
              Orders AS O USING (CustomerNum, OrderDate);
      

      【讨论】:

        【解决方案5】:

        不确定客户表的结构或关系,但这应该可以:

        SELECT Customer.Num, (
            SELECT OrderNum FROM Orders WHERE CustomerNum = Customer.Num AND OrderDate BETWEEN :start AND :end ORDER BY OrderNum DESC LIMIT 1
        ) AS LastOrderNum
        FROM Customer
        

        【讨论】:

          【解决方案6】:
          select customernum, max(ordernum)
          from table
          where orderdate between '...' and '...'
          group by customernum
          

          就是这样。

          【讨论】:

          • 这就是我要说的。只需要它一个组。
          • OrderNum 是一个顺序值的假设可能不成立,或者只是创建顺序可能与日期顺序不匹配(例如,OrderDate 可能会标记订单何时完成,而不是比创建时更重要,并且您关心完成时间)。
          猜你喜欢
          • 2011-05-02
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2016-05-14
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2021-02-23
          相关资源
          最近更新 更多