Il y a 2 jours, j'ai créé une fonction qui a besoin de INSERT
et UPDATE
, et j'ai activé XP_CMDShell
Et je l'ai fait en exécutant un script.
Par la suite, SQL Server fonctionne très lentement pour les commandes SELECT
. Même une instruction SELECT
très simple qui s'exécute après INSERT
via une commande séparée.
J'ai testé ce comportement sur d'autres bases de données et il donne le même résultat que SELECT
s'exécute après quelques minutes.
De plus, je l'ai testé sur 2 autres hôtes avec SQL Server 2014 et le résultat était le même.
Fonction que j'ai créée pour en obtenir la valeur dans une autre instruction select:
ALTER FUNCTION [Prg].[intCheckDelayedProcessProgram]
(
@SalesOrderProductID INT,
@MainProductTreeID INT,
@ProductTreeID INT,
@ProcessId INT,
@additionalDays INT = 2,
@currentDateReverceString VARCHAR(10) = NULL
)
RETURNS INT
AS
BEGIN
--declare @SalesOrderProductID INT = 40957,
-- @MainProductTreeID INT = 93758,
-- @ProductTreeID INT = 93758,
-- @ProcessId INT = 4472,
-- @additionalDays INT = 2,
-- @currentDateReverceString VARCHAR(10) = null -- '30/09/1398'
DECLARE @ProduceDailyProgramProductTree_Id INT, @ProgramQuantity INT, @startDate JalaliDate,
@CurrentDate JalaliDate, @lastDate JalaliDate, @additionalDate JalaliDate, @holiDaysCount INT;
IF(@currentDateReverceString IS NULL OR @currentDateReverceString = '')
SET @CurrentDate = dbo.GetCurrentJalaliDate();
ELSE
SET @CurrentDate = Gnr.RevercePersianDate(@currentDateReverceString);
IF (@additionalDays IS NULL)
SET @additionalDays = 2;
DECLARE @allDelayedItemsCount INT = 0;
BEGIN
DECLARE @sql NVARCHAR(4000), @cmd VARCHAR(4000);
DECLARE cursor_pdppt CURSOR
FOR SELECT pdppt.ID, pdppt.ProgramQuantity, pdppt.[Date] FROM Prg.ProduceDailyProgramProductTree pdppt
WHERE pdppt.SalesOrderProductID = @SalesOrderProductID
AND pdppt.MainProductTreeID = @MainProductTreeID
AND pdppt.ProductTreeID = @ProductTreeID
AND pdppt.Process = @ProcessId
AND ISNULL(pdppt.IsDelayed, 0) = 0
OPEN cursor_pdppt;
FETCH NEXT FROM cursor_pdppt INTO @ProduceDailyProgramProductTree_Id, @ProgramQuantity, @startDate;
WHILE @@FETCH_STATUS = 0
BEGIN
SET @lastDate = Gnr.RevercePersianDate([Prg].[intGetLastDateForDelayedProcessProgram](@startDate, @additionalDays, @CurrentDate, 1));
IF (@CurrentDate.GetDate() > @lastDate.GetDate())
BEGIN
IF ((SELECT COUNT(*) FROM Prg.ProduceDailyOperation pdo WHERE pdo.ProduceDailyProgramProductTreeID = @ProduceDailyProgramProductTree_Id AND pdo.Process = @ProcessId
AND pdo.[Date].GetDate() > @lastDate.GetDate()) = 0)
BEGIN
SET @allDelayedItemsCount = @allDelayedItemsCount + @ProgramQuantity;
END
ELSE
BEGIN
SET @allDelayedItemsCount = @allDelayedItemsCount +
(@ProgramQuantity -
(SELECT SUM(pdo.ProducedQuantity) FROM Prg.ProduceDailyOperation pdo
WHERE pdo.ProduceDailyProgramProductTreeID = @ProduceDailyProgramProductTree_Id
AND pdo.Process = @ProcessId AND pdo.[Date].GetDate() <= @lastDate.GetDate()));
END;
SELECT @sql = 'UPDATE [Prg].[ProduceDailyProgramProductTree] SET [IsDelayed] = 1 WHERE ID = ' + CONVERT(VARCHAR(10), @ProduceDailyProgramProductTree_Id);
SELECT @cmd = 'sqlcmd -S ' + @@SERVERNAME + ' -d ' + DB_NAME() + ' -Q "' + @sql + '"'
EXEC MASTER..XP_CMDSHELL @cmd , 'no_output'
END;
FETCH NEXT FROM cursor_pdppt INTO @ProduceDailyProgramProductTree_Id, @ProgramQuantity, @startDate;
END;
CLOSE cursor_pdppt;
IF (@allDelayedItemsCount > 0)
BEGIN
IF (EXISTS(SELECT 1 FROM Prg.ProduceDailyProgramProductTreeDelayed pdppt
WHERE pdppt.SalesOrderProductID = @SalesOrderProductID
AND pdppt.MainProductTreeID = @MainProductTreeID
AND pdppt.ProductTreeID = @ProductTreeID
AND pdppt.Process = @ProcessId))
BEGIN
SELECT @allDelayedItemsCount = @allDelayedItemsCount
+ (SELECT pdpptd.DelayedQuantity FROM Prg.ProduceDailyProgramProductTreeDelayed pdpptd
WHERE pdpptd.SalesOrderProductID = @SalesOrderProductID
AND pdpptd.MainProductTreeID = @MainProductTreeID
AND pdpptd.ProductTreeID = @ProductTreeID
AND pdpptd.Process = @ProcessId
AND ISNULL(pdpptd.Active, 0) = 1
AND ISNULL(pdpptd.IsDeleted, 0) = 0);
SELECT @sql = 'UPDATE [Prg].[ProduceDailyProgramProductTreeDelayed] SET DelayedQuantity = '
+ CONVERT(VARCHAR(10), @allDelayedItemsCount) + 'WHERE SalesOrderProductID = '
+ CONVERT(VARCHAR(10), @SalesOrderProductID) + 'AND MainProductTreeID = '
+ CONVERT(VARCHAR(10), @MainProductTreeID) + 'AND ProductTreeID = '
+ CONVERT(VARCHAR(10), @ProductTreeID) + 'AND Process = '
+ CONVERT(VARCHAR(10), @ProcessId) +';';
END
ELSE
BEGIN
SELECT @sql = 'INSERT INTO [Prg].[ProduceDailyProgramProductTreeDelayed] (SalesOrderProductID, MainProductTreeID, Process, ProductTreeID, DelayedQuantity, Active, IsDeleted) VALUES ('
+ CONVERT(VARCHAR(10), @SalesOrderProductID) + ', '
+ CONVERT(VARCHAR(10), @MainProductTreeID) + ', '
+ CONVERT(VARCHAR(10), @ProcessId) + ', '
+ CONVERT(VARCHAR(10), @ProductTreeID) + ', '
+ CONVERT(VARCHAR(10), @allDelayedItemsCount) + ', ''1'', ''0'')';
END;
SELECT @cmd = 'sqlcmd -S ' + @@SERVERNAME + ' -d ' + DB_NAME() + ' -Q "' + @sql + '"'
EXEC MASTER..XP_CMDSHELL @cmd , 'no_output'
END
ELSE
IF(EXISTS(SELECT 1 FROM Prg.ProduceDailyProgramProductTreeDelayed pdpptd
WHERE pdpptd.SalesOrderProductID = @SalesOrderProductID
AND pdpptd.MainProductTreeID = @MainProductTreeID
AND pdpptd.ProductTreeID = @ProductTreeID
AND pdpptd.Process = @ProcessId
AND ISNULL(pdpptd.Active, 0) = 1
AND ISNULL(pdpptd.IsDeleted, 0) = 0))
BEGIN
SELECT @allDelayedItemsCount =
(SELECT pdpptd.DelayedQuantity FROM Prg.ProduceDailyProgramProductTreeDelayed pdpptd
WHERE pdpptd.SalesOrderProductID = @SalesOrderProductID
AND pdpptd.MainProductTreeID = @MainProductTreeID
AND pdpptd.ProductTreeID = @ProductTreeID
AND pdpptd.Process = @ProcessId
AND ISNULL(pdpptd.Active, 0) = 1
AND ISNULL(pdpptd.IsDeleted, 0) = 0);
END;
END;
RETURN @allDelayedItemsCount;
END
JalaliDate
est un type défini par l'utilisateur pour contenir l'heure de date persane par Assembly.
Fonction Gnr.RevercePersianDate
Pour convertir la chaîne de date persane en JalaliDate. [Prg].[intGetLastDateForDelayedProcessProgram]
Autre fonction pour calculer la date spécifiée par les vacances que je pense que la dose ne pose pas.
MISE À JOUR 1
Je teste les codes de fonction en transmettant les valeurs des paramètres en déclarant et en définissant les valeurs de test. Ensuite, l'utilisateur Sql Server Profiler pour vérifier le script de la fonction, qui voit les scripts s'arrête sur cette ligne SET @CurrentDate = dbo.GetCurrentJalaliDate()
qui utilise la fonction de Assembly. Exécution en cours sans erreur et sans réponse !! Remarque: je teste SELECT dbo.GetCurrentJalaliDate()
qui répond très rapidement sans erreur!
MISE À JOUR 2
J'appelle la fonction [Prg].[intCheckDelayedProcessProgram]
Dans SP
et dans une instruction complexe SELECT
, qui ne peut pas appeler SP
dans select
, donc juste une solution pour moi, c'était définir la fonction. Code exécuté sur le serveur local avec toutes les données et autres SP et fonctions. S'il existe une alternative pour CURSOR
aidez-moi à réécrire la fonction avec.
À la place d'utiliser xp_cmdshell
pour exécuter des requêtes sur SQL Server, modifiez votre code pour utiliser procédures stockées , fonctions définies par l'utilisateur , ou même T-SQL dynamique appelé en utilisant sp_executesql
. Si vous devez exécuter du code sur un autre serveur, utilisez un serveur lié .
Enfin, pensez à réécrire votre code pour ne pas utiliser de curseur si possible. Les opérations basées sur des ensembles dans SQL Server sont beaucoup plus efficaces.
Étant donné que ces sujets sont tous des sujets extrêmement approfondis à eux seuls, je ne peux pas étoffer une réponse ici, mais j'espère que ces liens vous aideront. Bonne chance.
Vous devriez certainement trouver un meilleur moyen que de faire du DML via une fonction. Vous n'avez pas besoin d'attacher ce processus à une instruction SELECT
. Vous pouvez simplement vider les résultats de la requête dans une table temporaire et utiliser un CURSEUR pour itérer dessus (comme vous le faites dans cette fonction), en appelant une procédure stockée pour faire ce que vous faites actuellement dans cette fonction.
Cependant, à tout le moins, vous devez ajouter quelques options à votre curseur afin qu'elles ne verrouillent pas les tables de base:
DECLARE cursor_pdppt CURSOR LOCAL STATIC READ_ONLY FORWARD_ONLY
FOR ...
Et, immédiatement après le CLOSE cursor_pdppt;
, vous devez ajouter:
DEALLOCATE cursor_pdppt;