web-dev-qa-db-fra.com

Requête pour trouver des lignes contenant ASCII caractères dans une plage donnée

J'utilise des scripts d'un autre sujet, mais la réponse acceptée ne fonctionne pas pour tous mes scénarios de données. J'aurais posé ma question sur l'original Comment vérifier les caractères non-ascii post, mais je n'ai pas encore assez de réputation pour commenter ou voter pour.

Questions:

Mes tests

J'ai créé SQL Fiddle avec des exemples de données, la procédure stockée à partir d'une des réponses et des requêtes pour illustrer le problème.

Requête 1: sample_table

-- Note: The "bad dash" row has char(150)

SELECT * FROM sample_table;

+-------------------+
|    DataColumn     |
+-------------------+
| test - good dash  |
| test – bad dash   |
+-------------------+

Requête 2: autre réponse par John montre la ligne "mauvais tiret" contenant char (150):

SELECT dbo.Find_Invalid_Chars(DataColumn) [Invalid Characters]
FROM sample_table
WHERE dbo.Find_Invalid_Chars(DataColumn) IS NOT NULL;

+----------------------+
|  Invalid Characters  |
+----------------------+
| test [150] bad dash  |
+----------------------+

Requête 3: Le réponse acceptée par Martin Smith ne renvoie aucun résultat :

SELECT DataColumn AS [Bad Data]
FROM sample_table
WHERE DataColumn LIKE '%[' + CHAR(127)+ '-' +CHAR(255)+']%' COLLATE Latin1_General_100_BIN2;

+------------+
| [Bad Data] |
+------------+

-- No rows returned.

Conclusion

Malheureusement, j'ai souvent besoin de trouver des caractères dans (ou en dehors) d'une plage dans des bases de données dans lesquelles je ne peux pas créer de procédures stockées. J'aimerais vraiment trouver un correctif pour le réponse acceptée ou un simple script qui ne nécessiterait la création d'aucun objet (y compris les tables temporaires).

Aucune suggestion? Merci d'avance.

EDIT 1: La solution ne peut pas modifier ou ajouter des objets ou des paramètres dans la base de données. Je recherche une requête autonome qui sélectionnera des lignes avec un ou plusieurs caractères dans une plage comprise entre deux nombres CHAR(), quels que soient les ASCII ou Extended ASCII numéro fourni.

EDIT 2: La DataColumn peut être dans VARCHAR ou NVARCHAR. Je n'ai aucun contrôle sur cela, donc j'espère trouver une requête autonome qui fonctionne pour les deux. Le but de la requête est de trouver dans la table/colonne source des caractères qui ne sont pas traités correctement par certaines applications logicielles. Les applications interprètent correctement la source, mais ont parfois des problèmes avec les caractères en dehors des plages "standard", bien que les plages varient selon l'application.

6
Fred

Pourquoi la réponse acceptée ne fonctionne-t-elle pas pour char (150)?

