¿Por qué la búsqueda de LIKE N '% %' coincide con cualquier carácter Unicode y = N' 'coincide con muchos?


21
DECLARE @T TABLE(
  Col NCHAR(1));

INSERT INTO @T
VALUES      (N'A'),
            (N'B'),
            (N'C'),
            (N'Ƕ'),
            (N'Ƿ'),
            (N'Ǹ');

SELECT *
FROM   @T
WHERE  Col LIKE N'%�%'

Devoluciones

Col
A
B
C
Ƕ
Ƿ
Ǹ

SELECT *
FROM   @T
WHERE  Col = N'�' 

Devoluciones

Col
Ƕ
Ƿ
Ǹ

La generación de cada "carácter" de doble byte posible con el siguiente muestra que la =versión coincide con 21,229 de ellos y la LIKE N'%�%'versión con todos ellos (he intentado algunas colaciones no binarias con el mismo resultado).

WITH T(I, N)
AS 
(
SELECT TOP 65536 ROW_NUMBER() OVER (ORDER BY @@SPID),
                 NCHAR(ROW_NUMBER() OVER (ORDER BY @@SPID))
FROM master..spt_values v1, 
     master..spt_values v2
)
SELECT I, N 
FROM T
WHERE N = N'�'  

¿Alguien capaz de arrojar alguna luz sobre lo que está pasando aquí?

El uso COLLATE Latin1_General_BINentonces coincide con el carácter único NCHAR(65533), pero la pregunta es entender qué reglas usa en el otro caso. ¿Qué tienen de especial esos 21,229 caracteres que coinciden con =y por qué todo coincide con el comodín? Supongo que hay alguna razón detrás de esto que me estoy perdiendo.

nchar(65534)[y otros 21 mil] funcionan igual de bien nchar(65533). La pregunta podría haberse formulado usando nchar(502) igualmente como : se comporta igual que LIKE N'%Ƕ%'(coincide con todo) y en el =caso. Esa es probablemente una pista bastante grande.

Cambiar el SELECTen la última consulta SELECT I, N, RANK() OVER(ORDER BY N)muestra que SQL Server no puede clasificar los caracteres. Parece que cualquier carácter no manejado por la colación se considera equivalente.

Una base de datos con una Latin1_General_100_CS_ASclasificación produce 5840 coincidencias. Latin1_General_100_CS_ASreduce considerablemente los =partidos, pero no cambia el LIKEcomportamiento. Parece que hay una gran cantidad de caracteres que se ha reducido en colaciones posteriores que todos se comparan igual y se ignoran en las LIKEbúsquedas con comodines .

Estoy usando SQL Server 2016. El símbolo es el carácter de reemplazo Unicode, pero los únicos caracteres no válidos en la codificación UCS-2 son 55296 - 57343 AFAIK y coincide claramente con puntos de código perfectamente válidos como los N'Ԛ'que no están en este rango.

Todos estos caracteres se comportan como la cadena vacía para LIKEy =. Incluso evalúan como equivalente. N'' = N'�'es cierto, y puede soltarlo en una LIKEcomparación de espacios individuales LIKE '_' + nchar(65533) + '_'sin efecto. LENSin embargo, las comparaciones producen resultados diferentes, por lo que probablemente solo sean ciertas funciones de cadena.

Creo que el LIKEcomportamiento es correcto para este caso; se comporta como un valor desconocido (que podría ser cualquier cosa). Sucede también para estos otros personajes:

  • nchar(11217) (Signo de incertidumbre)
  • nchar(65532) (Carácter de reemplazo de objeto)
  • nchar(65533) (Carácter de reemplazo)
  • nchar(65534) (No es un personaje)

Entonces, si quiero encontrar todos los caracteres que representan incertidumbre con el signo igual, usaría una intercalación que admita caracteres adicionales como Latin1_General_100_CI_AS_SC.

Supongo que estos son el grupo de "caracteres no ponderados" mencionados en la documentación, Compaginación y Soporte Unicode .

Respuestas:


9

La forma en que un "personaje" (que puede estar compuesto por múltiples puntos de código: pares sustitutos, combinación de caracteres, etc.) se compara con otro se basa en un conjunto de reglas bastante complejo. Es tan complejo debido a la necesidad de tener en cuenta todas las diversas (y a veces "extrañas") reglas que se encuentran en todos los idiomas representados en la especificación Unicode . Este sistema se aplica a las intercalaciones no binarias para todos los NVARCHARdatos y para los VARCHARdatos que utilizan una intercalación de Windows y no una intercalación de SQL Server (una que comienza con SQL_). Este sistema no se aplica a los VARCHARdatos que utilizan una Intercalación de SQL Server, ya que utilizan asignaciones simples.

