【问题标题】:SQL Server - Get customers with nth order in specific date rangeSQL Server - 获取特定日期范围内第 n 个订单的客户
【发布时间】:2013-05-05 07:58:58
【问题描述】:

我的任务是:

  • 选择在特定日期范围(通常是特定月份)内有第 n 个订单的所有客户的列表。
  • 此列表需要包含:客户 ID、前 n 个订单的总和

我的表格是这样的:

  • [dbo.customers]:客户 ID
  • [dbo.orders]:订单 ID、客户 ID、 orderDate, orderTotal

这是我迄今为止尝试过的:

-- Let's assume our threshold (n) is 10
-- Let's assume our date range is April 2013
-- Get customers that already had n orders before the beginning of the given date range.
DECLARE @tmpcustomers TABLE (tmpcustomerID varchar(8))
INSERT INTO
    @tmpcustomers
SELECT
    c.customerID
FROM
    orders o
    INNER JOIN customers c ON o.customerID = c.customerID
WHERE
    o.orderDate < '2013-04-01'
GROUP BY c.customerID
HAVING (COUNT(o.orderID) >= 10)


-- Now get all customers that have n orders sometime within the given date range
-- but did not have n orders before the beginning of the given date range.
SELECT
    a.customerID, SUM(orderTotal) AS firstTenOrderTotal
SELECT 
    o.customerID, o.orderID, o.orderTotal
FROM
    orders o
    INNER JOIN customers c ON c.customerID = o.customerID    
WHERE
    a.customerID NOT IN ( SELECT tmpcustomerID FROM @tmpcustomers )
AND
    o.orderDate > '2013-04-01'
AND
    o.orderDate < '2013-05-01'
GROUP BY c.customerID
    HAVING COUNT(o.orderID) >= 10

这似乎可行,但它笨重且缓慢。另一个大问题是 firstTenOrderTotal 实际上是给定日期范围结束时订单总数的总和,不一定是前 10 个。

任何关于更好方法的建议将不胜感激。

