2009-09-09 19 views
72

Me encontré con algo raro esta mañana y pensé en enviarlo para comentario.SQL Server 2008 Empty String vs. Space

Puede alguien explicar por qué la siguiente consulta SQL impresiones 'igual' cuando se ejecuta en SQL 2008. El nivel de compatibilidad db se establece en 100.

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

Y esto devuelve 0:

select (LEN(' ')) 

Parece que está recortando automáticamente el espacio. No tengo idea de si este fue el caso en las versiones anteriores de SQL Server, y ya no tengo ninguno para probarlo.

Me encontré con esto porque una consulta de producción devolvía resultados incorrectos. No puedo encontrar este comportamiento documentado en ninguna parte.

¿Alguien tiene alguna información sobre esto?

+2

SQL 2005: select len ​​('') devuelve 0 – Mayo

+1

Hace lo mismo en Sql Server 2000. –

+1

Esta es una pregunta fascinante. Parece que vuelve igual sin importar cuántos espacios pongas en cada cadena, ya sea que coincidan o no. Después de más experimentación, noté que efectivamente está haciendo un RTRIM en ambos lados del operador de igualdad antes de la comparación. Parece que obtuvo una respuesta sobre la función LEN, pero estoy realmente interesado en una respuesta más completa que "varcharres e igualdad son espinosos en TSQ" para la parte de igualdad de su pregunta. – JohnFx

Respuesta

74

varchar sy la igualdad son espinosas en TSQL. La función LEN dice:

Devuelve el número de caracteres, en lugar del número de bytes, de la expresión cadena dada, excluyendo los blancos de cola.

Es necesario utilizar DATALENGTH para conseguir una verdadera byte recuento de los datos en cuestión. Si tiene datos Unicode, tenga en cuenta que el valor que obtenga en esta situación no será el mismo que la longitud del texto.

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

Cuando se trata de la igualdad de las expresiones, las dos cadenas se comparan para la igualdad de la siguiente manera:

  • Obtener cadena más corta
  • Pad con espacios en blanco hasta que la longitud es igual a la de cadena más larga
  • Comparar los dos

Es el medio El paso que está causando resultados inesperados: después de ese paso, efectivamente está comparando el espacio en blanco con el espacio en blanco, por lo tanto, se considera que son iguales.

LIKE se comporta mejor que = en la situación de "espacios en blanco", ya que no lleva a cabo en blanco-relleno en el patrón que estaba tratando de igualar:

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

daré eq mientras:

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

dará ne

cuidado con LIKE aunque: no es simétrico: trata los espacios en blanco al final como significativos en el patrón (RHS) pero no la expresión de coincidencia (LHS). Lo siguiente es tomado de here:

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 
+1

Buena respuesta. No lo había notado en la documentación de LEN. Sin embargo, no está limitado a LEN. La función DERECHA e IZQUIERDA muestra un comportamiento similar, pero no está documentado. Parece ser el literal con un espacio que causa el problema. Me di cuenta de esto también se vuelve igual: si '' = SPACE (1) \t de impresión 'igual' demás \t de impresión 'es igual a' No estoy realmente interesado en conseguir la longitud real, yo estaba confundido por qué cuando buscaba un espacio en una columna, se devolvían todas las columnas que eran cadenas vacías. – jhale

+0

Además, buena información sobre la declaración LIKE.Supongo que la moraleja de la historia es intentar no ponerte en la posición en la que necesitas comparar un espacio y una cadena vacía. – jhale

+2

El problema es más grande que comparar un espacio con una cadena vacía. La comparación de dos cadenas que terminan en un número diferente de espacios exhibe el mismo comportamiento. – JohnFx

4

No era una pregunta similar hace un tiempo en el que daba a un problema similar here

En lugar de LEN (' '), utilice DATALENGTH ('') - que le da el valor correcto.

Las soluciones fueron para usar una cláusula LIKE como se explicó en mi respuesta allí, y/o incluir una segunda condición en la cláusula WHERE para verificar DATALENGTH también.

Lea la pregunta y los enlaces allí.

9

yo encontramos este blog article que describe el comportamiento y explica por qué.

El estándar SQL requiere que la cadena comparaciones, efectivamente, la almohadilla cadena más corta con caracteres de espacio. Esto conduce al sorprendente resultado que N '' = N'' (la cadena vacía es igual a una cadena de uno o más espacio caracteres) y más generalmente cualquier cadena es igual a otra cadena si difieren sólo por espacios finales . Este puede ser un problema en algunos contextos.

Más información también está disponible en MSKB316626

+0

Gracias. Me sorprende que esté en el estándar. Estoy seguro de que alguien mucho más inteligente que yo tenía una buena razón para esto. – jhale

+0

@John: ¿quisiste escribir ≠ (no igual) en tu comentario? –

+0

La cita original tenía un error que copié directamente. Actualicé la cita para reflejar lo que significaba el autor original. – JohnFx

14

El operador = es T-SQL no es tanto "iguales" ya que es "son la misma palabra/frase, de acuerdo con la intercalación de la expresión de contexto, "y LEN es" el número de caracteres en la palabra/frase ". Ninguna intercalación trata los espacios en blanco finales como parte de la palabra/frase que los precede (aunque sí tratan los espacios en blanco iniciales como parte de la cadena que preceden).

