2012-09-05 6 views
1

Je viens de publier du code en production qui provoque des erreurs de manière aléatoire. J'ai déjà corrigé le problème en changeant totalement la façon dont je faisais la requête. Cependant, cela me dérange toujours que je ne sais pas ce qui causait le problème en premier lieu alors je me demandais si quelqu'un pourrait connaître la réponse. J'ai la requête suivante à l'intérieur d'une procédure stockée. Je ne cherche pas de commentaires à propos de ce n'est pas une bonne pratique pour faire des requêtes avec des appels de fonctions imbriquées et des choses comme ça :-). Je veux juste vraiment savoir pourquoi cela ne fonctionne pas de manière cohérente. Au hasard, la fonction dans la requête retournera une valeur non numérique et provoquera une erreur sur la jointure. Cependant, si je réexécute immédiatement la requête, cela fonctionne correctement.Résultats incohérents SQL Server 2008

SELECT  cscsf.cloud_server_current_software_firewall_id, 
       dbo.fn_GetCustomerFriendlyFromRuleName(cscsf.rule_name, np.policy_name) as rule_name, 
       cscsf.rule_action, 
       cscsf.rule_direction, 
       cscsf.source_address, 
       cscsf.source_mask, 
       cscsf.destination_address, 
       cscsf.destination_mask, 
       cscsf.protocol, 
       cscsf.port_or_port_range, 
       cscsf.created_date_utc, 
       cscsf.created_by 
    FROM  CLOUD_SERVER_CURRENT_SOFTWARE_FIREWALL cscsf 
    LEFT JOIN CLOUD_SERVER cs 
    ON   cscsf.cloud_server_id = cs.cloud_server_id 
    LEFT JOIN CLOUD_ACCOUNT cla 
    ON   cs.cloud_account_id = cla.cloud_account_id 
    LEFT JOIN CONFIGURATION co 
    ON   cla.configuration_id = co.configuration_id 
    LEFT JOIN DEDICATED_ACCOUNT da 
    ON   co.dedicated_account_id = da.dedicated_account_id 
    LEFT JOIN CORE_ACCOUNT ca 
    ON   da.core_account_number = ca.core_account_id 
    LEFT JOIN NETWORK_POLICY np 
    ON   np.network_policy_id = (select dbo.fn_GetIDFromRuleName(cscsf.rule_name)) 
    WHERE  cs.cloud_server_id = @cloud_server_id 
    AND   cs.current_software_firewall_confg_guid = cscsf.config_guid 
    AND   ca.core_account_id IS NOT NULL 
    ORDER BY cscsf.rule_direction, cscsf.cloud_server_current_software_firewall_id 

si vous remarquez la jointure

ON   np.network_policy_id = (select dbo.fn_GetIDFromRuleName(cscsf.rule_name)) 

appelle une fonction.

est que la fonction:

ALTER FUNCTION [dbo].[fn_GetIDFromRuleName] 
(
    @rule_name    varchar(100) 
) 
RETURNS varchar(12) 
AS 
BEGIN 
    DECLARE  @value  varchar(12) 

     SET @value = dbo.fn_SplitGetNthRow(@rule_name, '-', 2) 
     SET @value = dbo.fn_SplitGetNthRow(@value, '_', 2) 
     SET @value = dbo.fn_SplitGetNthRow(@value, '-', 1) 

    RETURN  @value 
END 

qui appelle ensuite cette fonction:

ALTER FUNCTION [dbo].[fn_SplitGetNthRow] 
(
    @sInputList  varchar(MAX), 
    @sDelimiter  varchar(10) = ',', 
    @sRowNumber  int = 1 
) 
RETURNS varchar(MAX) 
AS 
BEGIN 
    DECLARE  @value  varchar(MAX) 

    SELECT  @value = data_split.item 
         FROM 
         (
          SELECT *, ROW_NUMBER() OVER (ORDER BY (SELECT 1)) as row_num FROM dbo.fn_Split(@sInputList, @sDelimiter) 
         ) AS data_split 
         WHERE 
         data_split.row_num = @sRowNumber 

    IF   @value IS NULL 
     SET  @value = '' 

    RETURN  @value 
