Bonjour,

Je suis en train d'écrire une requête qui me pose un gros souci de performances, que je ne m'explique pas.

Le but de la requête est de calculer, en fonction des commandes, les dates de dernière commande, intervalle entre deux commande et date de prochaine commande par client et produit (en se basant sur l'année du potentiel et la précédente), en fonction de lignes de potentiel déjà existantes

Voici la structure des tables impliquées.
Je m'excuse par avance pour le nommage (illisible, entre Allemand et numérotation auto) et le choix des types loin d'être intelligents (date dans des int, etc.).
Je n'y peux rien, c'est la base de données d'un logiciel tiers, et je n'ai pas la main dessus.

Table des potentiels :
TE_ISI_C035 (208 970 lignes)
- ID bigint => clé primaire
- ID_FI bigint => id du client
- ID_AR bigint => id de l'article de regroupement
- F7007 int => année du potentiel
- F7025 int => date de dernière commande (YYYYMMDD)
- F7026 int => interval moyen entre deux commandes (jours)
- F7027 int => date prévisionnelle de prochaine commande (YYYYMMDD)
- DEL bit => indicateur de suppression

Table des articles :
TE_ISI_AR (196 550 lignes)
- ID bigint => clé primaire
- ArtikelNr nvarchar(45) => code produit, unique
- ID_AR_250 => id de l'article de regroupement (lien sur TE_ISI_AR.ID)
- LosKZ bit => indicateur de suppression

Table des commandes :
TE_ISI_AU (1 524 241 lignes)
- ID bigint => clé primaire
- AufDat int => date de commande (YYYYMMDD)
- ID_FI => id du client
- LosKZ bit => indicateur de suppression

Table des lignes de commande :
TE_ISI_UP (3 151 449 lignes)
- ID bigint => clé primaire
- ArtikelNr nvarchar(45) => code produit (lien vers TE_ISI_AR.ArtikelNr)
- ID_AU bigint => lien vers l'id de la commande
- LosKZ bit => indicateur de suppression

Voici une première requête que j'avais écrit, n'ayant pas fait attention du départ que les lignes de C035 étaient par produit, je n'étais donc pas descendu à la ligne de commande :

Code sql : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
 
with base (id, mindate, maxdate, nborder) 
as 
(
	select 
		C035.ID, 
		min(AU.AufDat), 
		max(AU.AufDat), 
		count(AU.ID) 
	from TE_ISI_C035 C035 
	left outer join TE_ISI_AU AU on AU.ID_FI = C035.ID_FI and AU.LosKZ = 0 and (AU.AufDat / 10000) in (C035.F7007 - 1, C035.F7007) 
	where C035.DEL = 0 
	group by C035.ID
), 
res (id, maxdate, interval, nextdate) 
as 
(
	select 
		b.id, 
		isnull(b.maxdate, 0), 
		case when nborder <= 1 then 0 else DATEDIFF(d, dbo.CU_CRMDate_To_Date(b.mindate), dbo.CU_CRMDate_To_Date(b.maxdate)) / b.nborder end, 
		case when nborder <= 1 then 0 else dbo.CU_Date_To_CRMDate(DATEADD(d, DATEDIFF(d, dbo.CU_CRMDate_To_Date(b.mindate), dbo.CU_CRMDate_To_Date(b.maxdate)) / b.nborder, dbo.CU_CRMDate_To_Date(b.maxdate))) end 
	from base b
) 
select 
	dbo.CU_ID_To_StaNo(r.id), 
	dbo.CU_ID_To_SerNo(r.id), 
	r.maxdate, 
	r.interval, 
	r.nextdate 
from res r 
inner join TE_ISI_C035 C035 on C035.ID = r.id and C035.F7025 <> r.maxdate and C035.F7026 <> r.interval and C035.F7027 <> r.nextdate
Ce requête tourne en 3 secondes et donne entière satisfaction.

Seulement, j'ai besoin de descendre à l'article.
Du coup je recherche les articles correspondants aux articles de regroupement indiqués sur les potentiels inner join TE_ISI_AR AR on AR.ID_AR_250 = C035.ID_AR and AR.LosKZ = 0.
Puis je recherche les informations des commandes utilisant les produits en question left outer join (TE_ISI_AU AU inner join TE_ISI_UP UP on UP.ID_AU = AU.ID and UP.LosKZ = 0) on AU.ID_FI = C035.ID_FI and UP.Artikelnr = AR.Artikelnr.

Ce qui donne :
Code sql : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
 
with base (id, mindate, maxdate, nborder) 
as 
(
	select 
		C035.ID, 
		min(AU.AufDat), 
		max(AU.AufDat), 
		count(AU.ID) 
	from TE_ISI_C035 C035 
	inner join TE_ISI_AR AR on AR.ID_AR_250 = C035.ID_AR and AR.LosKZ = 0
	left outer join 
	(
		TE_ISI_AU AU 
		inner join TE_ISI_UP UP on UP.ID_AU = AU.ID and UP.LosKZ = 0
	) on AU.ID_FI = C035.ID_FI and AU.LosKZ = 0 and (AU.AufDat / 10000) in (C035.F7007 - 1, C035.F7007) and UP.Artikelnr = AR.Artikelnr 
	where C035.DEL = 0 
	group by C035.ID
), 
res (id, maxdate, interval, nextdate) 
as 
(
	select 
		b.id, 
		isnull(b.maxdate, 0), 
		case when nborder <= 1 then 0 else DATEDIFF(d, dbo.CU_CRMDate_To_Date(b.mindate), dbo.CU_CRMDate_To_Date(b.maxdate)) / b.nborder end, 
		case when nborder <= 1 then 0 else dbo.CU_Date_To_CRMDate(DATEADD(d, DATEDIFF(d, dbo.CU_CRMDate_To_Date(b.mindate), dbo.CU_CRMDate_To_Date(b.maxdate)) / b.nborder, dbo.CU_CRMDate_To_Date(b.maxdate))) end 
	from base b
) 
select 
	dbo.CU_ID_To_StaNo(r.id), 
	dbo.CU_ID_To_SerNo(r.id), 
	r.maxdate, 
	r.interval, 
	r.nextdate 
from res r 
inner join TE_ISI_C035 C035 on C035.ID = r.id and C035.F7025 <> r.maxdate and C035.F7026 <> r.interval and C035.F7027 <> r.nextdate;

Et là c'est le drame : la requête n'en fini plus...

J'ai tenté de créer/modifier des index suivants (les index sont créés depuis l'application, donc à nouveau... j'ai pas la main sur leurs options, juste la liste et l'ordre des colonnes...) :
Code sql : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
 
