J'essaie d'optimiser cette fonction de valorisation de la table. Si je pouvais, je le changerais à la procédure mais je ne peux pas. Le problème est avec deux états de mise à jour. Je n'ai gardé que ces deux dans la fonction parce qu'ils causent les principaux problèmes de performance. J'ai réécrit le premier de l'externe s'appliquant à la jointure intérieure et j'ai examiné les statistiques et ils ont mal que j'ai tellement ajouté une option (recompiler) et cela a beaucoup contribué. Le problème est dans la deuxième mise à jour. Les statistiques sont fausses et je ne sais pas comment faire un plan d'exécution approprié et l'optimiser avec des allusions. Vous s'il vous plaît avoir une idée comment obtenir le temps de temps? J'ai essayé de indexer la variable de table mais sans résultat.
Voici un plan d'exécution https://www.brentozar.com/pastetheplan/?id=b1edbo5E4
Merci.
CREATE FUNCTION [dbo].[cfn_PlanServis_Seznam](
@IDVazRole INT,
@IDUzivatel INT,
@IDRole INT,
@IDLokalita INT,
@lCid INT
)
RETURNS @PlanServis TABLE(
lIDAuto INT,
szSPZ VARCHAR(100),
lDepozit INT,
szTypVozidla varchar(100),
szTypServisu NVARCHAR(300),
szServisniPlan NVARCHAR(300),
lZbyvaDni INT,
lZbyvaKm INT,
lNajetoKm INT,
dtServis DATETIME,
dcZbyvaMotohodin DECIMAL(15,1),
dcNajetoMotohodin DECIMAL(15,1),
IDVazPlanServisAuto INT,
IDPlanServisDefinice INT,
lBarva INT
)
AS
BEGIN
DECLARE @Auto TABLE(
lIDAuto INT,
szSPZ VARCHAR(100),
szTyp VARCHAR(100),
IDCisTypServis INT,
szTypServisu NVARCHAR(500),
szServisniPlan NVARCHAR(500),
lKmStart INT,
dtStart DATETIME,
lKmPriZavedeni INT,
lUjetoPredZavedenim INT,
dcMotohodinyStart DECIMAL(15,1),
lIntervalKm INT,
dcIntervalMotohodiny DECIMAL(15,1),
lUjeto INT,
dcMotohodiny DECIMAL(15,1),
IDServis INT,
lKmServis INT,
dcMotohodinyServis DECIMAL(15,1),
dtServis DATETIME,
lIntervalDatum INT,
lDniUbehlo INT,
lBarva INT,
lZbyvaKm INT,
dcZbyvaMotohodin DECIMAL(15,2),
lZbyvaDni INT,
lDepozit INT,
IDVazPlanServisAuto INT,
IDPlanServisDefinice INT,
lMaxTachograf INT,
lKmPretaceni INT,
dtOd DATE,
lKmPosledniServis INT
)
DECLARE @IDCisAutoParametrKmPriZavadeni INT = 10012
DECLARE @lKmPred INT = 30000
DECLARE @lKmPredMensi INT = 15000
DECLARE @lDniPred INT = 60
DECLARE @lDniPredMensi INT = 30
DECLARE @lMotohodinyPred INT = 100
DECLARE @lMotohodinyPredMensi INT = 50
DECLARE @IDBarvaBlizi INT = 1010 --Odkaz do CisTermBarva
DECLARE @IDBarvaBliziMensi INT = 1016 --Odkaz do CisTermBarva
DECLARE @IDBarvaPres INT = 1017 --Odkaz do CisTermBarva
--============ Koenc deklarace promennych ===========
INSERT INTO @Auto (lIDAuto, szSPZ, szTyp, IDCisTypServis, szTypServisu, lKmStart, dtStart, lKmPriZavedeni, dcMotohodinyStart,[@Auto].lIntervalKm,[@Auto].dcIntervalMotohodiny,[@Auto].lIntervalDatum,szServisniPlan,[@Auto].IDVazPlanServisAuto,[@Auto].IDPlanServisDefinice)
SELECT Auto.lIDAuto,
Auto.szSpz,
CASE WHEN Auto.lTyp = 0 THEN 'Taha?' WHEN Auto.lTyp = 1 THEN 'N?v?s' ELSE '' END,
PlanServisDefinice.IDCisTypServis,
dbo.GetLocalText('CisTypServis','szNazev',CisTypServis.lIDCisTypServis,@lCid,CisTypServis.szNazev,''),
PlanServisDefinice.lStartKM,
PlanServisDefinice.dtStartDatum,
CONVERT(INT,VazAutoParametr.varHodnota),
PlanServisDefinice.dcMotohodinyStart,
PlanServisDefinice.lIntervalKM,
PlanServisDefinice.dcIntervalMotohodin,
PlanServisDefinice.lIntervalDatum,
PlanServis.szNazev,
PlanServisDefinice.IDVazPlanServisAuto,
PlanServisDefinice.lIDPlanServisDefinice
FROM Auto INNER JOIN VazPlanServisAuto ON Auto.lIDAuto = VazPlanServisAuto.IDAuto
INNER JOIN PlanServisDefinice ON VazPlanServisAuto.lIDVazPlanServisAuto = PlanServisDefinice.IDVazPlanServisAuto
INNER JOIN CisTypServis ON PlanServisDefinice.IDCisTypServis = CisTypServis.lIDCisTypServis
LEFT OUTER JOIN VazAutoParametr ON VazAutoParametr.IDAuto = Auto.lIDAuto AND VazAutoParametr.IDCisAutoParametr = @IDCisAutoParametrKmPriZavadeni
INNER JOIN PlanServis ON VazPlanServisAuto.IDPlanServisu = PlanServis.lIDPlanServis
UPDATE @Auto SET lUjeto = Km.lKm
FROM @Auto INNER JOIN
(SELECT
SUM(JizdaTachograf.lkmDo - JizdaTachograf.lkmOd) AS lKm, JizdaTachograf.IDAuto,JizdaTachograf.IDNaves
FROM Jizda
INNER JOIN JizdaTachograf ON JizdaTachograf.IDJizda = Jizda.lIDJizda
WHERE JizdaTachograf.lkmOd IS NOT NULL
AND JizdaTachograf.lkmDo IS NOT NULL
AND Jizda.lProvozne = 1
GROUP BY
JizdaTachograf.IDAuto,JizdaTachograf.IDNaves
) as Km
ON Km.IDAuto = [@Auto].lIDAuto OR Km.IDNaves = [@Auto].lIDAuto
OPTION (RECOMPILE)
UPDATE @Auto SET lMaxTachograf = ISNULL(ISNULL(Km.lKm, [@Auto].lKmServis),[@Auto].lKmStart)
FROM @Auto
OUTER APPLY
(SELECT TOP 1 JizdaTachograf.lkmDo lKm
FROM Jizda INNER JOIN
JizdaTachograf ON JizdaTachograf.IDJizda = Jizda.lIDJizda
WHERE JizdaTachograf.lkmOd IS NOT NULL AND
JizdaTachograf.lkmDo IS NOT NULL AND Jizda.lProvozne = 1
AND (JizdaTachograf.IDAuto = [@Auto].lIDAuto
OR JizdaTachograf.IDNaves = [@Auto].lIDAuto)
AND Jizda.dtZacatek > ISNULL(ISNULL([@Auto].dtServis,
[@Auto].dtStart),DATEADD(YEAR,-100,GETDATE()))
ORDER BY
Jizda.dtZacatek DESC, JizdaTachograf.lkmDo desc) Km
INSERT INTO @PlanServis (lIDAuto,
szSPZ,
lDepozit,
szTypVozidla,
szTypServisu,
szServisniPlan,
lZbyvaDni,
lZbyvaKm,
lNajetoKm,
dtServis,
dcZbyvaMotohodin,
dcNajetoMotohodin,
IDVazPlanServisAuto,
IDPlanServisDefinice,
lBarva)
SELECT lIDAuto,
szSPZ,
lDepozit,
szTyp,
szTypServisu,
szServisniPlan,
lZbyvaDni,
lZbyvaKm,
lUjeto,--lNajetoKm,
dtServis,
dcZbyvaMotohodin,
dcMotohodiny,--dcNajetoMotohodin,
IDVazPlanServisAuto,
IDPlanServisDefinice,
lBarva
FROM @Auto
RETURN
END
GO
Pour une question comme celle-ci, il est très utile de fournir une [~ # ~ # ~ # ~] . Comme je devais gagner beaucoup de suppositions sur la structure de la table et la distribution des données. Vous dites que cette partie des plans de requête est trop lente sans autre élaboration:
Je peux voir trois raisons pour lesquelles cette partie pourrait être lente. Le premier problème est que les deux tables ne disposent que de 176 000 lignes totales. Pourtant, l'index cherche à tirer plus de 800 000 rangées des deux tables. Le deuxième problème est que l'indice rechercher sur Jizdatachograf n'atteint que le prédicat suivant: [Lori_MDL].[dbo].[JizdaTachograf].lkmOd IS NOT NULL
. Je suppose que cela pourrait être sélectif, mais sinon, vous numérisez efficacement la plupart des tables d'index 845. Le troisième numéro est au total de 800k lignes sont triés, bien que les sortes soient divisées en 846 itérations.
Il pourrait y avoir un moyen d'obtenir un plan qui fait simplement une seule analyse des deux tables, mais sans comprendre les distributions de données que je ne sais pas si cela en valait la peine. Les exigences de votre requête (inégalités, tri, OR logique) rendent difficile pour une jointure de fusion ou une jointure de hachage à travailler.
Un problème que vous pouvez résoudre est le second. Si vous définissez les bons indices et séparez (JizdaTachograf.IDAuto = [@Auto].lIDAuto OR JizdaTachograf.IDNaves = [@Auto].lIDAuto)
En deux sous-requêtes, vous pouvez obtenir plus d'index plus efficace sur JizdaTachograf
qui recherchent directement les lignes concernées. Cela pourrait économiser beaucoup de temps si la plupart des rangées de la table ont des valeurs non nulles pour lkmOd
. Il existe de nombreuses définitions d'index différents pouvant fonctionner. Deux sont ci-dessous:
CREATE INDEX IX2 ON JizdaTachograf (IDAuto, IDJizda, lkmDo) INCLUDE (lkmOd)
WHERE lkmOd IS NOT NULL AND lkmDo IS NOT NULL;
CREATE INDEX IX3 ON JizdaTachograf (IDNaves, IDJizda, lkmDo) INCLUDE (lkmOd)
WHERE lkmOd IS NOT NULL AND lkmDo IS NOT NULL;
J'ai ensuite séparé la requête afin que SQL Server puisse tirer parti des index.
UPDATE @Auto SET lMaxTachograf = ISNULL(ISNULL(Km.lKm, [@Auto].lKmServis),[@Auto].lKmStart)
FROM @Auto
OUTER APPLY
(
SELECT TOP (1) lKm
FROM
(
SELECT TOP (1) Jizda.dtZacatek, JizdaTachograf.lkmDo lKm
FROM JizdaTachograf
INNER JOIN Jizda WITH (INDEX(1)) ON JizdaTachograf.IDJizda = Jizda.lIDJizda
WHERE JizdaTachograf.lkmOd IS NOT NULL AND
JizdaTachograf.lkmDo IS NOT NULL AND Jizda.lProvozne = 1
AND JizdaTachograf.IDAuto = [@Auto].lIDAuto -- first half
AND Jizda.dtZacatek > ISNULL(ISNULL([@Auto].dtServis, [@Auto].dtStart),DATEADD(YEAR,-100,GETDATE()))
ORDER BY Jizda.dtZacatek DESC, JizdaTachograf.lkmDo desc
UNION ALL
SELECT TOP (1) Jizda.dtZacatek, JizdaTachograf.lkmDo lKm
FROM JizdaTachograf
INNER JOIN Jizda WITH (INDEX(1)) ON JizdaTachograf.IDJizda = Jizda.lIDJizda
WHERE JizdaTachograf.lkmOd IS NOT NULL AND
JizdaTachograf.lkmDo IS NOT NULL AND Jizda.lProvozne = 1
AND JizdaTachograf.IDNaves = [@Auto].lIDAuto -- second half
AND Jizda.dtZacatek > ISNULL(ISNULL([@Auto].dtServis, [@Auto].dtStart),DATEADD(YEAR,-100,GETDATE()))
ORDER BY Jizda.dtZacatek DESC, JizdaTachograf.lkmDo desc
) IDNaves_IDAuto
ORDER BY dtZacatek DESC, lKm DESC
) Km;
Je travaille avec des tables vides, mais je peux montrer qu'il est au moins possible d'obtenir la forme du plan souhaité:
L'avantage de ce plan est qu'il va faire moins IO sur JizdaTachograf
et que les sortes sont divisées encore plus loin. Cependant, vous tirez toujours le même nombre de lignes des index et triant le même nombre total de lignes.
Il est possible d'écrire cette requête pour qu'il n'y ait pas de tri. Le IO Motif est différent, ce qui pourrait entraîner moins de lecture globale. Vous aurez besoin d'un autre index. Vous trouverez ci-dessous un élément qui fonctionne:
CREATE INDEX IX1 ON Jizda (dtZacatek) INCLUDE (lIDJizda, lProvozne)
WHERE lProvozne = 1;
L'optimiseur ne peut pas toujours faire les mêmes conclusions que nous pouvons sur les données triées, j'ai donc changé la requête pour que cela comprenne que le tri n'est pas nécessaire:
UPDATE @Auto SET lMaxTachograf = ISNULL(ISNULL(Km.lKm, [@Auto].lKmServis),[@Auto].lKmStart)
FROM @Auto
OUTER APPLY
(
SELECT TOP (1) lkmDo lKm
FROM
(
SELECT TOP (1) Jizda.dtZacatek, ca.lkmDo
FROM Jizda
CROSS APPLY (
SELECT TOP (1) JizdaTachograf.lkmDo
FROM JizdaTachograf
WHERE JizdaTachograf.IDJizda = Jizda.lIDJizda
AND JizdaTachograf.lkmOd IS NOT NULL AND
JizdaTachograf.lkmDo IS NOT NULL
AND JizdaTachograf.IDNaves = [@Auto].lIDAuto -- this line is different
ORDER BY JizdaTachograf.lkmDo DESC
) ca
WHERE Jizda.lProvozne = 1
AND Jizda.dtZacatek > ISNULL(ISNULL([@Auto].dtServis,[@Auto].dtStart),DATEADD(YEAR,-100,GETDATE()))
ORDER BY Jizda.dtZacatek DESC
UNION ALL
SELECT TOP (1) Jizda.dtZacatek, ca.lkmDo
FROM Jizda
CROSS APPLY (
SELECT TOP (1) JizdaTachograf.lkmDo
FROM JizdaTachograf
WHERE JizdaTachograf.IDJizda = Jizda.lIDJizda
AND JizdaTachograf.lkmOd IS NOT NULL AND
JizdaTachograf.lkmDo IS NOT NULL
AND JizdaTachograf.IDAuto = [@Auto].lIDAuto -- this line is different
ORDER BY JizdaTachograf.lkmDo DESC
) ca
WHERE Jizda.lProvozne = 1
AND Jizda.dtZacatek > ISNULL(ISNULL([@Auto].dtServis,[@Auto].dtStart),DATEADD(YEAR,-100,GETDATE()))
ORDER BY Jizda.dtZacatek DESC
) IDNaves_IDAuto
ORDER BY dtZacatek DESC, lkmDo DESC
) Km
Maintenant, il n'y a pas de tri:
Cependant, c'est un peu une optimisation dangereuse à faire. Maintenant Jizda
est la table extérieure pour la jointure imbriquée de la boucle. Considérons une ligne dans @Auto
avec null pour [@Auto].dtServis
, Null pour [@Auto].dtStart
et pas de matchs contre JizdaTachograf
par IDNaves
et IDAuto
. SQL Server lira via toutes les lignes de 180 000 en Jizda
et do 180k Index cherche contre JizdaTachograf
pour ne renvoyer finalement aucune ligne de lignes. Je ne sais pas quelle est la probabilité que cela se produise, mais cela pourrait arriver.
Sur la base des informations fournies dans la question, mon conseil est d'essayer la première requête et de voir si cela le rend suffisamment rapide. Sinon, implémentez les deux requêtes avec des filtres sur eux. Numériser une variable de table de rangée 845 ne prend pas de temps. Vous pourrez peut-être obtenir la meilleure des deux requêtes avec deux déclarations de mise à jour distinctes qui fonctionnent sur différentes parties de la table. La première requête peut être plus efficace lorsqu'il n'y a pas de colonne de date non nulle:
WHERE [@Auto].dtServis IS NULL AND [@Auto].dtStart IS NULL;
La deuxième requête peut être plus efficace lorsqu'il existe une colonne de date non nulle (je suppose que la colonne est quelque peu sélective):
WHERE [@Auto].dtServis IS NOT NULL OR [@Auto].dtStart IS NOT NULL
Je pense qu'une partie de la requête qui met à jour @Auto Table peut être insérée plus loin dans la variable du tableau.
Pour que, encore une fois, la même requête ne soit pas répétée. Elle stimulera la performance.
S'il vous plaît comprendre l'idée et faire le bon possible.
declare @table TABLE(lkmDo int, lkmOd int,IDAuto int, IDNaves int,dtZacatek datetime )
insert into @table
SELECT JizdaTachograf.lkmDo ,JizdaTachograf.lkmOd ,IDAuto,IDNaves,dtZacatek
FROM Jizda INNER JOIN
JizdaTachograf ON JizdaTachograf.IDJizda = Jizda.lIDJizda
WHERE JizdaTachograf.lkmOd IS NOT NULL AND
JizdaTachograf.lkmDo IS NOT NULL AND Jizda.lProvozne = 1
AND (JizdaTachograf.IDAuto = [@Auto].lIDAuto
OR JizdaTachograf.IDNaves = [@Auto].lIDAuto)
-- OPTION (RECOMPILE)
UPDATE @Auto SET lUjeto = Km.lKm
FROM @Auto INNER JOIN
(SELECT
SUM(lkmDo - lkmOd) AS lKm, IDAuto,IDNaves
FROM @table
GROUP BY
JizdaTachograf.IDAuto,JizdaTachograf.IDNaves
) as Km
ON Km.IDAuto = [@Auto].lIDAuto OR Km.IDNaves = [@Auto].lIDAuto
UPDATE @Auto SET lMaxTachograf = ISNULL(ISNULL(Km.lKm, [@Auto].lKmServis),[@Auto].lKmStart)
FROM @Auto
OUTER APPLY
(SELECT TOP 1 JizdaTachograf.lkmDo lKm
FROM @table
WHERE
Jizda.dtZacatek > ISNULL(ISNULL([@Auto].dtServis,
[@Auto].dtStart),DATEADD(YEAR,-100,GETDATE()))
ORDER BY
Jizda.dtZacatek DESC, JizdaTachograf.lkmDo desc) Km