Cadena vacía de SQL Server 2008 frente a espacio

82

Me encontré con algo un poco extraño esta mañana y pensé en enviarlo para comentarios.

¿Alguien puede explicar por qué la siguiente consulta SQL imprime "igual" cuando se ejecuta contra SQL 2008. El nivel de compatibilidad de db se establece en 100.

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

Y esto devuelve 0:

select (LEN(' '))

Parece estar recortando automáticamente el espacio. No tengo idea de si este era el caso en 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?

jhale
fuente
2
SQL 2005: select len ​​('') devuelve 0
Mayo
1
Hace lo mismo en Sql Server 2000.
Pierre-Alain Vigeant
1
Ésta es una pregunta fascinante. Parece volver igual sin importar cuántos espacios ponga en cualquiera de las cadenas, 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 en la función LEN, pero estoy realmente interesado en una respuesta más completa que "los varchars y la igualdad son espinosos en TSQ" para la parte de igualdad de su pregunta.
JohnFx
Oracle también hace esto, creo.
Quillbreaker
En general, encuentro que almacenar cadenas vacías es una mala idea y esta es una de las razones. Prefiero el uso de Null y encuentro muchos problemas cuando las personas intentan convertir información nula en un valor como una cadena vacía o una salida de datos fuera del rango normal.
HLGEM

Respuestas:

87

varcharsy la igualdad son espinosos en TSQL. La LENfunción dice:

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

Debe utilizar DATALENGTHpara obtener un byterecuento real 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 igualdad de expresiones, las dos cadenas se comparan por igualdad de esta manera:

  • Obtener una cadena más corta
  • Rellene con espacios en blanco hasta que la longitud sea igual a la de una cuerda más larga
  • Compara los dos

Es el paso intermedio el que está causando resultados inesperados; después de ese paso, está comparando de manera efectiva los espacios en blanco con los espacios en blanco, por lo que se considera que son iguales.

LIKEse comporta mejor que =en la situación de "espacios en blanco" porque no realiza relleno de espacios en blanco en el patrón que estaba tratando de hacer coincidir:

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

Daré eqmientras:

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

Daré ne

Sin LIKEembargo, tenga cuidado con : no es simétrico: trata los espacios en blanco finales como significativos en el patrón (RHS) pero no en la expresión de coincidencia (LHS). Lo siguiente se toma de aquí :

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
pollo de mantequilla
fuente
1
Buena respuesta. No lo había notado en la documentación LEN. Sin embargo, no se limita a LEN. La función DERECHA e IZQUIERDA exhibe un comportamiento similar, pero no está documentado. Parece ser el literal con un espacio que causa el problema. Noté que esto también devuelve igual: if '' = SPACE (1) print 'equal' else print 'not equal' No estoy realmente interesado en obtener la longitud verdadera, solo estaba confundido por qué cuando estaba buscando un espacio en una columna, se devolvieron todas las columnas que eran cadenas vacías.
jhale
Además, buena información sobre la declaración LIKE. Supongo que la moraleja de la historia es tratar de no ponerse en la posición en la que necesite 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 muestra el mismo comportamiento.
JohnFx
3
@butterchicken: Lo siento por un puesto tan tardía, acabo de ver a esta pregunta, pero cuando me encontré con esto (la última) en mi sql-server-2008 r2recibo, @Space Not Like @Space2 @Space2 Not Like @Space . ¿Alguna idea de por qué?
Razort4x
1
Confirmado en SQL Server 2012 y SQL Server 2014, el resultado es@Space Not Like @Space2 @Space2 Not Like @Space
solo un alumno
19

El operador = es T-SQL no es tanto "igual" como "son la misma palabra / frase, de acuerdo con la intercalación del contexto de la expresión", 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 "esto" y "esto" son la misma palabra.

La idea de way = works es la idea de que el operador de igualdad de cadenas debe depender 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 ambos son tipos de cadena .

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

En el tema del tipo, no querrá que las palabras cambien cuando se almacenan en diferentes tipos de cadenas. Por ejemplo, los tipos VARCHAR (10), CHAR (10) y CHAR (3) pueden contener representaciones de la palabra 'gato' y? = 'gato' debería permitirnos decidir si un valor de cualquiera de estos tipos contiene la palabra 'gato' (con cuestiones de caso y acento determinados por la colación).

Respuesta al comentario de JohnFx:

Consulte Uso de datos char y varchar en Libros en pantalla. Citando de esa página, el énfasis es mío:

Cada valor de datos char y varchar tiene una intercalación. Las intercalaciones definen atributos como los patrones de bits utilizados para representar cada carácter, las reglas de comparación y la sensibilidad a mayúsculas y minúsculas o acentos.

Estoy de acuerdo en 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 (a diferencia de algo sobre los bits almacenados en la computadora) ha sido parte de SQL durante mucho tiempo. La premisa de RDBMS y SQL es la representación fiel de datos del mundo real, de ahí su soporte para las intercalaciones muchos años antes de que ideas similares (como CultureInfo) ingresaran al ámbito de los lenguajes tipo Algol. La premisa de esos lenguajes (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 algunos avances, pero Java, C #, etc. aún están luchando con sus raíces no comerciales).

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

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

Steve Kass
fuente
BOL describe el operador = así: "Compara la igualdad de dos expresiones (un operador de comparación)". Ya sea que el comportamiento sea 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
@JohnFx: Vea mi respuesta demasiado larga para un comentario en mi respuesta.
Steve Kass
9

Encontré este artículo de blog que describe el comportamiento y explica por qué.

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

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

JohnFx
fuente
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
@John: ¿quisiste escribir ≠ (no es igual) en tu comentario?
Steve Kass
La cita original tenía un error que copié directamente. Actualicé la cita para reflejar lo que quiso decir el autor original.
JohnFx
5

Hace un tiempo hubo una pregunta similar en la que analicé un problema similar aquí

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

Las soluciones fueron usar una LIKEcláusula como se explica en mi respuesta allí, y / o incluir una segunda condición en la WHEREcláusula para verificar DATALENGTHtambién.

Lea esa pregunta y los enlaces allí.

AdaTheDev
fuente
3

Para comparar un valor con un espacio literal, también puede utilizar esta técnica como alternativa a la declaración LIKE:

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

Cómo diferenciar registros en select con campos char / varchar en sql server: ejemplo:

declare @mayvar as varchar(10)

set @mayvar = 'data '

select mykey, myfield from mytable where myfield = @mayvar

esperado

mykey (int) | myfield (varchar10)

1 | 'datos'

adquirido

mykey | Mi campo

1 | 'datos' 2 | 'datos'

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

como lo resolví 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 util.

Orix
fuente
0

Otra forma es volver a ponerlo en un estado en el que el espacio tiene valor. por ejemplo: reemplace el espacio con un carácter conocido como _

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

devuelve: no es igual

No es ideal, y probablemente lento, pero es otra forma rápida de avanzar cuando se necesita rápidamente.

CooPzZ
fuente
0

A veces uno tiene que lidiar con espacios en los datos, con o sin otros caracteres, aunque la idea de usar Null es mejor, pero no siempre se puede usar. Me encontré con la situación descrita y la resolví de esta manera:

... where ('>' + @space + '<') <> ('>' + @space2 + '<')

Por supuesto, no haría eso para una gran cantidad de datos, pero funciona de forma rápida y sencilla para unas cien líneas ...

Herbert
fuente
1
La pregunta era por qué el servidor SQL se comportaba como lo hizo, no cómo manejar ese comportamiento en general. jhale probablemente preferiría no modificar el código de su programa, solo la configuración de su servidor.
Lutz Prechelt