La mayoría de las reglas se definen en el Algoritmo de clasificación Unicode (UCA) . Algunas de esas reglas, y algunas no cubiertas en la UCA, son:

  1. El orden predeterminado / peso dado en el allkeys.txtarchivo (indicado a continuación)
  2. ¿Qué sensibilidades y opciones se están utilizando (p. Ej., ¿Distingue mayúsculas o minúsculas ?, y si es sensible, ¿es primero mayúsculas o minúsculas primero?)
  3. Cualquier anulación basada en la configuración regional.
  4. Se está utilizando la versión del estándar Unicode.
  5. El factor "humano" (es decir, Unicode es una especificación, no un software y, por lo tanto, cada vendedor debe implementarlo)

Hice hincapié en el punto final con respecto al factor humano para que quede claro que no se debe esperar que SQL Server se comporte siempre al 100% de acuerdo con la especificación.

El factor principal aquí es la ponderación dada a cada Punto de Código, y el hecho de que múltiples Puntos de Código pueden compartir la misma especificación de peso. Puede encontrar los pesos básicos (sin anulaciones específicas de la localidad) aquí (creo que la 100serie de intercalaciones es Unicode v 5.0 - confirmación informal en los comentarios sobre el elemento de Microsoft Connect ):

http://www.unicode.org/Public/UCA/5.0.0/allkeys.txt

El punto de código en cuestión - U + FFFD - se define como:

FFFD  ; [*0F12.0020.0002.FFFD] # REPLACEMENT CHARACTER

Esa notación se define en la sección 9.1 Formato de archivo Allkeys de la UCA:

<entry>       := <charList> ';' <collElement>+ <eol>
<charList>    := <char>+
<collElement> := "[" <alt> <weight> "." <weight> "." <weight> ("." <weight>)? "]"
<alt>         := "*" | "."

Collation elements marked with a "*" are variable.

Esa última línea es importante ya que el Punto de Código que estamos viendo tiene una especificación que de hecho comienza con "*". En la sección 3.6 Ponderación variable hay cuatro comportamientos posibles definidos, basados ​​en los valores de configuración de Colación a los que no tenemos acceso directo (estos están codificados en la implementación de Microsoft de cada Colación, como si las mayúsculas y minúsculas usan minúsculas primero o mayúscula primero, una propiedad que es diferente entre los VARCHARdatos que usan SQL_colaciones y todas las demás variaciones).

