2010-09-18 37 views
8

Lütfen aşağıdaki sorguyu oluşturmama yardımcı olun. Müşteri masam ve sipariş masam var.TSQL Ardışık 3 ay içinde oluşan Sipariş Bulma

Müşteri Tablo

CustID CustName 

1  AA  
2  BB 
3  CC 
4  DD 

al Tablo

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 

Üç ay üst siparişleri gerçekleştirmiş müşterileri öğrenmek istiyorum. (SQL Server 2005 ve 2008 kullanarak sorgulamaya izin verilir).

İstenen çıkış geçerli:

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

satır '113, 13-Ağustos-2007, 1' Sipariş tabloya eklenirse Ne istiyorsun çıktı? Her biri 3 satır içeren 4 satırlı veya iki blok çıkışlı AA için bir çıkış bloğu mu? İsterseniz, 'bir defada sadece üç ay' veya 'bir seferde üç veya daha fazla ay' demektir. –

+0

Gecikme için özür dilerim, tam olarak üç ay tercih ederim – Gopi

+0

4 aylık bir dize 6 satır, 1, 2, 3 ve 1, 2, 3, 4 ayları olan bir setin geri dönmesi anlamına mı geliyor? Tam olarak 3 ay olmayan tüm sipariş dizeleri? – ErikE

cevap

7

Düzenleme: kurtuldum ya o kadar MAX() OVER (PARTITION BY ...) performansı öldürmek gibiydi. İşte

;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

DENSE_RANK kullanması gerekenler veya üç ay içinde dört + satış göz ardı edilecektir. –

+1

Mükemmel gruplanmış adalar çözümü ... – ErikE

+0

Martin, Sorgunuzu test ettim ve doğru sonuçları vermiyordum ... – ErikE

1

gitmek:

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

Uyarı: Bu sorgu son derece verimsiz. :) –

+0

Denis, aynı müşteri tarafından aynı müşteri tarafından iki sipariş olduğunda, bu sorgunun doğru sonuçları vermediğini bildirmek üzüldüm. – ErikE

+0

@Emtucifor, biliyorum! Ancak, @Charhar'ın neye ihtiyacı olduğunu bilmiyoruz! :) –

4

İşte benim sürümüdür. Bunu gerçekten bir merak olarak sunarken, problem hakkında başka bir düşünme şekli gösterdim. Bundan daha kullanışlı olduğu ortaya çıktı çünkü Martin Smith'in serin "gruplanmış adalar" çözümünden bile daha iyi performans gösterdi. Yine de, aşırı pahalı agregate pencereleme fonksiyonlarından kurtulduktan ve gerçek agregatları yerine getirdikten sonra, sorgulaması popo atmaya başladı.

Çözüm 1: 3 ay veya daha uzun çalıştırır, 1 ay öncesinde ve arkasında check-in yapıp buna karşı yarı katılmak kullanılarak yapılır.

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; 

Çözüm 2: Tam 3 aylık kalıpları. 4 aylık veya daha büyük bir çalışma ise değerler hariç tutulur. Bu, 2 ay önce ve iki ay sonra kontrol edilerek yapılır (esas olarak N, Y, Y, Y, N modellerini arar). Başkasının oynamak istiyorsa Burada

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; 

benim masa yükleme komut dosyası:

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 

Performans

İşte

3 aylık ya-fazla sorgu için bazı performans testleri sonuçları :

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

Bu tek bölgedir her biri, ancak sayılar oldukça temsilcisidir. Sorgunun bu kadar kötü performans göstermediği ortaya çıkıyor, Denis, sonuçta. Martin'in sorgusu diğerlerinin ellerini indirgemesine karşın, ilk başta sabitlediği aşırı pahalı pencereleme fonksiyonlarını kullanıyordu. Ben belirtildiği gibi onun sorgu o sabit olmadıkça yarışının dışına yani bir müşteri, aynı gün iki sipariş olduğunda Tabii

, Denis sorgu doğru satırların çekerek değildir. Ayrıca, farklı indeksler şeyleri sallayabilirdi. Bilmiyorum.

+0

Çözüme iki tane daha katık eklememe izin verme, zaten üç boyutlu. : P –

+0

Performans grafiğinizi güncellemeniz gerekiyor! –

+1

Bitti. Eski pencere sürümünüzdeki istatistikleri, tüm pencereleme işlevlerinin çok büyük olmadığını göstermek için bıraktım. Rasgele kullanıldığında performansa zarar verebilirler. – ErikE

0

İşte benim almam.

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