Si necesita distinguir 'esto' de 'esto', no debe usar el operador "son la misma palabra o frase" porque 'this' y 'this' son la misma palabra.

La idea de que el operador de igualdad de cadenas dependa del contenido de sus argumentos y del contexto de intercalación de la expresión, pero no debe depender de los tipos de argumentos, si el son ambos tipos de cadenas.

El concepto del lenguaje natural de "estas son la misma palabra" no suele ser lo suficientemente preciso como para poder ser capturado por un operador matemático como =, y no hay ningún concepto de tipo de cadena en el lenguaje natural. El contexto (es decir, la intercalación) importa (y existe en el lenguaje natural) y es parte de la historia, y las propiedades adicionales (algunas que parecen peculiares) son parte de la definición de = para hacerlo bien definido en el mundo antinatural de datos.

En cuanto al problema de tipo, no le gustaría cambiar las palabras cuando están almacenadas en diferentes tipos de cadena. Por ejemplo, los tipos VARCHAR (10), CHAR (10) y CHAR (3) pueden contener representaciones de la palabra 'cat', y? = 'cat' debería permitirnos decidir si un valor de cualquiera de estos tipos contiene la palabra 'cat' (con problemas de caso y acento determinados por la intercalación).

respuesta al comentario de JohnFx:

Ver Using char and varchar Data en los libros en pantalla. Citando de esa página, énfasis mío:

Cada valor de datos char y varchar tiene una intercalación. Colaciones definen atributos tales como los patrones de bits utilizados para representar cada carácter, reglas de comparación y sensibilidad a la caja o acentuación.

Acepto que podría ser más fácil de encontrar, pero está documentado.

También vale la pena señalar que la semántica de SQL, donde = tiene que ver con los datos del mundo real y el contexto de la comparación (en lugar de bits almacenados en la computadora) ha sido parte de SQL para un largo tiempo. La premisa de RDBMS y SQL es la representación fiel de datos del mundo real, de ahí su apoyo para colaciones muchos años antes de que ideas similares (como CultureInfo) entraran en el reino de los lenguajes tipo Algol. La premisa de esos idiomas (al menos hasta hace muy poco) era la resolución de problemas en ingeniería, no la gestión de datos comerciales. (Recientemente, el uso de lenguajes similares en aplicaciones que no son de ingeniería como la búsqueda está haciendo algunas incursiones, pero Java, C#, etc. todavía están luchando con sus raíces no comerciales.)

En mi opinión, no es justo criticar SQL por ser diferente de "la mayoría de los lenguajes de programación". SQL se diseñó para admitir un marco para el modelado de datos empresariales que es muy diferente de la ingeniería, por lo que el lenguaje es diferente (y mejor para su objetivo).

Diablos, cuando SQL se especificó por primera vez, algunos idiomas no tenían ningún tipo de cadena incorporada. Y aún en algunos idiomas, el operador de igual entre cadenas no compara los datos de los caracteres, ¡pero compara las referencias! No me sorprendería si en una década o dos, la idea de que == depende de la cultura se convierte en la norma.

+0

BOL describe el operador = de este modo: "Compara la igualdad de dos expresiones (un operador de comparación)". Independientemente de si el comportamiento es correcto o no, debe admitir que es extremadamente confuso y no estándar en términos del uso de este operador en la mayoría de los lenguajes de programación. MS debería al menos agregar una advertencia a la documentación sobre este comportamiento. – JohnFx

+0

@JohnFx: Vea mi respuesta de demasiado tiempo para un comentario en mi respuesta. –

3

Para comparar un valor de un espacio literal, también se puede utilizar esta técnica como una alternativa a la instrucción LIKE:

IF ASCII('') = 32 PRINT 'equal' ELSE PRINT 'not equal' 
0

A veces uno tiene que tratar con espacios en los datos, con o sin otros caracteres , aunque la idea de utilizar Null es mejor, pero no siempre utilizable. Lo hice correr en la situación descrita y resolverse de esta manera:

... donde ('>' + + @Space '< ') <> ('>' + @ + space2 '<')

Por supuesto que no harías esa gran cantidad de datos, pero funciona rápido y fácil para cientos de líneas ...

Herbert

+1

La pregunta era: ¿por qué? SQL Server se comportó como lo hizo, no cómo manejar ese comportamiento en general. jhale probablemente prefiera no modificar su código de programa, solo su configuración de servidor. –

0

Cómo distinta registros en los campos de selección con char/varchar en SQL Server: ejemplo:

declare @mayvar as varchar(10) 

set @mayvar = 'data ' 

select mykey, myfield from mytable where myfield = @mayvar 

espera

mykey (int) | myfield (varchar10)

1 | 'Datos'

obtenido

mykey | myfield

1 | 'datos' 2 | 'Datos'

incluso si escribo select mykey, myfield from mytable where myfield = 'data' (sin final en blanco) puedo obtener los mismos resultados.

cómo lo he resuelto? En este modo:

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

y si hay un índice en myfield, se usará en cada caso.

Espero que sea útil.