web-dev-qa-db-fra.com

Chaîne vide SQL Server 2008 et espace

J'ai rencontré quelque chose d'un peu étrange ce matin et j'ai pensé le soumettre pour commentaire.

Quelqu'un peut-il expliquer pourquoi la requête SQL suivante est imprimée "égale" lors de l'exécution avec SQL 2008. Le niveau de compatibilité de la base de données est défini sur 100.

if '' = ' '
    print 'equal'
else
    print 'not equal'

Et cela retourne 0:

select (LEN(' '))

Il semble que ce soit l’espacement automatique. Je ne sais pas du tout si c'était le cas dans les versions précédentes de SQL Server et je n'ai plus personne pour le tester.

J'ai rencontré ce problème car une requête de production renvoyait des résultats incorrects. Je ne trouve pas ce comportement documenté nulle part.

Quelqu'un at-il des informations à ce sujet?

77
jhale

varchars et l'égalité sont épineux dans TSQL. La fonction LEN dit:

Retourne le nombre de caractères, plutôt que le nombre d'octets, de l'expression de chaîne donnée, en excluant les espaces de fin.

Vous devez utiliser DATALENGTH pour obtenir un nombre exact byte des données en question. Si vous avez des données unicode, notez que la valeur que vous obtenez dans cette situation ne sera pas la même que la longueur du texte.

print(DATALENGTH(' ')) --1
print(LEN(' '))        --0

En ce qui concerne l’égalité des expressions, les deux chaînes sont comparées comme suit:

  • Obtenez une chaîne plus courte
  • Pad avec des espaces jusqu'à ce que la longueur soit égale à celle d'une chaîne plus longue
  • Comparer les deux

C'est l'étape intermédiaire qui produit des résultats inattendus. Après cette étape, vous comparez effectivement les espaces blancs aux espaces blancs. Ils sont donc considérés comme égaux.

LIKE se comporte mieux que = dans la situation des "blancs" car il ne remplit pas les blancs sur le modèle que vous tentiez de reproduire:

if '' = ' '
print 'eq'
else
print 'ne'

Donnera eq en:

if '' LIKE ' '
print 'eq'
else
print 'ne'

Donnera ne

Attention, avec LIKE cependant: ce n’est pas symétrique: il considère les espaces en fin de texte comme significatifs dans le modèle (RHS), mais pas dans l’expression de correspondance (LHS). Ce qui suit est tiré de ici :

declare @Space nvarchar(10)
declare @Space2 nvarchar(10)

set @Space = ''
set @Space2 = ' '

if @Space like @Space2
print '@Space Like @Space2'
else
print '@Space Not Like @Space2'

if @Space2 like @Space
print '@Space2 Like @Space'
else
print '@Space2 Not Like @Space'

@Space Not Like @Space2
@Space2 Like @Space
79
butterchicken

L'opérateur = est T-SQL n'est pas tellement "égal" qu'il est "sont le même mot/expression, selon l'assemblage du contexte de l'expression", et LEN est "le nombre de caractères du mot/expression". Aucune collation ne traite les espaces en fin de chaîne comme faisant partie du mot/de la phrase les précédant (bien qu'ils traitent les espaces en tête comme des éléments de la chaîne qu'ils précèdent).

Si vous devez distinguer "ceci" de "ceci", vous ne devez pas utiliser l'opérateur "sont le même mot ou la même phrase" car "ceci" et "ceci" sont le même mot.

L'idée selon laquelle l'opérateur d'égalité des chaînes devrait dépendre du contenu de ses arguments et du contexte de classement de l'expression contribue également à way = works, mais ne dépend pas des types des arguments, s'ils sont tous deux de type chaîne. .