END 

qui appelle enfin cette fonction:

ALTER FUNCTION [dbo].[fn_Split] (
    @sInputList VARCHAR(MAX), 
    @sDelimiter VARCHAR(10) = ',' 
) RETURNS @List TABLE (item VARCHAR(MAX)) 
BEGIN 
    DECLARE @sItem VARCHAR(MAX) 
    WHILE CHARINDEX(@sDelimiter,@sInputList,0) <> 0 
     BEGIN 
      SELECT @sItem=RTRIM(LTRIM(SUBSTRING(@sInputList,1,CHARINDEX(@sDelimiter,@sInputList,0)-1))), @sInputList=RTRIM(LTRIM(SUBSTRING(@sInputList,CHARINDEX(@sDelimiter,@sInputList,0)+LEN(@sDelimiter),LEN(@sInputList)))) 
      IF LEN(@sItem) > 0 
       INSERT INTO @List SELECT @sItem 
     END 

    IF LEN(@sInputList) > 0 
     INSERT INTO @List SELECT @sInputList -- Put the last item in 
    RETURN 
END 
+0

Quel est le type de données de np.network_policy_id? –

+0

Vous avez affaire à des conversions numériques implicites ici. La fonction retourne un type de données 'varchar (12)' et vous effectuez des manipulations de chaîne dans votre udf, donc vous avez la probabilité d'obtenir des données non-numériques ET vous n'effectuez aucune validation pour vérifier si les données de retour sont numériques (en supposant c'est ce que tu veux). D'où l'erreur dans la jointure. – Kash

+0

np.network_policy_id est un int. Je comprends que j'obtiens une erreur parce que la fonction renvoie un varchar (12) qui ne peut pas être converti en entier. Plus précisément, la fonction retourne la valeur 'NPG'. Ce que je ne comprends pas c'est pourquoi il retourne au hasard différentes choses. –

Répondre

6

La raison pour laquelle il est "au hasard" retournaient Différentes choses ont à voir avec la façon dont SQL Server optimise les requêtes et où elles sont court-circuitées.

Une façon de résoudre le problème est le changement de la valeur de retour de fn_GetIDFromRuleName:

return (case when isnumeric(@value) then @value end) 

Ou, changer la condition de jointure:

on np.network_policy_id = (select case when isnumeric(dbo.fn_GetIDFromRuleName(cscsf.rule_name)) = 1) 
             then dbo.fn_GetIDFromRuleName(cscsf.rule_name) end) 

Le problème sous-jacent est ordre d'évaluation. La raison pour laquelle l'instruction "case" résout le problème est qu'il vérifie une valeur numérique avant la conversion et SQL Server garantit l'ordre d'évaluation dans une instruction case. A titre de note, vous pourriez toujours avoir des problèmes avec la conversion de nombres comme "6e07" ou "1.23" qui sont numériques, mais pas entiers.

Pourquoi ça marche parfois? Eh bien, il est clair que le plan d'exécution de la requête est en train de changer, de façon statique ou dynamique. Le cas qui échoue est probablement sur une ligne qui est exclue par la condition WHERE. Pourquoi essaie-t-il de faire la conversion? La question est où la conversion se produit. L'exécution de la conversion dépend du plan de requête. Cela peut, à son tour, dépendre du moment où la table cscf en question est lue. S'il est déjà dans membre, il peut être lu et tenté d'être converti en tant que première étape de la requête. Ensuite, vous obtiendrez l'erreur. Dans un autre scénario, l'autre table peut être filtererd et les lignes supprimées avant d'être converties.

En tout cas, mon conseil est:

  • N'A JAMAIS conversion implicite dans les requêtes.
  • Utilisez l'instruction case pour les conversions explicites.
  • Ne comptez pas sur les clauses WHERE pour filtrer les données afin que les conversions fonctionnent. Utilisez l'instruction de cas.