Cómo crear parámetros Unicode y nombres de variables

53

Todo esto funciona:

CREATE DATABASE [¯\_(ツ)_/¯];
GO
USE [¯\_(ツ)_/¯];
GO
CREATE SCHEMA [¯\_(ツ)_/¯];
GO
CREATE TABLE [¯\_(ツ)_/¯].[¯\_(ツ)_/¯]([¯\_(ツ)_/¯] NVARCHAR(20));
GO
CREATE UNIQUE CLUSTERED INDEX [¯\_(ツ)_/¯] ON [¯\_(ツ)_/¯].[¯\_(ツ)_/¯]([¯\_(ツ)_/¯]);
GO
INSERT INTO [¯\_(ツ)_/¯].[¯\_(ツ)_/¯]([¯\_(ツ)_/¯]) VALUES (N'[¯\_(ツ)_/¯]');
GO
CREATE VIEW [¯\_(ツ)_/¯].[vw_¯\_(ツ)_/¯] AS SELECT [¯\_(ツ)_/¯] FROM [¯\_(ツ)_/¯].[¯\_(ツ)_/¯];
GO
CREATE PROC [¯\_(ツ)_/¯].[sp_¯\_(ツ)_/¯] @Shrug NVARCHAR(20) AS SELECT [¯\_(ツ)_/¯] FROM [¯\_(ツ)_/¯].[vw_¯\_(ツ)_/¯] WHERE [¯\_(ツ)_/¯] = @Shrug;
GO
EXEC [¯\_(ツ)_/¯].[¯\_(ツ)_/¯].[sp_¯\_(ツ)_/¯] @Shrug = N'[¯\_(ツ)_/¯]';
GO

Pero probablemente puedas ver a dónde voy con esto: no quiero a @Shrug, quiero @¯\_(ツ)_/¯.

Ninguno de estos funciona en ninguna versión de 2008-2017:

CREATE PROC [¯\_(ツ)_/¯].[sp_¯\_(ツ)_/¯] @[¯\_(ツ)_/¯] NVARCHAR(20) AS SELECT [¯\_(ツ)_/¯] FROM [¯\_(ツ)_/¯].[vw_¯\_(ツ)_/¯] WHERE [¯\_(ツ)_/¯] = @[¯\_(ツ)_/¯];
GO
CREATE PROC [¯\_(ツ)_/¯].[sp_¯\_(ツ)_/¯] [@¯\_(ツ)_/¯] NVARCHAR(20) AS SELECT [¯\_(ツ)_/¯] FROM [¯\_(ツ)_/¯].[vw_¯\_(ツ)_/¯] WHERE [¯\_(ツ)_/¯] = [@¯\_(ツ)_/¯];
GO

Entonces, ¿hay alguna manera de usar nombres de parámetros de procedimiento almacenado unicode?

Brent Ozar
fuente

Respuestas:

44

Bueno, los identificadores siempre son Unicode / NVARCHAR, por lo que técnicamente no puedes crear nada que no tenga un nombre Unicode 🙃.

