web-dev-qa-db-fra.com

La même requête s'exécute plus rapidement en dehors de la proc

Nous avons une requête spécifique qui s'exécute beaucoup plus lentement à l'intérieur d'un proc. Je dois ajouter ici qu'il est enfermé dans un curseur à deux niveaux. Cependant, les deux curseurs ont un ensemble de résultats d'itération d'une ligne.

Permettez-moi d’abord de dire ce que nous avons essayé et échoué:

  • Éviter le sniffing de paramètres en utilisant option (recompiler) et option (optiimize for (@var UNKNOWN)
  • Ce fil . Les variables qui semblent poser problème sont en réalité des variables locales et non des paramètres proc.

Voici la requête telle que prise à l'intérieur des proc/curseurs.

 select @tpdim1 = dim1, @tpdim2 = dim2, @typecalc = typecalc
    from loyalty_policy where code=@loop2_loyalty_policy

Remarque: @ loop2_loyalty_policy est la variable extraite du résultat du curseur interne et a une valeur. code est PK dans la table loyalty_policy. Ainsi, @ tpdim1 et @ tpdim2 ont chacun une valeur unique.

SET STATISTICS PROFILE ON 
SET STATISTICS    xml on           
                  insert into @tbl_loyal_loop2 (cnt, store, map, pda, insdate, line, item, loyalty_policy_data, loyal_calc, loyalty_policy)
                  select @cnt, t.store, t.map, t.pda, t.insdate, t.line, t.item, ld.tab_id,  
                  case @typecalc
                        when 1 then convert(bigint,round(isnull(t.valueFromTran,0.00) * ld.value , 0 ) )
                        when 2 then convert(bigint,round(isnull(t.netvalue,0.00) * ld.value , 0 ) )
                        when 3 then convert(bigint,isnull(t.qty,0) * ld.value )
                        when 4 then convert(bigint,round(isnull(t.valueFromPrice2,0.00) * ld.value , 0 ) )
                        when 5 then convert(bigint,round(isnull(t.valueFromPrice3,0.00) * ld.value , 0 ) )
                        when 6 then convert(bigint,round(isnull(t.valueFromPrice4,0.00) * ld.value , 0 ) )
                  else 0 end
                  ,@loop2_loyalty_policy
                  from loyalty_policy_data ld-- with (index=ind_loyalty_policy_02)
                              inner join #tbl_data t on t.insdate >= ld.fdateactive and t.insdate <= ld.tdateactive
                  where ld.loyalty_policy = @loop2_loyalty_policy 
                  and ld.tdateactive >= @from_rundate and ld.fdateactive <= @to_rundate
                  and t.dbupddate > @loop1_dbupddate  
                  and
                        case when @tpdim1 is null then '' 
                        else  
                              case  @tpdim1 
                                    when 'STORE'            then t.store when 'BRAND' then t.brand  when 'CAT1' then t.cat1   when 'CAT2' then t.cat2   when 'CAT3' then t.cat3   when 'ITEM' then t.item    
                                    when 'CUSTGROUP'  then t.custgroup when 'CUSTGROUP2' then t.custgroup2 when 'CUSTGROUP3' then t.custgroup3
                                    when 'CUSTOMER'         then @customer
                              else '' end
                        end
                        = case when @tpdim1 is null then '' else ld.dim1 end
                  and 
                        case when @tpdim2 is null then '' 
                        else  
                              case  @tpdim2 
                                    when 'STORE'            then t.store when 'BRAND' then t.brand  when 'CAT1' then t.cat1   when 'CAT2' then t.cat2   when 'CAT3' then t.cat3   when 'ITEM' then t.item    
                                    when 'CUSTGROUP'  then t.custgroup when 'CUSTGROUP2' then t.custgroup2 when 'CUSTGROUP3' then t.custgroup3
                                    when 'CUSTOMER'         then @customer                     
                              else '' end
                        end
                        = case when @tpdim2 is null then '' else ld.dim2 end
SET STATISTICS    xml off    

Le SET STATISTICS XML pour ce qui précède renvoie ce plan .

En essayant de le déboguer, nous avons isolé la requête sous la forme suivante (ici, vous pouvez également voir comment la table #a est créée, laquelle contient exactement les mêmes données que la précédente #tbl_data):

drop table #a;
select dt.dbupddate, dt.insdate, dt.map, dt.pda, pt.line, pt.item, 
( pt.exp_qty - pt.imp_qty)  as qty,  
( pt.exp_value + pt.imp_value )  as netvalue, 
( (document.exp_val - document.imp_val) * (pt.netvalue - pt.vat_value) )  as valueFromTran,  
( (document.exp_val - document.imp_val) * ( ( (pt.qty - pt.qty_gift) * isnull(pt.price2,0.00) ) * (1.00-( pt.disc_perc / 100)) ) ) as valueFromPrice2, 
( (document.exp_val - document.imp_val) * ( ( (pt.qty - pt.qty_gift) * isnull(pt.price3,0.00) ) * (1.00-( pt.disc_perc / 100)) ) ) as valueFromPrice3, 
( (document.exp_val - document.imp_val) * ( ( (pt.qty - pt.qty_gift) * isnull(pt.price4,0.00) ) * (1.00-( pt.disc_perc / 100)) ) ) as valueFromPrice4, 
dt.store, item.brand, item.cat1, item.cat2, item.cat3, customer.custgroup, customer.custgroup2, customer.custgroup3 
into #a
from document with (nolock) 
      inner join dt with (nolock) on dt.doccode = document.code 
      inner join store with (nolock) on store.code = dt.store and store.calc_loyal = 1 
      inner join customer with (nolock) on customer.code = dt.customer  
      inner join pt with (nolock) on dt.map = pt.map and dt.pda=pt.pda 
      inner join item with (nolock) on item.code = pt.item and item.itemtype in (select code from itemtype with (nolock) where vsales = 1)
where dt.canceled = 0 and document.is_opposite = 0 and document.type = 3 and dt.customer=N'EL4444444'
and dt.insdate >= '20180109' and dt.insdate <= '20190108' ;



SET STATISTICS PROFILE ON 
                  select t.store, t.map, t.pda, t.insdate, t.line, t.item, ld.tab_id,  
                  case 4
                        when 1 then convert(bigint,round(isnull(t.valueFromTran,0.00) * ld.value , 0 ) )
                        when 2 then convert(bigint,round(isnull(t.netvalue,0.00) * ld.value , 0 ) )
                        when 3 then convert(bigint,isnull(t.qty,0) * ld.value )
                        when 4 then convert(bigint,round(isnull(t.valueFromPrice2,0.00) * ld.value , 0 ) )
                        when 5 then convert(bigint,round(isnull(t.valueFromPrice3,0.00) * ld.value , 0 ) )
                        when 6 then convert(bigint,round(isnull(t.valueFromPrice4,0.00) * ld.value , 0 ) )
                  else 0 end
                  ,'003'
                  --select count(*)
                  from loyalty_policy_data ld with (index=ind_loyalty_policy_02)
                              inner join #a t on t.insdate >= ld.fdateactive and t.insdate <= ld.tdateactive
                  where ld.loyalty_policy = '003' 
                  --and ld.tdateactive >= '20180109' and ld.fdateactive <= '20190108'
                  and t.dbupddate > '20000101'
      and 
                        case when 'CUSTOMER' is null then '' 
                        else  
                              case  'CUSTOMER' 
                                    when 'STORE'            then t.store when 'BRAND' then t.brand  when 'CAT1' then t.cat1   when 'CAT2' then t.cat2   when 'CAT3' then t.cat3   when 'ITEM' then t.item    
                                    when 'CUSTGROUP'  then t.custgroup when 'CUSTGROUP2' then t.custgroup2 when 'CUSTGROUP3' then t.custgroup3
                                    when 'CUSTOMER'         then 'EL0134366'
                              else '' end
                        end
                        = case when 'CUSTOMER' is null then '' else ld.dim1 end
                  and 
                        case when 'BRAND' is null then '' 
                        else  
                              case  'BRAND' 
                                    when 'STORE'            then t.store when 'BRAND' then t.brand  when 'CAT1' then t.cat1   when 'CAT2' then t.cat2   when 'CAT3' then t.cat3   when 'ITEM' then t.item    
                                    when 'CUSTGROUP'  then t.custgroup when 'CUSTGROUP2' then t.custgroup2 when 'CUSTGROUP3' then t.custgroup3
                                    when 'CUSTOMER'         then 'EL0134366'

                              else '' end
                        end
                        = case when 'BRAND' is null then '' else ld.dim2 end
SET STATISTICS PROFILE off    

Et ici est le plan d'exécution. Cela fonctionne beaucoup plus vite.

Pourquoi cette énorme différence? De ma connaissance limitée de l'analyse de l'exécution, j'ai remarqué 

  1. La première requête (lente), sur l'opération index spool, a une estimation de lignes de ~ 9700 mais des lignes réelles de 3 millions. 
  2. La deuxième requête a utilisé de nombreuses opérations avec parallélisme
  3. La seule différence "réelle" que je peux voir dans la deuxième requête concerne les valeurs substituées manuellement des valeurs @ tpdim1 et @ tpdim2. Bien sûr, lorsque nous sommes entrés dans le code proc de la première requête et avons remplacé les @ tpdim1 & @ tpdim2 par les valeurs uniques qu'ils devraient obtenir, il exécutait aussi rapidement que la seconde requête.

Pourriez-vous expliquer cette différence et proposer des conseils pour résoudre le problème?


Edit: Comme recommandé par Laughing Vergil, j’ai remplacé les littéraux de la seconde requête par des variables précédemment déclarées. 


Edit 2: J'ai quelques informations supplémentaires provenant de recherches plus poussées.

Premièrement, j'ai isolé le problème à cette ligne:

case when @tpdim1 is null then '' <- Ceci utilise le plan lent

case when 'CUSTOMER' is null then '' <- Ceci utilise le plan rapide

Ceci est vrai dans la requête ad-hoc, pas besoin de se soucier de spcs et/ou de curseurs.

Cela continue même si je modifie le code selon la structure dynamique recommandée.

Je n'ai pas encore créé de données d'échantillons, mais l'info importante (comme on peut le voir dans les plans) est que loyalty_policy_data contient environ 720k lignes si nous ne filtrons que par loyalty_policy = @loop2_loyalty_policy. Toutefois, si nous évaluons la condition @ tpdim1, qui est essentiellement dim1 = N'EL0134366 ', les lignes renvoyées ne sont que 4.

La différence dans le plan réside donc dans l’évaluation de cette condition par rapport aux conditions de contrôle de la date.

Dans le plan rapide, il est évalué en premier. Lors de la recherche de l'index pour la valeur de la stratégie de fidélité, un prédicat (non recherché) est ajouté. Bien que ce prédicat ne soit pas dans l'index, les lignes renvoyées sont 4 et tous les autres opérateurs ont des tailles "logiques".

En revanche, le plan lent ignore douloureusement ce prédicat trop tard. Si j'ai correctement interprété, cela crée une boucle imbriquée dans loyalty_policy_data en tant que table externe (ce qui est fou). Il passe les colonnes nécessaires en tant que références externes. Pour chacun de ces Tuple, la spool d'index analyse la #table (~ 1k lignes) et trouve environ 250 résultats, puis transmet au filtre lequel enfin effectue le filtrage tpdim1. Ainsi, 250 * 700k lignes sont transmises à l'opérateur de filtrage.

Alors maintenant, je pense savoir ce qui se passe. Mais je ne peux pas comprendre pourquoi. 

6
George Menoutis

Pour répondre à ta question:

Une explication claire et reproductible du comment et du pourquoi de l’analyseur de requêtes se comporte différemment dans ces cas

L'optimiseur de requêtes se comporte différemment dans ces cas-là, car le plan avec variables doit être valide pour toute valeur future possible des paramètres any. Aussi, optimiser génère un plan générique complexe qui produirait des résultats corrects même lorsque les paramètres sont NULL.

Le plan avec des littéraux (et non des variables) est généralement plus efficace, car l’optimiseur peut grandement simplifier votre logique CASE pendant la phase de compilation du plan. L'optimiseur a plus de chances de choisir la forme de plan optimale, car il est plus facile pour l'optimiseur de prendre en compte les informations disponibles sur les index et les estimations de cardinalité lorsque la requête est plus simple et que les filtres ont des valeurs connues.


Martin Smith dans le commentaire, vous indiquez que vous utilisez la version du serveur 10.0.2531.0, version 2008 SP1, pour laquelle l'optimisation de l'intégration du paramètre n'est pas activée. Vous aurez besoin de moins de SP1 CU5 sur cette branche pour que la fonction OPTION (RECOMPILE) fonctionne correctement (comme je l’attendais dans les explications ci-dessous).

Erland Sommarskog en parle également dans son article mentionné ci-dessous. Il dit que vous devez être sur au moins SP2.

Si vous ne parvenez pas à mettre à jour le serveur, consultez l'ancienne version de l'article d'Erland Conditions de recherche dynamique dans la version T-SQL pour SQL 2005 et versions antérieures pour savoir comment gérer cette situation lorsque OPTION (RECOMPILE) correct disponible.


Voici ma réponse originale.

Je sais que vous avez dit que vous aviez essayé, mais je vous demanderais quand même de vérifier. En regardant vos symptômes, OPTION (RECOMPILE) devrait vous aider. 

Vous devez ajouter cette option à la requête principale. Pas à l'ensemble de la procédure stockée. Comme ça:

insert into @tbl_loyal_loop2 (cnt, store, map, pda, insdate, line, item, loyalty_policy_data, loyal_calc, loyalty_policy)
select @cnt, t.store, t.map, t.pda, t.insdate, t.line, t.item, ld.tab_id,  
case @typecalc
    when 1 then convert(bigint,round(isnull(t.valueFromTran,0.00) * ld.value , 0 ) )
    when 2 then convert(bigint,round(isnull(t.netvalue,0.00) * ld.value , 0 ) )
    when 3 then convert(bigint,isnull(t.qty,0) * ld.value )
    when 4 then convert(bigint,round(isnull(t.valueFromPrice2,0.00) * ld.value , 0 ) )
    when 5 then convert(bigint,round(isnull(t.valueFromPrice3,0.00) * ld.value , 0 ) )
    when 6 then convert(bigint,round(isnull(t.valueFromPrice4,0.00) * ld.value , 0 ) )
else 0 end
,@loop2_loyalty_policy
from loyalty_policy_data ld -- with (index=ind_loyalty_policy_02)
            inner join #tbl_data t on t.insdate >= ld.fdateactive and t.insdate <= ld.tdateactive
where ld.loyalty_policy = @loop2_loyalty_policy 
and ld.tdateactive >= @from_rundate and ld.fdateactive <= @to_rundate
and t.dbupddate > @loop1_dbupddate  
and
    case when @tpdim1 is null then '' 
    else  
            case  @tpdim1 
                when 'STORE'            then t.store when 'BRAND' then t.brand  when 'CAT1' then t.cat1   when 'CAT2' then t.cat2   when 'CAT3' then t.cat3   when 'ITEM' then t.item    
                when 'CUSTGROUP'  then t.custgroup when 'CUSTGROUP2' then t.custgroup2 when 'CUSTGROUP3' then t.custgroup3
                when 'CUSTOMER'         then @customer
            else '' end
    end
    = case when @tpdim1 is null then '' else ld.dim1 end
and 
    case when @tpdim2 is null then '' 
    else  
            case  @tpdim2 
                when 'STORE'            then t.store when 'BRAND' then t.brand  when 'CAT1' then t.cat1   when 'CAT2' then t.cat2   when 'CAT3' then t.cat3   when 'ITEM' then t.item    
                when 'CUSTGROUP'  then t.custgroup when 'CUSTGROUP2' then t.custgroup2 when 'CUSTGROUP3' then t.custgroup3
                when 'CUSTOMER'         then @customer                     
            else '' end
    end
    = case when @tpdim2 is null then '' else ld.dim2 end
OPTION(RECOMPILE);

OPTION (RECOMPILE) n’est pas le cas pour alléger le reniflage de paramètres, mais pour permettre à l’optimiseur d’aligner les valeurs réelles des paramètres dans la requête. Cela donne à l'optimiseur la liberté de simplifier la logique de requête.

Le type de votre requête ressemble à Conditions de recherche dynamique et je vous recommande vivement de lire cet article de Erland Sommarskog.

Aussi, au lieu de 

and
    case when @tpdim1 is null then '' 
    else  
            case  @tpdim1 
                when 'STORE'            then t.store when 'BRAND' then t.brand  when 'CAT1' then t.cat1   when 'CAT2' then t.cat2   when 'CAT3' then t.cat3   when 'ITEM' then t.item    
                when 'CUSTGROUP'  then t.custgroup when 'CUSTGROUP2' then t.custgroup2 when 'CUSTGROUP3' then t.custgroup3
                when 'CUSTOMER'         then @customer
            else '' end
    end
    = case when @tpdim1 is null then '' else ld.dim1 end

Je l'écrirais un peu différemment:

and
(
    @tpdim1 is null
    OR
    (
            ld.dim1 =
            case @tpdim1
                when 'STORE'      then t.store 
                when 'BRAND'      then t.brand  
                when 'CAT1'       then t.cat1   
                when 'CAT2'       then t.cat2   
                when 'CAT3'       then t.cat3   
                when 'ITEM'       then t.item    
                when 'CUSTGROUP'  then t.custgroup 
                when 'CUSTGROUP2' then t.custgroup2 
                when 'CUSTGROUP3' then t.custgroup3
                when 'CUSTOMER'   then @customer
                else ''
            end
    )
)

Avec OPTION (RECOMPILE) lorsque @tpdim1 a pour valeur CUSTOMER et que @customer a pour valeur EL0134366, l’optimiseur doit transformer cette instruction en une simple

and
(
    ld.dim1 = `EL0134366`
)

il pourrait ensuite utiliser un index approprié ou estimer le nombre de lignes de manière plus précise et prendre une meilleure décision quant à la forme du plan. Avec cette option, le plan ne serait valide que pour cette valeur spécifique du paramètre.

Notez que option (optimize for UNKNOWN) ne peut pas aider ici. optimize for UNKNOWN devrait générer un plan générique valide pour toute valeur possible de paramètres.

1
Vladimir Baranov

après avoir nettoyé la requête à des fins de lisibilité, j’ai le texte suivant.

insert into @tbl_loyal_loop2 
( cnt, 
  store, 
  map, 
  pda, 
  insdate, 
  line, 
  item, 
  loyalty_policy_data, 
  loyal_calc, 
  loyalty_policy
)
select 
      @cnt, 
      t.store, 
      t.map, 
      t.pda, 
      t.insdate, 
      t.line, 
      t.item, 
      ld.tab_id,
      convert(bigint, round( coalesce(
         case @typecalc
               when 1 then t.valueFromTran
               when 2 then t.netvalue
               when 3 then t.qty
               when 4 then t.valueFromPrice2
               when 5 then t.valueFromPrice3
               when 6 then t.valueFromPrice4
               else 0 
            END,   0.00) * ld.value , 0 ) ),
      @loop2_loyalty_policy
   from 
      loyalty_policy_data ld  -- with (index=ind_loyalty_policy_02)
         inner join #tbl_data t 
            on t.insdate >= ld.fdateactive 
            and t.insdate <= ld.tdateactive
   where 
          ld.loyalty_policy = @loop2_loyalty_policy 
      and ld.tdateactive >= @from_rundate 
      and ld.fdateactive <= @to_rundate
      and t.dbupddate > @loop1_dbupddate  
      and (   @tpdim1 is null
           OR ld.dim1 = case @tpdim1
                           when 'STORE' then t.store 
                           when 'BRAND' then t.brand  
                           when 'CAT1' then t.cat1   
                           when 'CAT2' then t.cat2   
                           when 'CAT3' then t.cat3   
                           when 'ITEM' then t.item    
                           when 'CUSTGROUP' then t.custgroup 
                           when 'CUSTGROUP2' then t.custgroup2 
                           when 'CUSTGROUP3' then t.custgroup3
                           when 'CUSTOMER' then @customer
                           else ''
                          END )
      and (   @tpdim2 is null
           OR ld.dim2 = case when @tpdim1
                         when 'STORE' then t.store 
                         when 'BRAND' then t.brand  
                         when 'CAT1' then t.cat1
                         when 'CAT2' then t.cat2
                         when 'CAT3' then t.cat3
                         when 'ITEM' then t.item    
                         when 'CUSTGROUP' then t.custgroup 
                         when 'CUSTGROUP2' then t.custgroup2 
                         when 'CUSTGROUP3' then t.custgroup3
                         when 'CUSTOMER' then @customer
                         else '' 
                      END )

De plus, je m'assurerais que vous ayez un index composite dans votre table loyalty_policy_data ... index sur (loyalty_policy, tdateactive, fdateactive, dbupdate, dim1, dim2)

De cette façon, vous qualifiez tous les champs utilisés dans vos critères de filtrage WHERE. Ne comptez pas uniquement sur l'index de la clé ... mais la clé PLUS les dates vous aidera à optimiser la plage de dates spécifique sans avoir à revenir aux pages de données brutes, mais vous pouvez optimiser les conditions de requête de la requête JOIN en fonction des valeurs de l'INDEX. .

En ce qui concerne votre table temporaire #tbl_data, assurez-vous que vous avez un index (insdate) car il s'agit du seul critère de base JOIN (au cas où vous ne possédez pas déjà d'index sur cette table).

COMMENTAIRE -

De votre commentaire sur la requête lente vs rapide basée sur la valeur null de 

@ tpdim1 = NULL vs 'CUSTOMER' = NULL

une chaîne fixe 'CUSTOMER' n'est JAMAIS nulle, il n'est donc jamais nécessaire de la prendre en compte dans le chemin nul. Chaîne fixe 'CUSTOMER' vs la variable @ client étant nulle ou comparée à dans le cas/quand ld.dim1 et ld.dim2 respectivement comparés à null ... peut-être que ce qui doit être testé doit être changé

  and (   @tpdim1 is null
               OR ld.dim1 = case @tpdim1
                               when 'STORE' then t.store 
                               when 'BRAND' then t.brand  ... end
     )

à

  and ld.dim1 = case @tpdim1
                when NULL then ''
                when 'STORE' then t.store 
                when 'BRAND' then t.brand  ... end

Idem avec le cas/quand ld.dim2. Incluez le "NULL" comme première valeur testée pour les tests @ tpdim1 (et @ tpdim2).

4
DRapp

De manière générale, une requête avec literal value est plus rapide qu'une requête avec proc parameter ou local variable.

Lorsque la valeur littérale est utilisée, Optimizer créera un plan spécial uniquement pour cette valeur si Forced Parameterization n'est pas activé.

Optimizer peut également créer Trivial Plan ou Simple Parameterize Plan, mais dans votre cas, cela n’est pas vrai.

Lorsque vous utilisez un paramètre, l'optimiseur crée un plan pour ce paramètre .__, appelé Parameter Sniffing, .__, puis le réutilise. 

Option Recompile est un moyen de résoudre ce problème: Créez un plan pour chaque valeur de variable différente, afin de conserver «l'estimation de cardinalité». C'est très court

Par conséquent, une requête avec une valeur littérale sera toujours plus rapide.

Permettez-moi d’abord de dire ce que nous avons essayé et échoué:

• Éviter de rechercher des paramètres à l'aide de l'option (recompiler) et de l'option (optiimize for (@var UNKOWN)

•Ce fil. Les variables qui semblent être le problème sont en réalité les locales et non les paramètres de proc.

Vous échouez car votre requête est très mal écrite (avec tout le respect que je vous dois).

Ne pas utiliser le curseur. Il semble que le curseur peut être évité dans votre cas

Une requête proc complète avec un paramètre variable, car la logique pour obtenir une valeur dans @ loop2_loyalty_policy, etc., n'est pas claire. Cela aidera à donner la suggestion correcte "pour éviter le curseur".

case when @tpdim1 is null: Cette logique complète peut être créée et insérée dans la table Temp elle-même, de sorte que la nouvelle colonne soit immédiatement utilisée dans join. J'espère que vous êtes capable de comprendre mon idée et son langage.

1.La première requête (lente), sur l'opération de spool d'index, contient environ 9700 lignes environ, mais les lignes effectives sont de 3 millions.

En raison d'une estimation élevée de la cardinalité par optmizer, en cas de mauvaise connexion 

Je ne sais pas si cela améliorera certainement votre requête et l'estimation de la cardinalité, car je n'ai pas encore bien compris votre requête.

Mais changer de condition de joint aide souvent comme,

Lisez attentivement ici, je ne sais pas quelles sont les données présentes dans les colonnes loyalty_policy et t.insdate.Il ne semble pas que vous ayez besoin d'une jointure aussi compliquée que celle décrite ci-dessous.

Au cas où vous en auriez vraiment besoin, vous pouvez alter join condition comme ci-dessous une fois.

from loyalty_policy_data ld with (nolock)
 inner join #tbl_data t on ld.loyalty_policy = @loop2_loyalty_policy
 and ld.tdateactive >= @from_rundate and ld.fdateactive <= @to_rundate 
and t.insdate >= ld.fdateactive and t.insdate <= ld.tdateactive
                  where t.dbupddate > @loop1_dbupddate 

L'objectif principal est d'éviter le curseur.

0
KumarHarsh