Le concept de langage naturel de "ceux-ci sont le même mot" n'est généralement pas suffisamment précis pour pouvoir être capturé par un opérateur mathématique tel que =, et il n'existe aucun concept de type de chaîne en langage naturel. Le contexte (c'est-à-dire la collation) importe (et existe en langage naturel) et fait partie de l'histoire, et des propriétés supplémentaires (certaines qui semblent bizarres) font partie de la définition de = afin de le définir correctement dans le monde non naturel de Les données.

En ce qui concerne le type, vous ne voudriez pas que les mots changent quand ils sont stockés dans différents types de chaîne. Par exemple, les types VARCHAR (10), CHAR (10) et CHAR (3) peuvent tous contenir des représentations du mot 'cat', et? = 'cat' devrait nous permettre de décider si une valeur de l’un quelconque de ces types contient le mot 'cat' (avec des problèmes de casse et d’accent déterminés par la collation).

Réponse au commentaire de JohnFx:

Voir Utilisation de char et varchar Data dans la documentation en ligne. Citant cette page, soulignons le mien:

Chaque valeur de données char et varchar a un classement. Les collations définissent des attributs tels que les modèles de bits utilisés pour représenter chaque caractère, règles de comparaison, et sensibilité au casse ou à l’accentuation.

Je conviens que cela pourrait être plus facile à trouver, mais c'est documenté.

Il convient également de noter que la sémantique de SQL, où = concerne les données réelles et que le contexte de la comparaison (par opposition à quelque chose concernant les bits stockés sur l'ordinateur) fait partie de SQL depuis longtemps. Le principe des SGBDR et SQL est la représentation fidèle de données du monde réel, d'où son support pour les collations de nombreuses années avant que des idées similaires (telles que CultureInfo) ne soient entrées dans le domaine des langages de type ALGOL. Le principe de ces langages (du moins jusqu'à tout récemment) était la résolution de problèmes en ingénierie, pas la gestion de données commerciales. (Récemment, l'utilisation de langages similaires dans des applications autres que l'ingénierie, telles que la recherche, est en train de faire des incursions, mais Java, C #, etc. luttent toujours avec leurs racines non professionnelles.)

À mon avis, il est injuste de reprocher à SQL d'être différent de "la plupart des langages de programmation". SQL a été conçu pour prendre en charge un cadre de modélisation de données d’entreprise très différent de l’ingénierie. Le langage est donc différent (et meilleur pour son objectif).

Heck, quand SQL a été spécifié pour la première fois, certaines langues n'avaient pas de type de chaîne intégré. Et dans certaines langues encore, l'opérateur égal entre les chaînes ne compare pas du tout les données de caractères, mais compare les références! Cela ne me surprendrait pas si dans une ou deux décennies, l’idée que == dépende de la culture devienne la norme.

17
Steve Kass

J'ai trouvé cet article de blog qui décrit le comportement et explique pourquoi.

Le standard SQL requiert cette chaîne les comparaisons, effectivement, pad le chaîne plus courte avec des espaces. Cela conduit au résultat surprenant que N '' = N '' (la chaîne vide est égale à une chaîne d'un ou de plusieurs espaces caractères) et plus généralement à n'importe quel chaîne est égale à une autre chaîne si elles ne diffèrent que par les espaces de fin. Ce peut être un problème dans certains contextes.

Plus d'informations également disponibles dans MSKB316626

9
JohnFx

Il y a quelque temps, il y avait une question similaire dans laquelle j'ai examiné un problème similaire ici

Au lieu de LEN (''), utilisez DATALENGTH ('') - cela vous donnera la valeur correcte.

Les solutions consistaient à utiliser une clause LIKE comme expliqué dans ma réponse et/ou à inclure une deuxième condition dans la clause WHERE pour vérifier également DATALENGTH.

Ayez une lecture de cette question et des liens ici.

4
AdaTheDev

Pour comparer une valeur à un espace littéral, vous pouvez également utiliser cette technique comme alternative à l'instruction LIKE:

IF ASCII('') = 32 PRINT 'equal' ELSE PRINT 'not equal'
3
David G

Une autre solution consiste à rétablir l'état dans lequel l'espace a une valeur. Exemple: remplacer l'espace par un caractère connu comme le _

if REPLACE('hello',' ','_') = REPLACE('hello ',' ','_')
    print 'equal'
else
    print 'not equal'

renvoie: pas égal

Pas idéal, et probablement lent, mais c'est un autre moyen rapide d'avancer lorsque cela est nécessaire rapidement.

0
CooPzZ

Comment distinguer des enregistrements sur select avec des champs char/varchar sur un serveur SQL: Exemple:

declare @mayvar as varchar(10)

set @mayvar = 'data '

select mykey, myfield from mytable where myfield = @mayvar

prévu

mykey (int) | myfield (varchar10)

1 | 'Les données '

obtenu

mykey | mon champ

1 | 'data'2 | 'Les données '

même si j'écris select mykey, myfield from mytable where myfield = 'data' (sans espace, en blanc) .__, j'obtiens les mêmes résultats.

comment j'ai résolu? Dans ce mode:

select mykey, myfield
from mytable
where myfield = @mayvar 
and DATALENGTH(isnull(myfield,'')) = DATALENGTH(@mayvar)

et s'il existe un index sur myfield, il sera utilisé dans chaque cas.

J'espère que cela vous sera utile.

0
Orix

Parfois, il faut gérer des espaces dans les données, avec ou sans autres caractères, même si l'idée d'utiliser Null est préférable - mais pas toujours utilisable ... J'ai rencontré la situation décrite et l'ai résolue de cette façon:

... où ('>' + @space + '<') <> ('>' + @ space2 + '<')

Bien sûr, vous ne feriez pas cette grande quantité de données, mais cela fonctionne rapidement et facilement pour quelques centaines de lignes ...

Herbert

0
Herbert