Ecco la mia versione. Lo stavo davvero presentando come una semplice curiosità, per mostrare un altro modo di pensare al problema. Si è rivelato più utile di così perché ha funzionato meglio della soluzione "isole raggruppate" di Martin Smith. Tuttavia, una volta che si è sbarazzato di alcune funzioni di windowing aggregate eccessivamente costose e ha fatto invece aggregati reali, la sua query ha iniziato a buttare calci.
Soluzione 1: Esecuzione di 3 mesi o più, eseguita controllando 1 mese in anticipo e indietro e utilizzando un semi-join contro quello.
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;
Soluzione 2: esatti modelli a 3 mesi. Se è una corsa di 4 o più mesi, i valori sono esclusi. Questo viene fatto controllando 2 mesi prima e due mesi indietro (essenzialmente cercando il modello 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;
Ecco il mio script di tabella di carico se qualcun altro vuole giocare:
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
prestazioni
Qui ci sono alcuni risultati di test delle prestazioni per i 3 mesi-or-più query :
Query CPU Reads Duration
Martin 1 2297 299412 2348
Martin 2 625 285 809
Denis 3641 401 3855
Erik 1855 94727 2077
Questa è solo una corsa di ciascuno, ma i numeri sono abbastanza rappresentativi. Si scopre che la tua domanda non è stata così cattiva, dopotutto. La query di Martin supera le altre mani, ma all'inizio ha usato alcune strategie di funzioni di windowing eccessivamente costose che ha risolto.
Naturalmente, come ho notato, la query di Denis non sta tirando le righe a destra quando un cliente ha due ordini nello stesso giorno, quindi la sua query è fuori dal conflitto a meno che non lo sia.
Inoltre, diversi indici potrebbero probabilmente scuotere le cose. Non lo so.
Quale output si desidera se la riga '113, 13-AUG-2007, 1' viene aggiunta alla tabella Ordine? Un blocco di output per AA con 4 righe o due blocchi di output, ciascuno contenente 3 righe? Se preferisci, è "rigorosamente tre mesi alla volta" o "tre o più mesi alla volta". –
Ci scusiamo per il ritardo, preferisco esattamente tre mesi – Gopi
Vuoi dire che una stringa di 4 mesi restituirebbe 6 righe, una serie con mese 1, 2, 3 e un'altra serie con mese 2, 3, 4 o semplicemente per escludere tutte le stringhe di ordini che non sono esattamente 3 mesi? – ErikE