Aquí está mi versión. Realmente estaba presentando esto como una mera curiosidad, para mostrar otra forma de pensar sobre el problema. Resultó ser más útil que eso porque funcionó mejor que incluso la solución genial de "islas agrupadas" de Martin Smith. Sin embargo, una vez que se deshizo de algunas funciones de ventana agregadas demasiado costosas e hizo agregados reales en su lugar, su consulta comenzó a patear a tope.
Solución 1: Ejecuciones de 3 meses o más, hecho verificando 1 mes adelante y atrás y usando una semi-unión contra eso.
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;
Solución 2: Exactas patrones de 3 meses. Si es una ejecución de 4 meses o más, los valores están excluidos. Esto se hace verificando 2 meses adelante y dos meses atrás (esencialmente buscando el patrón 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;
Aquí está mi script de tablas de carga si alguien más quiere jugar:
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
Rendimiento
Aquí están algunos resultados de pruebas de rendimiento para los 3 meses o más-consultas :
Query CPU Reads Duration
Martin 1 2297 299412 2348
Martin 2 625 285 809
Denis 3641 401 3855
Erik 1855 94727 2077
Esto es solo una carrera de cada uno, pero los números son bastante representativos. Al fin y al cabo, tu consulta no tuvo tan buen rendimiento, Denis. La consulta de Martin supera a los demás, pero al principio estaba usando algunas estrategias de funciones de ventana demasiado costosas que él arregló.
Por supuesto, como noté, la consulta de Denis no está tirando de las filas correctas cuando un cliente tiene dos pedidos en el mismo día, por lo que su consulta está fuera de discusión a menos que lo arregle.
Además, los diferentes índices podrían agitar las cosas. No lo sé.
¿Qué resultado desea si la fila '113, 13-AUG-2007, 1' se agrega a la tabla de pedidos? ¿Un bloque de salida para AA con 4 filas, o dos bloques de salida, cada uno con 3 filas? Si lo prefiere, ¿es 'estrictamente tres meses a la vez' o 'tres o más meses a la vez'? –
Disculpe la demora, prefiero exactamente tres meses – Gopi
¿Quiere decir que una cadena de 4 meses devolvería 6 filas, una con el mes 1, 2, 3 y otra con el mes 2, 3, 4 o simplemente excluirla? todas las cadenas de pedidos que no son exactamente 3 meses? – ErikE