TSQL Finding Order, который произошел за 3 месяца подряд - PullRequest
8 голосов
/ 19 сентября 2010

Пожалуйста, помогите мне сгенерировать следующий запрос. Скажем, у меня есть таблица клиента и таблица заказа.

Таблица клиентов

CustID CustName

1      AA     
2      BB
3      CC
4      DD  

Стол заказов

OrderID  OrderDate          CustID
100      01-JAN-2000        1  
101      05-FEB-2000        1     
102      10-MAR-2000        1 
103      01-NOV-2000        2    
104      05-APR-2001        2 
105      07-MAR-2002        2
106      01-JUL-2003        1
107      01-SEP-2004        4
108      01-APR-2005        4
109      01-MAY-2006        3 
110      05-MAY-2007        1  
111      07-JUN-2007        1
112      06-JUL-2007        1 

Я хочу узнать клиентов, которые делали заказы в течение трех месяцев подряд. (Запрос с использованием SQL Server 2005 и 2008 разрешен).

Желаемый результат:

CustName      Year   OrderDate   

    AA        2000  01-JAN-2000       
    AA        2000  05-FEB-2000
    AA        2000  10-MAR-2000

    AA        2007  05-MAY-2007        
    AA        2007  07-JUN-2007        
    AA        2007  06-JUL-2007         

Ответы [ 4 ]

7 голосов
/ 19 сентября 2010

Редактировать: Избавился или MAX() OVER (PARTITION BY ...), поскольку это, казалось, убило производительность.

;WITH cte AS ( 
SELECT    CustID  ,
          OrderDate,
          DATEPART(YEAR, OrderDate)*12 + DATEPART(MONTH, OrderDate) AS YM
 FROM     Orders
 ),
 cte1 AS ( 
SELECT    CustID  ,
          OrderDate,
          YM,
          YM - DENSE_RANK() OVER (PARTITION BY CustID ORDER BY YM) AS G
 FROM     cte
 ),
 cte2 As
 (
 SELECT CustID  ,
          MIN(OrderDate) AS Mn,
          MAX(OrderDate) AS Mx
 FROM cte1
GROUP BY CustID, G
HAVING MAX(YM)-MIN(YM) >=2 
 )
SELECT     c.CustName, o.OrderDate, YEAR(o.OrderDate) AS YEAR
FROM         Customers AS c INNER JOIN
                      Orders AS o ON c.CustID = o.CustID
INNER JOIN  cte2 c2 ON c2.CustID = o.CustID and o.OrderDate between Mn and Mx
order by c.CustName, o.OrderDate
4 голосов
/ 21 сентября 2010

Вот моя версия.Я действительно представлял это как простое любопытство, чтобы показать другой способ мышления о проблеме.Это оказалось более полезным, потому что оно работало лучше, чем даже классное решение Мартина Смита «сгруппированные острова».Хотя, как только он избавился от некоторых излишне дорогих оконных функций для агрегатов и вместо этого выполнил реальные агрегаты, его запрос начал задавать зад.1 месяц вперед и назад, используя полусоединение против этого.

WITH Months AS (
   SELECT DISTINCT
      O.CustID,
      Grp = DateDiff(Month, '20000101', O.OrderDate)
   FROM
      CustOrder O
), Anchors AS (
   SELECT
      M.CustID,
      Ind = M.Grp + X.Offset
   FROM
      Months M
      CROSS JOIN (
         SELECT -1 UNION ALL SELECT 0 UNION ALL SELECT 1
      ) X (Offset)
   GROUP BY
      M.CustID,
      M.Grp + X.Offset
   HAVING
      Count(*) = 3
)
SELECT
   C.CustName,
   [Year] = Year(OrderDate),
   O.OrderDate
FROM
   Cust C
   INNER JOIN CustOrder O ON C.CustID = O.CustID
WHERE
   EXISTS (
      SELECT 1
      FROM
         Anchors A
      WHERE
         O.CustID = A.CustID
         AND O.OrderDate >= DateAdd(Month, A.Ind, '19991201')
         AND O.OrderDate < DateAdd(Month, A.Ind, '20000301')
   )
ORDER BY
   C.CustName,
   OrderDate;

Решение 2: Точные 3-месячные схемы.Если это 4-месячный или более прогон, значения исключаются.Это делается путем проверки на 2 месяца вперед и на два месяца позади (по сути, ища схему N, Y, Y, Y, N).

WITH Months AS (
   SELECT DISTINCT
      O.CustID,
      Grp = DateDiff(Month, '20000101', O.OrderDate)
   FROM
      CustOrder O
), Anchors AS (
   SELECT
      M.CustID,
      Ind = M.Grp + X.Offset
   FROM
      Months M
      CROSS JOIN (
         SELECT -2 UNION ALL SELECT -1 UNION ALL SELECT 0 UNION ALL SELECT 1 UNION ALL SELECT 2
      ) X (Offset)
   GROUP BY
      M.CustID,
      M.Grp + X.Offset
   HAVING
      Count(*) = 3
      AND Min(X.Offset) = -1
      AND Max(X.Offset) = 1
)
SELECT
   C.CustName,
   [Year] = Year(OrderDate),
   O.OrderDate