El problema que tiene aquí se debe completamente a la clasificación de los caracteres que se utilizan. Las reglas para los identificadores regulares (es decir, no delimitados) son:

  • La primera letra debe ser:
    • Una carta según la definición del estándar Unicode 3.2.
    • guión bajo (_), en el signo (@) o signo de número (#)
  • Las letras posteriores pueden ser:
    • Letras tal como se definen en el Estándar Unicode 3.2.
    • Números decimales del latín básico u otros scripts nacionales.
    • subrayado (_), en el signo (@), signo de número (#) o signo de dólar ($)
  • No se permiten espacios incrustados o caracteres especiales.
  • No se permiten caracteres suplementarios.

En negrita las únicas reglas que importan en este contexto. La razón por la cual las reglas de "Primera letra" no son relevantes aquí es que la primera letra en todas las variables y parámetros locales es siempre el "signo de at" @.

Y para ser claros: lo que se considera una "letra" y lo que se considera un "dígito decimal" se basa en las propiedades que cada carácter tiene asignado en la base de datos de caracteres Unicode. Unicode asigna muchas propiedades a cada carácter, como por ejemplo: is_uppercase, is_lowercase, is_digit, is_decimal, is_combining, etc., etc. Estas propiedades se usan a menudo en Expresiones regulares para que coincidan con la "puntuación", etc. Por ejemplo, \p{Lu}coincide con cualquier letra mayúscula (en todos los idiomas / scripts) y \p{IsDingbats}con cualquier carácter "Simbolos".

Entonces, en su intento de hacer:

DECLARE @¯\_(ツ)_ INT;

solo los caracteres _(subrayado o "línea baja") y (Katakana Letter Tu U + 30C4) se ajustan a esas reglas. Ahora, todos los caracteres ¯\_(ツ)_/¯están bien para identificadores delimitados, pero desafortunadamente parece que los nombres y GOTOetiquetas de variables / parámetros no pueden delimitarse (aunque los nombres de cursor sí pueden).

Por lo tanto, para los nombres de variables / parámetros, dado que no se pueden delimitar, debe usar solo caracteres que califiquen como "letras" o "dígitos decimales" a partir de Unicode 3.2 (bueno, de acuerdo con la documentación; necesito probar si las clasificaciones se han actualizado para las versiones más nuevas de Unicode, ya que las clasificaciones se manejan de manera diferente a los pesos de clasificación).

SIN EMBARGO # 1 , las cosas no son tan sencillas como deberían ser. Ahora he podido completar mi investigación y he descubierto que la definición establecida no es del todo correcta. La definición precisa (y verificable) de qué caracteres son válidos para los identificadores regulares es:

  • Primer personaje:

    • Puede ser cualquier cosa clasificada en Unicode 3.2 como "ID_Start" (que incluye "Letras" pero también "caracteres numéricos tipo carta")
    • Puede ser _(línea baja / subrayado) o _(línea baja de ancho completo)
    • Puede ser @, pero solo para variables / parámetros
    • Puede ser #, pero si es un objeto vinculado al esquema, solo para tablas y procedimientos almacenados (en cuyo caso indican que el objeto es temporal)
  • Caracteres posteriores:

    • Puede ser cualquier cosa clasificada en Unicode 3.2 como "ID_Continue" (que incluye números "decimales", pero también "signos de combinación de espaciado y no espaciado" y "conexión de signos de puntuación")
    • Puede ser @, #o$
    • Puede ser cualquiera de los 26 caracteres clasificados en Unicode 3.2 como caracteres de control de formato

(Dato curioso: la "ID" en "ID_Start" y "ID_Continue" significa "Identificador". Imagínese eso ;-)

De acuerdo con "Utilidades Unicode: UnicodeSet":

  • Caracteres iniciales válidos

    [: Edad = 3.2:] & [: ID_Start = Sí:]

    -- Test one "Letter" from each of 10+ languages, as of Unicode 3.2
    DECLARE @ᔠᑥᑒᏯשፙᇏᆇᄳᄈლဪඤagೋӁウﺲﶨ   INT;
    -- works
    
    
    -- Test a Supplementary Character that is a "Letter" as of Unicode 3.2
    DECLARE @𝒲 INT;-- Mathematical Script Capital W (U+1D4B2)
    /*
    Msg 102, Level 15, State 1, Line XXXXX
    Incorrect syntax near '0xd835'.
    */
  • Caracteres de continuación válidos

    [: Age = 3.2:] & [: ID_Continue = Sí:]

    -- Test various decimal numbers, but none are Supplementary Characters
    DECLARE @६৮༦൯௫୫9 INT;
    -- works (including some Hebrew and Arabic, which are right-to-left languages)
    
    
    -- Test a Supplementary Character that is a "decimal" number as of Unicode 3.2
    DECLARE @𝟜 INT; -- MATHEMATICAL DOUBLE-STRUCK DIGIT FOUR (U+1D7DC)
    /*
    Msg 102, Level 15, State 1, Line XXXXX
    Incorrect syntax near '0xd835'.
    */
    -- D835 is the first character in the surrogate pair D835 DFDC that makes up U+1D7DC

SIN EMBARGO # 2 , ni siquiera buscar en la base de datos Unicode puede ser tan fácil. Esas dos búsquedas producen una lista de caracteres válidos para esas categorizaciones, y esos caracteres son de Unicode 3.2, PERO las definiciones de las diferentes categorizaciones cambian a través de las versiones del Estándar Unicode. Es decir, la definición de "ID_Start" en Unicode v 10.0 (lo que esa búsqueda está usando hoy, 2018-03-26) no es lo que era en Unicode v 3.2. Por lo tanto, la búsqueda en línea no puede proporcionar una lista exacta. Pero puede tomar los archivos de datos Unicode 3.2 y tomar la lista de caracteres "ID_Start" y "ID_Continue" desde allí para comparar con lo que SQL Server realmente usa. Y he hecho esto y he confirmado una coincidencia exacta con las reglas que dije anteriormente en "SIN EMBARGO # 1".

Las siguientes dos publicaciones de blog detallan los pasos dados para encontrar la lista exacta de caracteres, incluidos los enlaces a los scripts de importación:

  1. El Uni-Code: la búsqueda de la verdadera lista de caracteres válidos para identificadores regulares de T-SQL, Parte 1
  2. El Uni-Code: la búsqueda de la verdadera lista de caracteres válidos para identificadores regulares de T-SQL, Parte 2

Finalmente, para cualquiera que solo quiera ver la lista y no le preocupe lo que se necesitó para descubrirla y verificarla, puede encontrarla aquí:

Lista completamente completa de caracteres de identificación T-SQL válidos
(por favor, dedique un momento a la página para cargar; son 3,5 MB y casi 47k líneas)


Con respecto a los caracteres ASCII "válidos", como /y -, no funciona: el problema no tiene nada que ver con si los caracteres también están definidos en el conjunto de caracteres ASCII. Para que sea válido, el carácter debe tener la propiedad ID_Starto la ID_Continuepropiedad, o ser uno de los pocos caracteres personalizados que se indican por separado. Hay bastantes caracteres ASCII "válidos" (62 del total de 128, principalmente caracteres de puntuación y control) que no son válidos en los identificadores "regulares".

Con respecto a los caracteres suplementarios: si bien ciertamente se pueden usar en identificadores delimitados (y la documentación no parece indicar lo contrario), si es cierto que no se pueden usar en identificadores regulares, probablemente se deba a que no son totalmente compatibles en las funciones integradas anteriores a las intercalaciones suplementarias con reconocimiento de caracteres se introdujeron en SQL Server 2012 (se tratan como dos caracteres "desconocidos" individuales), ni siquiera podrían diferenciarse entre sí en intercalaciones no binarias anteriores a la 100- Intercalaciones de nivel (introducidas en SQL Server 2008).

Con respecto a ASCII: las codificaciones de 8 bits no se utilizan aquí ya que todos los identificadores son Unicode / NVARCHAR/ UTF-16 LE. La declaración SELECT ASCII('ツ');devuelve un valor 63cuyo es un "?" (intente:) SELECT CHAR(63);ya que ese carácter, incluso si está prefijado con una "N" mayúscula, ciertamente no está en la página de códigos 1252. Sin embargo, ese carácter está en la página de códigos coreana y produce el resultado correcto, incluso sin la "N "prefijo, en una base de datos con una clasificación coreana predeterminada:

SELECT UNICODE('ツ'); -- 12484

Con respecto a la primera letra que afecta el resultado: esto no es posible ya que la primera letra para variables y parámetros locales es siempre @. La primera letra que podemos controlar para estos nombres es en realidad el segundo carácter del nombre.

Con respecto a por qué los nombres de variables locales, los nombres de parámetros y las GOTOetiquetas no se pueden delimitar: sospecho que esto se debe a que estos elementos son parte del lenguaje en sí y no algo que se encontrará en una tabla del sistema como datos.

Solomon Rutzky
fuente
Tan genial, gracias. Eso me llevó a esto, que será una gran publicación de blog: gist.github.com/BrentOzar/9b08b5ab2b617847dbe4aa0297b4cd5b
Brent Ozar
8
@BrentOzar, ¿ha tenido una tomografía computarizada recientemente?
Ross Presser
¡Guau, esa es una respuesta bastante impresionante! Y secundo el comentario de Ross Presser.
SQL Nerd
22

No creo que sea Unicode lo que está causando el problema; en el caso de variables locales o nombres de parámetros, es que el carácter no es un carácter ASCII / Unicode 3.2 válido (y no hay ninguna secuencia de escape para variables / parámetros como la hay para otros tipos de entidades).

Este lote funciona bien, usa un carácter Unicode que simplemente no viola las reglas para identificadores no delimitados:

CREATE OR ALTER PROCEDURE dbo.[💩]
  @ツ int
AS
  CREATE TABLE [#ツ] (ツ int);
  INSERT [#ツ](ツ) SELECT @ツ;
  SELECT +1 FROM [#ツ];
GO
EXEC dbo.[💩] @ツ = 1;

Tan pronto como intente utilizar una barra o un guión, los cuales son caracteres ASCII válidos, bombardea:

Msg 102, Level 15, State 1, Procedure 💩 Incorrect syntax near '-'.

La documentación no aborda por qué estos identificadores están sujetos a reglas ligeramente diferentes que todos los demás identificadores, o por qué no se pueden escapar como los demás.

Aaron Bertrand
fuente
Hola aarón Solo para aclarar algunos puntos aquí: 1) el primer carácter no es un problema ya que el primer carácter es en realidad el @del nombre var / param. Cualquiera de los caracteres que no funcionan no debería funcionar en ninguna posición, incluso si están precedidos por caracteres válidos. 2) el documento solo establece que los caracteres suplementarios no se pueden usar en identificadores regulares (que parece ser el caso de todo lo que he intentado), pero no impone restricciones a los identificadores delimitados, al igual que con los espacios incrustados. Además, creo que estos son diferentes porque son parte del lenguaje T-SQL, no cosas en la base de datos.
Solomon Rutzky
@SolomonRutzky Siento que el problema es simple y completamente que el nombre de un parámetro no se puede delimitar como otras entidades. Si pudiera poner corchetes o comillas dobles alrededor del nombre de un parámetro, podría poner cualquiera de estos caracteres en él, en cualquier posición. La pregunta postula que no puede usar caracteres Unicode en el nombre de un parámetro, y ese claramente no es el caso. Hay algunos caracteres Unicode que puede usar, y algunos caracteres ASCII que no puede .
Aaron Bertrand
Sí, estoy de acuerdo en que si los nombres de variables / parámetros y las GOTOetiquetas permiten delimitarse, entonces la única restricción sería la longitud. Solo puedo suponer que analizar y / o manejar esos pocos elementos ocurre en un nivel diferente o tiene algunas otras restricciones que hicieron que permitir valores delimitados no funcionara. Al menos espero que no haya sido arbitrario o un descuido.
Solomon Rutzky
(no había visto la actualización de tu comentario cuando respondí hace un momento). Sí, la pregunta implica que el OP no puede usar caracteres Unicode, pero la formulación de la pregunta es técnicamente incorrecta ya que todos los nombres son siempre Unicode / NVARCHAR. Esto no tiene nada que ver con ASCII ya que es una codificación de 8 bits que no se usa aquí. Todos los caracteres aquí son caracteres Unicode, incluso si algunos también existen en varias páginas de códigos de 8 bits. Como expliqué en mi respuesta, qué caracteres se pueden usar es una cuestión de cuáles fueron etiquetados con is_alphabetico numeric_type=decimal.
Solomon Rutzky
¡He visto procs almacenados que estaban llenos de popó pero nunca lo nombraron!
Mitch Wheat