En fait, c'est le cas. Le problème est que votre test est mauvais/invalide. Vous testez la colonne, DataColumn, utilise NVARCHAR au lieu de VARCHAR. Le caractère lui-même fonctionne dans les deux types de données, mais le comportement est différent en raison de la façon dont il est utilisé dans chaque cas:

  • Dans la fonction Find_Invalid_Chars() (c'est-à-dire la réponse "autre"), la chaîne est reconvertie en VARCHAR car il s'agit du type de données du paramètre d'entrée pour cette fonction. Dans ce cas, cela fonctionne comme prévu (même si je pense que cela peut être fait beaucoup plus efficacement que cette boucle, mais c'est pour une autre fois ;-)
  • Dans la requête LIKE (c'est-à-dire la réponse "acceptée"), le résultat développé et concaténé de '%[' + CHAR(127)+ '-' +CHAR(255)+']%' est en fait converti en NVARCHAR puisque c'est le type de données de la colonne qu'il est comparé à (et NVARCHAR a une priorité de type de données plus élevée), donc que la fonction LIKE est pas se comportant comme attendu: soit le caractère CHAR(255) correspond à un point de code différent, et/ou le caractère CHAR(150) dans la colonne elle-même correspond à un point de code différent (le CHAR(127) le caractère ne change pas car il se trouve dans la plage standard ASCII). Dans les deux cas, la conversion en NVARCHAR entraîne la valeur numérique du caractère "En Dash" ("- ") pour ne plus être dans cette plage. Signification, la fonction LIKE recherche des valeurs, y, entre 127 et x (où x> = 128) et y pour le caractère "En Dash" est maintenant> x. Alors qu'en VARCHAR, x = 255 et y = 150.

La solution rapide pour voir que cela fonctionne est simplement de changer le type de données NVARCHAR de la colonne DataColumn en VARCHAR (oui, il suffit de supprimer le "N" initial), puis recréez le schéma, puis exécutez, et la requête LIKE se comportera comme prévu.

Les éléments suivants peuvent aider à expliquer pourquoi la création de la colonne de test NVARCHAR a fait que la requête LIKE ne correspond pas à la ligne:

SELECT UNICODE(CHAR(127)) AS [CHAR(127)],
       UNICODE(CHAR(150)) AS [CHAR(150)],
       UNICODE(CHAR(255)) AS [CHAR(255)];

/*
CHAR(127)     CHAR(150)     CHAR(255)
127           8211          255
*/

Comme vous pouvez le voir dans les résultats sous la requête, le "mauvais tiret", qui était CHAR(150) est devenu NCHAR(8211) lorsqu'il est stocké dans la colonne NVARCHAR. Et, puisque ce prédicat utilise un classement binaire (généralement la bonne chose à faire dans ce scénario), il regardait les points/valeurs de code, pas les caractères. Par conséquent, la clause LIKE recherchait des caractères avec des valeurs comprises entre 127 et 255, et 8211 n'est généralement pas dans cette plage ;-).

PS Veuillez garder à l'esprit que la fonction CHAR(150) peut retourner des caractères différents, ou même NULL, basé sur le classement par défaut de la base de données dans laquelle vous exécutez cette fonction. Cela est dû au fait que les données VARCHAR sont basées sur des pages de codes, et celles-ci sont déterminées par le classement, et le classement utilisé lors de l'exécution de la fonction CHAR() est le classement par défaut de la base de données active/actuelle . Cela affecte les valeurs 128 à 255. Les valeurs 0 à 127 renvoient toujours les mêmes caractères, quel que soit le classement, car il s'agit du jeu de caractères standard ASCII et sont les mêmes sur toutes les pages de codes prises en charge dans SQL Server (mais pas dans toutes les pages de codes en général).