FROM
   Cust C
   INNER JOIN CustOrder O ON C.CustID = O.CustID
   INNER JOIN Anchors A
      ON O.CustID = A.CustID
      AND O.OrderDate >= DateAdd(Month, A.Ind, '19991201')
      AND O.OrderDate < DateAdd(Month, A.Ind, '20000301')
ORDER BY
   C.CustName,
   OrderDate;

Вот мой скрипт загрузки таблицы, если кто-то еще хочет играть:

IF Object_ID('CustOrder', 'U') IS NOT NULL DROP TABLE CustOrder
IF Object_ID('Cust', 'U') IS NOT NULL DROP TABLE Cust
GO
SET NOCOUNT ON
CREATE TABLE Cust (
  CustID int identity(1,1) NOT NULL PRIMARY KEY CLUSTERED,
  CustName varchar(100) UNIQUE
)

CREATE TABLE CustOrder (
   OrderID int identity(100, 1) NOT NULL PRIMARY KEY CLUSTERED,
   CustID int NOT NULL FOREIGN KEY REFERENCES Cust (CustID),
   OrderDate smalldatetime NOT NULL
)

DECLARE @i int
SET @i = 1000
WHILE @i > 0 BEGIN
   WITH N AS (
      SELECT
         Nm =
            Char(Abs(Checksum(NewID())) % 26 + 65)
            + Char(Abs(Checksum(NewID())) % 26 + 97)
            + Char(Abs(Checksum(NewID())) % 26 + 97)
            + Char(Abs(Checksum(NewID())) % 26 + 97)
            + Char(Abs(Checksum(NewID())) % 26 + 97)
            + Char(Abs(Checksum(NewID())) % 26 + 97)
   )
   INSERT Cust
   SELECT N.Nm
   FROM N
   WHERE NOT EXISTS (
      SELECT 1
      FROM Cust C
      WHERE
         N.Nm = C.CustName
   )

   SET @i = @i - @@RowCount
END
WHILE @i < 50000 BEGIN
   INSERT CustOrder
   SELECT TOP (50000 - @i)
      Abs(Checksum(NewID())) % 1000 + 1,
      DateAdd(Day, Abs(Checksum(NewID())) % 10000, '19900101')
   FROM master.dbo.spt_values
   SET @i = @i + @@RowCount
END

Производительность

Вот некоторые результаты тестирования производительности для запросов на 3 месяца и более:

Query     CPU   Reads Duration
Martin 1  2297 299412   2348 
Martin 2   625    285    809
Denis     3641    401   3855
Erik      1855  94727   2077

Этотолько один прогон каждого, но цифры довольно представительные.Оказывается, ваш запрос не так плохо работал, Денис, в конце концов.Запрос Мартина опускает руки других, но сначала он использовал слишком дорогостоящие стратегии оконных функций, которые он исправил.

Конечно, как я заметил, запрос Дениса не тянет правильные строки, когда клиент имеетдва заказа в один и тот же день, поэтому его запрос не оспаривается, если он не исправлен.

Кроме того, разные индексы могут потрясти вещи.Я не знаю.

1 голос
/ 19 сентября 2010

Вот, пожалуйста:

select distinct
 CustName
,year(OrderDate) [Year]
,OrderDate
from 
(
select 
 o2.OrderDate [prev]
,o1.OrderDate [curr]
,o3.OrderDate [next]
,c.CustName
from [order] o1 
join [order] o2 on o1.CustId = o2.CustId and datediff(mm, o2.OrderDate, o1.OrderDate) = 1
join [order] o3 on o1.CustId = o3.CustId and o2.OrderId <> o3.OrderId and datediff(mm, o3.OrderDate, o1.OrderDate) = -1
join Customer c on c.CustId = o1.CustId
) t
unpivot
(
    OrderDate for [DateName] in ([prev], [curr], [next])
)
unpvt
order by CustName, OrderDate
0 голосов
/ 15 апреля 2016

Вот мой дубль.

select 100 as OrderID,convert(datetime,'01-JAN-2000') OrderDate,    1  as CustID  into #tmp union
    select 101,convert(datetime,'05-FEB-2000'),        1 union
    select 102,convert(datetime,'10-MAR-2000'),        1 union
    select 103,convert(datetime,'01-NOV-2000'),        2 union   
    select 104,convert(datetime,'05-APR-2001'),        2 union
    select 105,convert(datetime,'07-MAR-2002'),        2 union
    select 106,convert(datetime,'01-JUL-2003'),        1 union
    select 107,convert(datetime,'01-SEP-2004'),        4 union
    select 108,convert(datetime,'01-APR-2005'),        4 union
    select 109,convert(datetime,'01-MAY-2006'),        3 union
    select 110,convert(datetime,'05-MAY-2007'),        1 union 
    select 111,convert(datetime,'07-JUN-2007'),        1 union
    select 112,convert(datetime,'06-JUL-2007'),        1 


    ;with cte as
    (
        select
            *   
            ,convert(int,convert(char(6),orderdate,112)) - dense_rank() over(partition by custid order by orderdate) as g
        from #tmp
    ),
    cte2 as 
    (
    select 
        CustID
        ,g  
    from cte a
    group by CustID, g
    having count(g)>=3
    )
    select
        a.CustID
        ,Yr=Year(OrderDate)
        ,OrderDate
    from cte2 a join cte b
        on a.CustID=b.CustID and a.g=b.g
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...