No tengo tiempo para hacer una investigación completa sobre qué caminos se toman e inferir qué opciones se están utilizando de manera que se pueda dar una prueba más sólida, pero es seguro decir que dentro de cada especificación de Punto de Código, ya sea o no algo se considera "igual" no siempre va a utilizar la especificación completa. En este caso, tenemos "0F12.0020.0002.FFFD" y lo más probable es que solo se usen los niveles 2 y 3 (es decir, .0020.0002. ). Haciendo un "Recuento" en Notepad ++ para ".0020.0002". encuentra 12,581 coincidencias (incluidos los caracteres suplementarios con los que aún no hemos estado lidiando). Hacer un "conteo" en "[*" devuelve 4049 coincidencias. Hacer un RegEx "Buscar" / "Contar" utilizando un patrón de\[\*\d{4}\.0020\.0002devuelve 832 partidos. Entonces, en alguna parte de esta combinación, más posiblemente algunas otras reglas que no estoy viendo, más algunos detalles de implementación específicos de Microsoft, es la explicación completa de este comportamiento. Y para ser claros, el comportamiento es el mismo para todos los personajes coincidentes, ya que todos coinciden entre sí, ya que todos tienen el mismo peso una vez que se aplican las reglas (es decir, esta pregunta podría haberse hecho sobre cualquiera de ellos, no necesariamente señor ).

Puede ver con la consulta a continuación y cambiar la COLLATEcláusula según los resultados debajo de la consulta cómo funcionan las diversas sensibilidades en las dos versiones de Colaciones:

;WITH cte AS
(
  SELECT     TOP (65536) ROW_NUMBER() OVER (ORDER BY (SELECT 0)) - 1 AS [Num]
  FROM       [master].sys.columns col
  CROSS JOIN [master].sys.objects obj
)
SELECT cte.Num AS [Decimal],
       CONVERT(VARBINARY(2), cte.Num) AS [Hex],
       NCHAR(cte.Num) AS [Character]
FROM   cte
WHERE  NCHAR(cte.Num) = NCHAR(0xFFFD) COLLATE Latin1_General_100_CS_AS_WS --N'�'
ORDER BY cte.Num;

A continuación se muestran los diversos recuentos de caracteres coincidentes en diferentes colaciones.

Latin1_General_100_CS_AS_WS   =   5840
Latin1_General_100_CS_AS      =   5841 (The "extra" character is U+3000)
Latin1_General_100_CI_AS      =   5841
Latin1_General_100_CI_AI      =   6311

Latin1_General_CS_AS_WS       = 21,229
Latin1_General_CS_AS          = 21,230
Latin1_General_CI_AS          = 21,230
Latin1_General_CI_AI          = 21,537

En todas las colaciones enumeradas anteriormente N'' = N'�'también se evalúa como verdadero.

ACTUALIZAR

Pude investigar un poco más y esto es lo que encontré:

Cómo debería funcionar "probablemente"

Utilizando la demostración de colación de la UCI , configuré la configuración regional en "en-US-u-va-posix", configuré la fuerza en "primario", verifiqué mostrar "ordenar claves" y pegué los siguientes 4 caracteres que copié de resultados de la consulta anterior (usando la Latin1_General_100_CI_AIClasificación):

�
Ԩ
ԩ
Ԫ

y eso devuelve:

Ԫ
    60 2E 02 .
Ԩ
    60 7A .
ԩ
    60 7A .
�
    FF FD .

Luego, verifique las propiedades de caracteres para " " en http://unicode.org/cldr/utility/character.jsp?a=fffd y vea que la clave de clasificación de nivel 1 (es decir, FF FD) coincide con la propiedad "uca". Al hacer clic en esa propiedad "uca" se accede a una página de búsqueda: http://unicode.org/cldr/utility/list-unicodeset.jsp?a=%5B%3Auca%3DFFFD%3A%5D , que muestra solo 1 coincidencia. Y, en el archivo allkeys.txt , el peso de clasificación de nivel 1 se muestra como 0F12, y solo hay una coincidencia para eso.

Para asegurarme de que estamos interpretando el comportamiento correctamente, miré a otro personaje: OMICRON DE LA LETRA DE CAPITAL GRIEGO CON VARIA en http://unicode.org/cldr/utility/character.jsp?a=1FF8 que tiene una "uca" ( es decir, nivel 1 peso de clasificación / elemento de clasificación) de 5F30. Al hacer clic en ese "5F30" nos lleva a una página de búsqueda: http://unicode.org/cldr/utility/list-unicodeset.jsp?a=%5B%3Auca%3D5F30%3A%5D , que muestra 30 coincidencias, 20 de estando en el rango 0 - 65535 (es decir, U + 0000 - U + FFFF). Al buscar en el archivo allkeys.txt el Punto de código 1FF8 , vemos un peso de clasificación de nivel 1 de 12E0. Haciendo un "conteo" en Notepad ++ en12E0. muestra 30 coincidencias (esto coincide con los resultados de Unicode.org, aunque no está garantizado ya que el archivo es para Unicode v 5.0 y el sitio utiliza datos de Unicode v 9.0).

En SQL Server, la siguiente consulta devuelve 20 coincidencias, igual que la búsqueda de Unicode.org al eliminar los 10 caracteres adicionales:

;WITH cte AS
(
  SELECT TOP (65535) ROW_NUMBER() OVER (ORDER BY (SELECT 0)) AS [Num]
  FROM   [master].sys.columns col
  CROSS JOIN [master].sys.objects obj
)
SELECT cte.Num AS [Decimal],
       CONVERT(VARCHAR(50), CONVERT(VARBINARY(2), cte.Num), 2) AS [Hex],
       NCHAR(cte.Num) AS [Character]
FROM cte
WHERE NCHAR(cte.Num) = NCHAR(0x1FF8) COLLATE Latin1_General_100_CI_AI
ORDER BY cte.Num;

Y, solo para estar seguro, volviendo a la página de demostración de colación de ICU y reemplazando los caracteres en el cuadro "Entrada" con los siguientes 3 caracteres tomados de la lista de 20 resultados de SQL Server:


𝜪

muestra que, de hecho, todos tienen el mismo 5F 30peso de clasificación de nivel 1 (que coincide con el campo "uca" en la página de propiedades del personaje).

Entonces, ciertamente parece que este personaje en particular no debería coincidir con nada más.

Cómo funciona realmente (al menos en Microsoft-land)

A diferencia de SQL Server, .NET tiene un medio para mostrar la clave de clasificación para una cadena a través del método CompareInfo.GetSortKey . Usando este método y pasando solo el carácter U + FFFD, devuelve una clave de clasificación de 0x0101010100. Luego, iterando sobre todos los caracteres en el rango de 0 - 65535 para ver cuáles tenían una clave de clasificación de 0x01010101004529 coincidencias. Esto no coincide exactamente con el 5840 devuelto en SQL Server (cuando se usa la Latin1_General_100_CS_AS_WSClasificación), pero es lo más cercano que podemos llegar (por ahora) dado que estoy ejecutando Windows 10 y .NET Framework versión 4.6.1, que usa Unicode v 6.3.0 según el gráfico de la clase CharUnicodeInfo(en "Nota para quienes llaman", en la sección "Comentarios"). Por el momento, estoy usando una función SQLCLR y, por lo tanto, no puedo cambiar la versión de Framework de destino. Cuando tenga la oportunidad, crearé una aplicación de consola y usaré una versión de Framework de destino de 4.5, ya que usa Unicode v 5.0, que debería coincidir con las intercalaciones de la serie 100.

Lo que muestra esta prueba es que, incluso sin el mismo número exacto de coincidencias entre .NET y SQL Server para U + FFFD, está bastante claro que este no es un comportamiento específico de SQL Server, y que ya sea intencional o de supervisión con la implementación realizada por Microsoft, el carácter U + FFFD de hecho coincide con bastantes caracteres, incluso si no debería de acuerdo con la especificación Unicode. Y, dado que este personaje coincide con U + 0000 (nulo), probablemente sea solo una cuestión de pesos perdidos.

ADEMÁS

Con respecto a la diferencia de comportamiento en la =consulta frente a la LIKE N'%�%'consulta, tiene que ver con los comodines y los pesos faltantes (supongo) para estos (es decir � Ƕ Ƿ Ǹ) caracteres. Si la LIKEcondición se cambia a simple LIKE N'�', devuelve las mismas 3 filas que la =condición. Si el problema con los comodines no se debe a los pesos "perdidos" (no hay 0x00clave de clasificación devuelta por CompareInfo.GetSortKey, por cierto), entonces podría deberse a que estos caracteres potencialmente tienen una propiedad que permite que la clave de clasificación varíe según el contexto (es decir, los caracteres circundantes )


Gracias: en el enlace allkeys.txt parece que no hay nada más dado el mismo peso que FFFD(la búsqueda *0F12.0020.0002.FFFDsolo devuelve un resultado). Según la observación de @ Forrest de que todos coinciden con la cadena vacía y un poco más de lectura sobre el tema, parece que el peso que comparten en las diversas colaciones no binarias es, de hecho, cero.
Martin Smith

1
@MartinSmith Hizo algunas investigaciones usando la demostración de colación de la UCI , y colocando � A a \u24D0y algunas otras que estaban en el conjunto de resultados de 5839 coincidencias. Parece que no puedes saltarte el primer peso, y este reemplazo de carbón es el único que comienza 0F12. Muchos otros también tuvieron un primer peso único, y muchos faltaban por completo en el archivo allkeys. Entonces, esto podría ser un error de implementación debido a un error humano. Vi este personaje en el grupo "no compatible" en el sitio Unicode en sus tablas de colaciones. Se verá más mañana.
Solomon Rutzky el

Rextester usa 4.5. De hecho, veo menos coincidencias en esa versión (3385). ¿Tal vez estoy configurando alguna opción diferente para ti? rextester.com/JBWIN31407
Martin Smith

Por cierto, esa clave de clasificación 01 01 01 01 00se menciona aquí archives.miloush.net/michkap/archive/2007/09/10/4847780.html (parece CompareInfo.InternalGetSortKeyllamadas LCMapStringEx)
Martin Smith

@ MartinSmith Jugué un poco con él, pero aún no estoy seguro de cuál es la diferencia. El sistema operativo en el que .NET se está ejecutando sí tiene en cuenta. Mañana buscaré más si tengo tiempo. Sin embargo, independientemente del número de coincidencias, esto al menos parece confirmar la razón del comportamiento, especialmente ahora que tenemos una idea de la estructura de la clave de clasificación gracias al blog al que se vinculó y a otros vinculados. La página CharUnicodeInfo a la que me vinculé menciona las llamadas subyacentes de colación, que es la base de mi sugerencia aquí: connect.microsoft.com/SQLServer/feedback/details/2932336 :-)
Solomon Rutzky
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.