3
votes

Faire en sorte que SQL SERVER évalue les clauses dans un certain ordre

Prenez le tableau suivant comme exemple:

SELECT  [name]
FROM    dbo.tblNames AS tn
WHERE   ISNUMERIC([name]) = 1
        AND [name] = 2541

sqlfiddle

L'exécution de la requête suivante lève une exception:

SELECT  [name]
FROM    dbo.tblNames AS tn
WHERE   [name] IN ( SELECT  [name]
                    FROM    dbo.tblNames
                    WHERE   ISNUMERIC([name]) = 1 )
        AND [name] = 2541

Msg 245, niveau 16, état 1, ligne 1 La conversion a échoué lors de la conversion la valeur varchar 'Ken' au type de données int.

Alors que la requête suivante s'exécute sans erreur:

CREATE TABLE TBL_Names(Name VARCHAR(32))

INSERT INTO TBL_Names
VALUES (Ken),(1965),(Karen),(2541)

Je sais que cela est dû à la décision de l'Optimiseur de requêtes SQL Server. mais je me demande s'il existe un moyen de faire en sorte que le serveur SQL évalue les clauses dans un certain ordre. de cette façon, dans la première requête, la première clause filtre les Noms qui ne sont pas numériques afin que la deuxième clause n'échoue pas lors de la conversion en nombre.

Mise à jour: Comme vous l'avez peut-être remarqué, la requête ci-dessus n'est qu'une instance pour illustrer le problème. Je connais les risques de cette conversion implicite et j'apprécie ceux qui ont essayé de m'en avertir. Cependant, ma principale question est de savoir comment modifier le comportement d’Optimizer pour évaluer les clauses dans un certain ordre.


2 commentaires

Juste un punt. Mais avez-vous essayé une clause WITH pour la sélection imbriquée à la place pour voir si elle génère toujours une erreur?


Je recommande contre ISNUMERIC car il donne de faux positifs. Par exemple, ISNUMERIC ('.') renvoie true, cependant, CONVERT (int, '.') échouera. Je suggère TRY_CONVERT ou TRY_CAST .


5 Réponses :


1
votes

Essayez comme ceci

AND  [name] =  convert(varchar,2541 )

2)

  SELECT  [name]
    FROM    #TBL_Names AS tn
    WHERE   [name] IN ( SELECT  [name]
                        FROM    #TBL_Names
                        WHERE   ISNUMERIC([name]) = 1 )
            AND  [name] = '2541'

Puisque vous stockez le nom en tant que varchar (32), varchar acceptera des valeurs de type de données entières également appelée valeur de priorité


0 commentaires

0
votes

Vous pouvez essayer ceci

SELECT  [name]
FROM    dbo.TBL_Names AS tn
WHERE   [name] IN ( SELECT  [name]
                    FROM    dbo.TBL_Names
                    WHERE   ISNUMERIC([name]) = 1 )
        AND [name] = '2541'

Il vous suffit de [name] = 2541 à [name] = '2541' . Il vous manque ' (guillemet simple) avec le nom dans la condition where.

Vous pouvez trouver la démo en direct Ici .


0 commentaires

1
votes

Et:

SELECT *
FROM dbo.tblNames AS tn
WHERE [name] = convert(varchar, 2541)

Pourquoi avez-vous besoin de ISNUMERIC ([name]) = 1) puisque vous ne vous souciez que de la valeur '2541' ?


0 commentaires

3
votes

Il n'y a pas de moyen "direct" de dire au moteur d'exécuter les opérations dans l'ordre. SQL n'est pas un langage impératif où vous avez un contrôle complet sur comment faire les choses, vous dites simplement quoi dont vous avez besoin et le serveur décide comment le faire lui-même.

Dans ce cas particulier, tant que vous avez [name] = 2541 , vous risquez un échec de conversion potentiel car vous comparez un Colonne VARCHAR par rapport à un INT . Même si vous utilisez une sous-requête / CTE, il est encore possible pour l'optimiseur d'évaluer cette expression en premier et d'essayer de convertir toutes les valeurs varchar en int (échouant ainsi).

Vous pouvez éviter cela avec des solutions de contournement: p>

  • Comparaison correcte des types de données correspondants:

    DECLARE @Table TABLE (IntsAsVarchar VARCHAR(100))
    
    INSERT INTO @Table (IntsAsVarchar)
    VALUES
        ('1'), 
        ('2'),
        ('20'),
        ('25'),
        ('30'),
    
        ('A') -- Not an INT!
    
    SELECT
        CASE WHEN T.IntsAsVarchar < 15 THEN 15 ELSE 30 END,
        COUNT(*)
    FROM
        @Table AS T
    WHERE
        TRY_CAST(T.IntsAsVarchar AS INT) IS NOT NULL -- Will filter out non-INT values first
    GROUP BY
        CASE WHEN T.IntsAsVarchar < 15 THEN 15 ELSE 30 END
    
  • Casting de [name] en INT au préalable et uniquement dans la mesure du possible et sur une instruction différente , faites la comparaison. p>

    DECLARE @tblNamesInt TABLE (nameInt INT)
    
    INSERT INTO @tblNamesInt (
        nameInt)
    SELECT
        [nameInt] = CONVERT(INT, [name])
    FROM    
        dbo.tblNames
    WHERE   
        TRY_CAST([name] AS INT) IS NOT NULL -- TRY_CAST better than ISNUMERIC for INT
    
    
    SELECT
        *
    FROM
        @tblNamesInt AS T
    WHERE
        T.nameInt = 2351 -- data types match
    

Même un indice d'index ne forcera pas l'optimiseur à utiliser un index (c'est pourquoi il s'appelle un indice), nous avons donc peu de contrôle sur la façon dont il fait les choses.


Il y a quelques mécanismes que nous savons qui sont évalués dans l’ordre et que nous pouvons utiliser à notre avantage, comme les expressions HAVING seront toujours calculées après le regroupement des valeurs, et le regroupement toujours après les conditions WHERE . Nous pouvons donc "en toute sécurité" faire le regroupement suivant:

[name] = '2541'

Mais vous devez toujours éviter d'écrire du code qui implique des conversions implicites (comme T.IntsAsVarchar ).


0 commentaires

0
votes

Honnêtement, je n'appliquerais pas le cast implicite à votre colonne [name] , cela rendra la requête non SARGable. À la place, convertissez la valeur de votre entrée (ou transmettez-la sous forme de chaîne)

SELECT [name]
FROM dbo.TBL_Names tn
WHERE TRY_CONVERT(int,[name]) = 2541;

Si vous " devez ", cependant, encapsulez [nom] (et subir une dégradation des performances) puis utiliser TRY_CONVERT:

SELECT [name]
FROM dbo.TBL_Names tn
WHERE [name] = CONVERT(varchar(32),2541);


0 commentaires