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 NVARCHAR
datos y para los VARCHAR
datos 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 VARCHAR
datos 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:
- El orden predeterminado / peso dado en el
allkeys.txt
archivo (indicado a continuación)
- ¿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?)
- Cualquier anulación basada en la configuración regional.
- Se está utilizando la versión del estándar Unicode.
- 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 100
serie 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 VARCHAR
datos 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\.0002
devuelve 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 COLLATE
clá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_AI
Clasificació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 30
peso 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 0x0101010100
4529 coincidencias. Esto no coincide exactamente con el 5840 devuelto en SQL Server (cuando se usa la Latin1_General_100_CS_AS_WS
Clasificació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 LIKE
condició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 0x00
clave 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 )
FFFD
(la búsqueda*0F12.0020.0002.FFFD
solo 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.