CREATE UNIQUE NONCLUSTERED INDEX [TE_ISI_AR_AF302] ON [dbo].[TE_ISI_AR]
(
	[Artikelnr] ASC,
	[LosKZ] ASC,
	[ID_AR_250] ASC,
	[ID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
GO
 
CREATE UNIQUE NONCLUSTERED INDEX [TE_ISI_AU_AF310] ON [dbo].[TE_ISI_AU]
(
	[AufDat] ASC,
	[LosKZ] ASC,
	[Status] ASC,  -- inutile pour ma requête
	[ID_FI] ASC,
	[ID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
GO
 
CREATE UNIQUE NONCLUSTERED INDEX [TE_ISI_UP_AF301] ON [dbo].[TE_ISI_UP]
(
	[LosKZ] ASC,
	[ID_AU] ASC,
	[Artikelnr] ASC,
	[MNO] ASC,  -- inutile pour ma requête
	[ID] ASC
)
INCLUDE ( 	[ID_FI],
	[Artikelbez],  -- inutile pour ma requête
	[Menge],  -- inutile pour ma requête
	[F7001],  -- inutile pour ma requête
	[F7024]  -- inutile pour ma requête
) WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
GO
 
-- SQL Server ne veut pas l'utiliser, il prévère le suivant, que je ne peux pas modifier/supprimer
CREATE UNIQUE NONCLUSTERED INDEX [TE_ISI_C035_AF8] ON [dbo].[TE_ISI_C035]
(
	[ID] ASC,
	[F7025] ASC,
	[F7026] ASC,
	[F7027] ASC
)
INCLUDE ( 	[DEL]) WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
GO
 
-- Celui-là m'emmerde bien, je ne peux pas le modifier, et SQL Server préfère l'utiliser...
ALTER TABLE [dbo].[TE_ISI_C035] ADD PRIMARY KEY CLUSTERED 
(
	[ID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
GO
 
-- Celui-là est bien utilisé par contre
CREATE UNIQUE NONCLUSTERED INDEX [TE_ISI_C035_AF7] ON [dbo].[TE_ISI_C035]
(
	[F7007] ASC,
	[ID_FI] ASC,
	[ID_AR] ASC,
	[DEL] ASC,
	[ID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
GO

Je n'arrive pas à comprendre pourquoi d'un coup ça devient si long...
J'ai fait un produit cartésien sans l'en rendre compte ?