PPS AUSSI, je viens de remarquer une légère différence de logique entre la fonction et la requête (c'est-à-dire les deux réponses de la question liée): CHAR(127) est considérée comme bonne/valide dans le Find_Invalid_Chars(), mais elle est considérée comme incorrecte/invalide dans la requête LIKE. Si c'était moi, je considérerais CHAR(127) valide car il fait partie du jeu de caractères standard ASCII. Mais, vous devez décider de ce que vous considérez. Soyez juste conscient de cette différence au cas où vous auriez besoin d'ajuster un peu la syntaxe LIKE.


Donné:

  1. Le but de la requête est de trouver dans la table/colonne source des caractères qui ne sont pas traités correctement par certaines applications logicielles.

    et:

  2. Les données peuvent être dans VARCHAR ou NVARCHAR.

Je dirais que:

  1. Vous ne voulez pas convertir NVARCHAR les données source en VARCHAR car il pourrait y avoir des mappages "les mieux adaptés" qui traduire des caractères source non valides en caractères valides, mais une ou plusieurs de vos applications logicielles peuvent ne pas utiliser les mappages "les mieux adaptés".

    SELECT NCHAR(178) AS [Unicode], -- Superscript 2 (U+00B2)
           CONVERT(VARCHAR(5), NCHAR(178)
                       COLLATE SQL_Latin1_General_CP1_CI_AS) AS [CodePage-1252],
           CONVERT(VARCHAR(5), NCHAR(178)
                       COLLATE Turkmen_100_CI_AS) AS [CodePage-1250]
    
    /*
    Unicode    CodePage-1252    CodePage-1250
    ²          ²                2
    */
    
  2. Il sera probablement plus fiable de rechercher des caractères et non dans une plage spécifique "valide" par opposition à ceux dans une plage non valide spécifique, en particulier lorsqu'il s'agit de NVARCHAR qui contient beaucoup plus de 256 caractères.
  3. Vous pourriez vous en tirer avec une seule requête si la plage "valide" est toujours entre les valeurs 0 et 127 (puisque ces valeurs sont les mêmes dans les deux cas) . Mais si vous devez spécifier des valeurs supérieures à 127, vous aurez besoin d'une requête pour VARCHAR et une pour NVARCHAR.

Cela étant dit:

  • La requête suivante renvoie des lignes contenant au moins un caractère qui n'est pas compris entre 0 et 127, pour VARCHAR et NVARCHAR. Mais, cela ne fonctionne qu'avec les colonnes NVARCHAR pour les valeurs supérieures à 127.

    SELECT *
    FROM   (VALUES (NCHAR(178)), (NCHAR(8211)), (N''), (NULL), (N'xy' + NCHAR(165)),
               (N'AA'), (N'mM' + NCHAR(999) + N'Nn'), (N'#!~()')) tmp(TestValue)
    WHERE  tmp.[TestValue] LIKE N'%[^' + NCHAR(0) + N'-' + NCHAR(127)
              + N']%' COLLATE Latin1_General_100_BIN2;
    
    /*
    TestValue
    ²
    –
    xy¥
    mMϧNn
    */
    
  • La requête suivante retourne également des lignes contenant au moins un caractère qui n'est pas compris entre 0 et 127, mais ne fonctionne que pour VARCHAR Colonnes. Cependant, il permet d'utiliser des valeurs comprises entre 128 et 255.

    SELECT *
    FROM   (VALUES (CHAR(178)), (CHAR(150)), (''), (NULL), ('AA'), ('#!~()'),
            ('xy' + CONVERT(VARCHAR(5), NCHAR(165) COLLATE Latin1_General_100_BIN2)),
            ('mM' + CONVERT(VARCHAR(5), NCHAR(199) COLLATE Latin1_General_100_BIN2) + 'Nn')
           ) tmp(TestValue)
    WHERE  tmp.[TestValue] LIKE '%[^' + CHAR(0) + '-' + CHAR(127)
              + ']%' COLLATE Latin1_General_100_BIN2;
    
    /*
    TestValue
    ²
    –
    xy¥
    mMÇNn
    */
    

En ce qui concerne:

Les applications interprètent correctement la source, mais ont parfois des problèmes avec les caractères en dehors des plages "standard", bien que les plages varient selon l'application.

  1. Je ne suis pas sûr de comprendre comment il peut y avoir des "problèmes" avec certains personnages si l'application interprète correctement les données source, à moins que vous vouliez dire qu'ils interprètent "principalement" les données correctement.
  2. Les plages variant selon le son de l'application comme celle-ci peuvent nécessiter une enquête plus détaillée que ce qui peut être fait dans un simple format de questions/réponses comme celui-ci. Ce comportement peut être dû au fait qu'ils utilisent différents pilotes pour se connecter (ODBC/OLEDB/etc), dans quelle langue ils sont écrits, quelles hypothèses ils font sur les données qu'ils obtiennent, etc. Certains problèmes peuvent être corrigés avec une configuration (pas de changement de code) de l'application, certains peuvent être corrigés uniquement avec un changement de code, etc.
4
Solomon Rutzky