2010-09-18 23 views
8

Vui lòng giúp tôi tạo truy vấn sau. Nói rằng tôi có bảng khách hàng và bảng thứ tự.TSQL Tìm thứ tự xảy ra trong 3 tháng liên tiếp

Bảng khách hàng

CustID CustName 

1  AA  
2  BB 
3  CC 
4  DD 

thứ tự Bảng

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 

Tôi muốn tìm ra những khách hàng đã thực hiện lệnh trên ba tháng liên tiếp. (Truy vấn sử dụng SQL server 2005 và 2008 được cho phép).

Kết quả mong muốn là:

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   
+0

Bạn muốn thêm đầu ra nào nếu hàng '113, 13-AUG-2007, 1' được thêm vào bảng Đặt hàng? Một khối đầu ra cho AA với 4 hàng, hoặc hai khối đầu ra, mỗi khối chứa 3 hàng? Nếu bạn thích, đó là "đúng ba tháng tại một thời điểm" hoặc "ba hoặc nhiều tháng tại một thời điểm". –

+0

Xin lỗi vì sự chậm trễ, tôi thích chính xác ba tháng – Gopi

+0

Bạn có nghĩa là một chuỗi 4 tháng sẽ trả về 6 hàng, một tập hợp với tháng 1, 2, 3 và một tập hợp khác với tháng 2, 3, 4 hoặc đơn giản để loại trừ tất cả các chuỗi đơn đặt hàng không chính xác 3 tháng? – ErikE

Trả lời

7

Edit: Chấn thoát khỏi hoặc MAX() OVER (PARTITION BY ...) như rằng dường như giết hiệu suất.

;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 
+1

Cần sử dụng DENSE_RANK hoặc bốn + bán hàng trong vòng ba tháng sẽ bị bỏ qua. –

+1

Giải pháp đảo nhóm hoàn hảo ... – ErikE

+0

Martin, tôi đã kiểm tra truy vấn của bạn và nó không đưa ra kết quả phù hợp ... – ErikE

1

Ở đây bạn đi:

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

Cảnh báo: Truy vấn này vô cùng không hiệu quả. :) –

+0

Denis, tôi rất tiếc khi báo cáo rằng truy vấn này không trả lại kết quả chính xác khi có hai đơn đặt hàng của cùng một khách hàng trong cùng một ngày. – ErikE

+0

@Emtucifor, tôi biết! Nhưng chúng tôi không biết những gì @CSharpy cần! :) –

4

Đây là phiên bản của tôi. Tôi thực sự đã trình bày điều này như một sự tò mò, để thể hiện một cách suy nghĩ khác về vấn đề này. Hóa ra nó hữu ích hơn thế bởi vì nó hoạt động tốt hơn cả giải pháp "đảo nhóm" mát mẻ của Martin Smith. Mặc dù, một khi ông đã thoát khỏi một số chức năng cửa sổ tổng hợp quá đắt tiền và đã làm tổng hợp thực thay vào đó, truy vấn của ông bắt đầu đá mông.

Giải pháp 1: Chạy 3 tháng trở lên, được thực hiện bằng cách kiểm tra 1 tháng trước và sau và sử dụng kết hợp bán chống lại điều đó.

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; 

Giải pháp 2: Mẫu chính xác 3 tháng. Nếu nó là một 4 tháng hoặc lớn hơn chạy, các giá trị được loại trừ. Điều này được thực hiện bằng cách kiểm tra trước 2 tháng và hai tháng sau (về cơ bản là tìm kiếm mẫu 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; 

Dưới đây là kịch bản bảng nạp của tôi nếu ai muốn chơi:

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 

Performance

Dưới đây là một số kết quả thử nghiệm hiệu suất cho 3 tháng hoặc-nhiều truy vấn :

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

Đây chỉ là một lần chạy của mỗi số, nhưng các con số này khá đại diện. Nó chỉ ra rằng truy vấn của bạn không được thực hiện tồi tệ như vậy, Denis, sau khi tất cả. Truy vấn của Martin đánh bại những người khác xuống, nhưng lúc đầu đã sử dụng một số chiến lược chức năng cửa sổ quá đắt tiền mà anh ta đã sửa. Tất nhiên, như tôi đã lưu ý, truy vấn của Denis không kéo các hàng phải khi khách hàng có hai đơn đặt hàng trong cùng một ngày, vì vậy truy vấn của anh ta không có tranh chấp trừ khi anh ta cố định.

Ngoài ra, các chỉ mục khác nhau có thể làm rung chuyển mọi thứ. Tôi không biết.

+0

Đừng làm cho tôi thêm hai tham gia vào giải pháp của tôi, nó đã là ba chiều. : P –

+0

Bạn cần cập nhật biểu đồ hiệu suất của mình! –

+1

Xong. Tôi để lại các số liệu thống kê trên phiên bản cũ của bạn chỉ để cho thấy rằng không phải tất cả các hoạt động chức năng cửa sổ đều tuyệt vời như vậy. Được sử dụng bừa bãi họ có thể làm tổn thương hiệu suất. – ErikE

0

Đây là của tôi.

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 
Các vấn đề liên quan