Voici ma version. Je présentais vraiment cela comme une simple curiosité, pour montrer une autre façon de penser au problème. Il s'est avéré plus utile que cela parce qu'il a obtenu de meilleurs résultats que la solution «groupée» de Martin Smith. Cependant, une fois qu'il s'est débarrassé de certaines fonctions de fenêtrage agrégées trop coûteuses et a fait de vrais agrégats à la place, sa requête a commencé à donner des coups de pied.
Solution 1: Essais de 3 mois ou plus, effectués en vérifiant 1 mois en avant et en arrière et en utilisant une demi-jointure contre cela.
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;
Solution 2: Exact modèles 3 mois. S'il s'agit d'un cycle de 4 mois ou plus, les valeurs sont exclues. Ceci est fait en vérifiant 2 mois d'avance et deux mois de retard (essentiellement en recherchant le modèle 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;
Voici mon scénario table de chargement si quelqu'un d'autre veut jouer:
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
Voici quelques résultats des tests de performance pour les plus ou 3 mois requêtes :
Query CPU Reads Duration
Martin 1 2297 299412 2348
Martin 2 625 285 809
Denis 3641 401 3855
Erik 1855 94727 2077
Ceci est seulement une course de chacun, mais les chiffres sont assez représentatifs. Il s'avère que votre requête n'était pas si mal performante, Denis, après tout. La requête de Martin bat les autres, mais au début, il utilisait des stratégies de fenêtrage trop chères. Bien sûr, comme je l'ai noté, la requête de Denis ne tire pas les bonnes lignes lorsqu'un client a deux commandes le même jour, donc sa requête est hors de conflit à moins qu'il ne soit corrigé.
De plus, différents index pourraient éventuellement faire bouger les choses. Je ne sais pas.
Quelle sortie voulez-vous si la ligne '113, 13-AUG-2007, 1' est ajoutée à la table des commandes? Un bloc de sortie pour AA avec 4 lignes, ou deux blocs de sortie, chacun contenant 3 lignes? Si vous préférez, est-ce «strictement trois mois à la fois» ou «trois mois ou plus à la fois»? –
Désolé pour le délai, je préfère exactement trois mois – Gopi
Voulez-vous dire qu'une chaîne de 4 mois retournerait 6 lignes, un ensemble avec le mois 1, 2, 3 et un autre ensemble avec le mois 2, 3, 4, ou simplement pour exclure toutes les séries de commandes qui ne sont pas exactement 3 mois? – ErikE