SQL Server - 在特定日期范围内获取第n个订单的客户

时间:2013-05-10 15:19:35

标签: sql-server select having

我的任务如下:

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

我的表格是这样的:

  • [dbo.customers]:customerID
  • [dbo.orders]:orderID,customerID, 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实际上是给定日期范围结束时订单总量的SUM,而不一定是前10个。

非常感谢任何有关更好方法的建议。

3 个答案:

答案 0 :(得分:0)

  1. 在@tmpcustomers的插入中,为什么要加入客户表?订单表已具有您想要的customerID。另外,为什么要查找订单日期在您的日期范围之前的订单?您是否只想要在日期范围之间超过n个订单的客户?这将使第二个查询更容易。

  2. 通过仅在表变量@tmpcustomers中拥有n个或更多订单的客户,您应该能够在第二个查询中加入它和订单表以获得这些客户的所有订单的总和您将再次将订单表记录限制在您的日期范围内(因此您不会获得该范围之外的订单)。这将删除最终结果查询中的having语句和join表中的customers表。

答案 1 :(得分:0)

试一试。根据您的订单分配,它可能会表现更好。在此查询中,我在汇编范围内的订单列表,然后回头计算先前订单的数量(也抓住订单总数)。

注意:我假设订单放置时订单ID增量。 如果不是这种情况,只需在日期上使用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

答案 2 :(得分:0)

这是我的建议:

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' 

EDIT :::::::::

我拿了我的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 */