web-dev-qa-db-fra.com

Erreur de lancement CAST lors de l'exécution dans une procédure stockée, mais pas lors de l'exécution en tant que requête brute

Quelque chose de très étrange se passe ici.

J'ai une requête qui ressemble à ceci.

SELECT CAST(FT.DOP AS SMALLINT) FROM TRACKING_DATA WHERE date > @mydate and identifier = 0000000000

Lorsqu'il est exécuté en tant que requête brute, il renvoie très bien les données.

Lorsque je le place dans une procédure stockée qui modifie la clause where, il renvoie cette erreur.

Msg 244, Level 16, State 2, Procedure myprocedure, Line 107 [Batch Start Line 2]
The conversion of the varchar value '58629' overflowed an INT2 column. Use a larger integer column.

Voici donc la bizarrerie. Je passe en revue toutes les données possibles pour cette clause where avec une requête comme celle-ci.

SELECT DISTINCT DOP FROM TRACKING_DATA where identifier = 000000000000

et

SELECT DISTINCT CAST(DOP AS smallint) FROM TRACKING_DATA where identifier = 000000000000

Et c'est ce que je récupère.

17
12
9
19
8
14
6
16
11
13
7
10
0
18
5
15
4

Nulle part il n'a quelque chose de trop gros pour un SMALLINT. Alors j'ai pensé, ok c'est peut-être un caractère non imprimable ASCII. Mais je n'en trouve pas.

Je suis un peu perplexe en ce moment. Il exécute la recherche en tant que requête brute, explose en tant que procédure et toutes les données possibles en fonction de l'endroit où est valide. Mon seul soupçon est que le plan de requête fait quelque chose d'étrange avec le filtrage ou s'exécute peut-être avec une validation différente lorsqu'il est exécuté en tant que procédure.

8
Chris Rice

Il vaut mieux ne pas dépendre de l'indexation pour éviter ces erreurs, et plutôt écrire la requête pour se prémunir contre cette situation. Puisque vous êtes sur SQL Server 2012, une option serait d'utiliser TRY_CAST:

SELECT TRY_CAST(FT.DOP AS SMALLINT) 
FROM TRACKING_DATA 
WHERE date > @mydate and identifier = 0000000000;

Cela entraînera la sélection de NULL pour les valeurs qui ne parviennent pas à convertir de varchar en smallint. Mais tant que vous savez qu'il n'y a pas de résultats comme celui-ci, ou que votre application peut gérer les résultats NULL, vous devriez être bon.

13
Josh Darnell

C'était donc le putain de plan de requête qu'il décidait d'utiliser.

La valeur sur laquelle elle explosait était présente dans le tableau (ce qu'elle ne devrait pas être mais c'est un autre problème) mais elle était associée à un identifiant complètement différent.

La requête en question exécutait un clustered seek sur un index qui couvrait le date mais pas le identifier cela l'a amené à scanner la table ENTIÈRE qui est tellement, donc, si mal pour un certain nombre de raisons.

Ajout d'un index approprié pour la clause where et la procédure fredonne joyeusement maintenant car elle filtre sur l'identifiant avant d'aller après la date. J'aimerais pouvoir obtenir des statistiques avant/après, mais je suis sûr que son fonctionnement est plus rapide maintenant aussi.

12
Chris Rice

Vous obtenez évidemment différents plans de requête et votre distribution est essayée par rapport à des valeurs qui sont filtrées par la clause where, je ne prendrais pas la peine de comprendre pourquoi - quelle qu'en soit la cause, cela pourrait évidemment se produire pour une raison différente plus tard. S'appuyer sur le filtrage n'est pas fiable.

La chose à faire est d'écrire une requête qui n'échouera pas, quels que soient l'index, l'indice ou les statistiques.

Je pense qu'il y a 3 façons de procéder:

  1. Utilisez une table/variable de table temporaire.

    declare @result table (dop varchar);
    Insert into @result
    SELECT FT.DOP  FROM TRACKING_DATA 
    WHERE date > @mydate and identifier = 0000000000
    
    SELECT cast(dop as SMALLINT) dop from @result
    
  2. Utilisez une déclaration de cas:

    SELECT CAST(case when FT.DOP like '%[^0-9]%' then null else ft.dop end AS SMALLINT) 
    FROM TRACKING_DATA 
    WHERE date > @mydate and identifier = 0000000000
    
  3. Utilisez plutôt try_cast et cast:

    SELECT try_CAST(FT.DOP AS SMALLINT) 
    FROM TRACKING_DATA 
    WHERE date > @mydate and identifier = 0000000000
    

Personnellement, je recommanderais 3 si possible.

1
jmoreno