【问题讨论】:

    标签: sql-server select having


    【解决方案1】:
    1. 在@tmpcustomers 的插入中,您为什么要加入回客户表?订单表已经有您想要的 customerID。另外,您为什么要查找订单日期早于您的日期范围的订单?您不只是希望在一个日期范围内有超过 n 个订单的客户吗?这将使第二个查询更容易。

    2. 通过在表变量@tmpcustomers 中仅包含具有 n 个或更多订单的客户,您应该能够将其与第二个查询中的订单表连接起来,以获得这些客户的所有订单的总和您将再次将订单表记录限制在您的日期范围内(因此您不会收到超出该范围的订单)。这将在您的最终结果查询中删除 having 语句和对 customers 表的连接。

    【讨论】:

    • 感谢您的回复。 1)好点。我可以从插入到@tmpcustomers 中删除连接。我正在寻找日期在日期范围之前的订单,因为我基本上是在选择要在第二个查询中排除的 ID 列表。我正在获取在日期范围开始之前已经有 10 个或更多订单的人员列表,然后将他们排除在我的下一个查询之外。现在我实际上是在解释发生了什么,我想我想太多了,这可能是不必要的。
    • 澄清我的意图 - 我想选择在指定范围内达到第 n 个整体订单的所有客户。我还需要前 n 个订单总数的总和。
    【解决方案2】:

    试试这个。根据您的订单分布,它可能会表现更好。在此查询中,我组装了该范围内的订单列表,然后回头计算先前订单的数量(同时获取 orderTotal)。

    注意:我假设 orderID 随着订单的下达而增加。 如果不是这种情况,只需在日期上使用 row_number 将序列投影到查询中。

    declare @orders table (orderID int primary key identity(1,1), customerID int, orderDate datetime, orderTotal int)
    insert into @orders (customerID, orderDate, orderTotal)
        select 1, '2013-01-01', 1 union all
        select 1, '2013-01-02', 2 union all
        select 1, '2013-02-01', 3 union all
        select 2, '2013-01-25', 5 union all
        select 2, '2013-01-26', 5 union all
        select 2, '2013-02-02', 10 union all
        select 2, '2013-02-02', 10 union all
        select 2, '2013-02-04', 20
    
    declare @N int, @StartDate datetime, @EndDate datetime
    select  @N = 3,
            @StartDate = '2013-02-01',
            @EndDate = '2013-02-20'
    
    select  o.customerID, 
            [total] = o.orderTotal + p.total --the nth order + total prior
    from    @orders o
    cross
    apply   (   select  count(*)+1, sum(orderTotal)
                from    @orders
                where   customerId = o.customerID and 
                        orderID < o.orderID and
                        orderDate <= o.orderDate
            ) p(n, total)
    where   orderDate between @StartDate and @EndDate and p.n = @N
    

    【讨论】:

      【解决方案3】:

      这是我的建议:

      Use Northwind
      GO
      
      
      select ords.OrderID , ords.OrderDate , '<-->' as Sep1 , derived1.* from
      dbo.Orders ords 
      join
      (
      select CustomerID, OrderID, ROW_NUMBER() OVER(PARTITION BY CustomerID ORDER BY OrderId DESC) AS ThisCustomerCardinalOrderNumber from dbo.Orders 
      ) as derived1
          on ords.OrderID = derived1.OrderID
      
      where 
      derived1.ThisCustomerCardinalOrderNumber = 3
      and ords.OrderDate between '06/01/1997' and '07/01/1997' 
      

      编辑::::::::::

      我采用了我的 CTE 示例,并为多个客户重新设计了它(见下文)。 去大学试试吧。

      Use Northwind
      GO
      
      
      
      declare @BeginDate datetime
      declare @EndDate datetime
      
      select @BeginDate = '01/01/1900'
      select @EndDate = '12/31/2010'
      
      ;
       WITH
       MyCTE /* http://technet.microsoft.com/en-us/library/ms175972.aspx */
       ( ShipName,ShipAddress,ShipCity,ShipRegion,ShipPostalCode,ShipCountry,CustomerID,CustomerName,[Address],
       City,Region,PostalCode,Country,Salesperson,OrderID,OrderDate,RequiredDate,ShippedDate,ShipperName,
       ProductID,ProductName,UnitPrice,Quantity,Discount,ExtendedPrice,Freight,ROWID) AS
       (
       SELECT
       ShipName ,ShipAddress,ShipCity,ShipRegion,ShipPostalCode,ShipCountry,CustomerID,CustomerName,[Address]
       ,City ,Region,PostalCode,Country,Salesperson,OrderID,OrderDate,RequiredDate,ShippedDate,ShipperName
       ,ProductID ,ProductName,UnitPrice,Quantity,Discount,ExtendedPrice,Freight
       , ROW_NUMBER() OVER (PARTITION BY CustomerID  ORDER BY OrderDate , ProductName ASC ) as ROWID  /* Note that the ORDER BY (here) is directly related to the ORDER BY (near the very end of the query) */
       FROM
       dbo.Invoices inv /* “Invoices” is a VIEW, FYI */
       where
      (inv.OrderDate between @BeginDate and @EndDate)
       )
       SELECT
       /*
       ShipName,ShipAddress,ShipCity,ShipRegion,ShipPostalCode,ShipCountry,CustomerID,CustomerName,[Address],
       City,Region,PostalCode,Country,Salesperson,OrderID,OrderDate,RequiredDate,ShippedDate,ShipperName,
       ProductID,ProductName,UnitPrice,Quantity,Discount,ExtendedPrice,Freight,
       */
      
      /*trim the list down a little for the final output */
      
      CustomerID ,OrderID , OrderDate, (ExtendedPrice + Freight) as ComputedTotal
      
      /*The below line is the “trick”. I reference the above CTE, but only get data that is less than or equal to the row that I am on (outerAlias.ROWID)*/
       , (Select SUM (ExtendedPrice + Freight) from MyCTE innerAlias where innerAlias.ROWID <= outerAlias.ROWID and innerAlias.CustomerID = outerAlias.CustomerID) as RunningTotal
       , ROWID as ROWID_SHOWN_FOR_KICKS , OrderDate as OrderDate
       FROM
       MyCTE outerAlias
      
       GROUP BY CustomerID ,OrderID, OrderDate, ProductName,(ExtendedPrice + Freight) ,ROWID,OrderDate
      
      /*Two Order By Options*/
       ORDER BY outerAlias.CustomerID , outerAlias.OrderDate , ProductName
      
      /* << Whatever the ORDER BY is here, should match the “ROW_NUMBER() OVER ( ORDER BY ________ ASC )” statement inside the CTE */
       /*ORDER BY outerAlias.ROWID */ /* << Or, to keep is more “trim”, ORDER BY the ROWID, which will of course be the same as the “ROW_NUMBER() OVER ( ORDER BY” inside the CTE */
      

      【讨论】:

      • 谢谢。我现在正在尝试这个。到目前为止我遇到的问题是,在我的情况下,订单实际上是一个视图。尝试将其与您的示例一起使用时出现错误。我会继续弄乱它并报告。
      • 好吧,我现在看到了一个我没有做的“SUM”。我在这里给出了一个链接……关于我曾经做过的总 CTE。等等。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-04-09
      • 1970-01-01
      • 2012-02-26
      • 2013-09-28
      • 2021-03-27
      相关资源
      最近更新 更多