Como criar nomes de parâmetros e variáveis ​​Unicode

53

Tudo isso 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

Mas você provavelmente pode ver para onde estou indo com isso: não quero @Shrug, quero @¯\_(ツ)_/¯.

Nenhum deles funciona em nenhuma versão de 2008 a 2017:

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

Então, existe uma maneira de usar nomes de parâmetros de procedimento armazenado unicode?

Brent Ozar
fonte

Respostas:

44

Bem, os identificadores são sempre Unicode / NVARCHAR, então tecnicamente você não pode criar nada que não tenha um nome Unicode 🙃.

O problema que você está tendo aqui se deve inteiramente à classificação do (s) personagem (s) que está sendo usado. As regras para identificadores regulares (ou seja, não delimitados) são:

  • A primeira letra deve ser:
    • Uma letra conforme definida pelo Padrão Unicode 3.2.
    • sublinhado (_), sinal de arroba (@) ou sinal de número (#)
  • As cartas subsequentes podem ser:
    • Letras como definidas no Padrão Unicode 3.2.
    • Números decimais de scripts em latim básico ou outros scripts nacionais.
    • sublinhado (_), sinal de arroba (@), sinal de número (#) ou cifrão ($)
  • Espaços incorporados ou caracteres especiais não são permitidos.
  • Caracteres suplementares não são permitidos.

Coloquei em negrito as únicas regras que importam neste contexto. A razão pela qual as regras da "Primeira letra" não são relevantes aqui é que a primeira letra em todas as variáveis ​​e parâmetros locais é sempre o "arroba" @.

E para ficar claro: o que é considerado uma "letra" e o que é considerado um "dígito decimal" é baseado nas propriedades que cada caractere é atribuído no banco de dados de caracteres Unicode. O Unicode atribui muitas propriedades a cada caractere, como: is_uppercase, is_lowercase, is_digit, is_decimal, is_combining, etc. etc. Não se trata do que os mortais considerariam letras ou dígitos decimais, mas quais caracteres foram atribuídos a essas propriedades. Essas propriedades são frequentemente usadas em Expressões regulares para corresponder à "pontuação" etc. Por exemplo, \p{Lu}corresponde a qualquer letra maiúscula (em todos os idiomas / scripts) e \p{IsDingbats}corresponde a qualquer caractere "Dingbats".

Então, na sua tentativa de fazer:

DECLARE @¯\_(ツ)_ INT;

somente os _caracteres (sublinhado ou "linha baixa") e (letra Katakana Tu U + 30C4) se encaixam nessas regras. Agora, todos os caracteres ¯\_(ツ)_/¯são adequados para identificadores delimitados, mas infelizmente parece que nomes e GOTOrótulos de variáveis ​​/ parâmetros não podem ser delimitados (embora os nomes dos cursores possam ser).

Portanto, para nomes de variáveis ​​/ parâmetros, uma vez que eles não podem ser delimitados, você deve usar apenas caracteres que se qualificam como "letras" ou "dígitos decimais" a partir do Unicode 3.2 (bem, de acordo com a documentação; preciso testar se as classificações foram atualizadas para versões mais recentes do Unicode, pois as classificações são tratadas de maneira diferente dos pesos de classificação).

NO ENTANTO # 1 , as coisas não são tão diretas quanto deveriam ser. Agora pude concluir minha pesquisa e descobri que a definição declarada não está totalmente correta. A definição precisa (e verificável) de quais caracteres são válidos para identificadores regulares é:

  • Primeiro personagem:

    • Pode ser qualquer coisa classificada no Unicode 3.2 como "ID_Start" (que inclui "Letras", mas também "caracteres numéricos semelhantes a letras")
    • Pode ser _(linha baixa / sublinhado) ou _(linha baixa de largura total)
    • Pode ser @, mas apenas para variáveis ​​/ parâmetros
    • Pode ser #, mas se objeto vinculado ao esquema, apenas para Tabelas e Procedimentos Armazenados (nesse caso, eles indicam que o objeto é temporário)
  • Caracteres subsequentes:

    • Pode ser qualquer coisa classificada no Unicode 3.2 como "ID_Continue" (que inclui números "decimais", mas também "marcas combinadas de espaçamento e não espaçamento" e "conexão de sinais de pontuação")
    • Pode ser @, #ou$
    • Pode ser um dos 26 caracteres classificados no Unicode 3.2 como caracteres de controle de formato

(curiosidade: o "ID" em "ID_Start" e "ID_Continue" significa "Identifier". Imagine isso ;-)

De acordo com "Utilitários Unicode: UnicodeSet":

  • Caracteres iniciais válidos

    [: Idade = 3,2:] & [: ID_Start = Sim:]

    -- 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 continuação válidos

    [: Idade = 3,2:] & [: ID_Continue = Sim:]

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

No entanto, o número 2 , nem mesmo pesquisar no banco de dados Unicode pode ser tão fácil. Essas duas pesquisas produzem uma lista de caracteres válidos para essas categorizações e esses caracteres são do Unicode 3.2, MAS as definições das várias categorizações são alteradas nas versões do Unicode Standard. Ou seja, a definição de "ID_Start" no Unicode v 10.0 (o que essa pesquisa está usando hoje, 26/03/2018) não é o que era no Unicode v 3.2. Portanto, a pesquisa online não pode fornecer uma lista exata. Mas você pode pegar os arquivos de dados Unicode 3.2 e pegar a lista de caracteres "ID_Start" e "ID_Continue" a partir daí para comparar com o que o SQL Server realmente usa. E eu fiz isso e confirmei uma correspondência exata com as regras que afirmei acima em "NO ENTANTO # 1".

As duas postagens a seguir detalham as etapas para encontrar a lista exata de caracteres, incluindo links para os scripts de importação:

  1. O código uni: a busca da lista verdadeira de caracteres válidos para identificadores regulares do T-SQL, parte 1
  2. O código uni: a busca da lista verdadeira de caracteres válidos para identificadores regulares do T-SQL, parte 2

Por fim, para quem quer apenas ver a lista e não está preocupado com o que foi necessário para descobrir e verificar, você pode encontrar isso aqui:

Lista completamente completa de caracteres identificadores T-SQL válidos
(aguarde um momento para carregar a página; são 3,5 MB e quase 47k linhas)


Em relação aos caracteres ASCII "válidos", como /e -, não funcionando: o problema não tem nada a ver com se os caracteres também estão definidos ou não no conjunto de caracteres ASCII. Para ser válido, o personagem tem que ter tanto o ID_Startou ID_Continuepropriedade, ou ser um dos poucos personagens personalizados observado separadamente. Existem alguns caracteres ASCII "válidos" (62 do total de 128 caracteres - principalmente caracteres de pontuação e controle) que não são válidos nos identificadores "regulares".

Quanto aos caracteres suplementares: embora eles certamente possam ser usados ​​em identificadores delimitados (e a documentação não pareça estar afirmando de outra forma), se é verdade que eles não podem ser usados ​​em identificadores regulares, é mais provável que eles não sejam totalmente suportados nas funções internas anteriores aos Agrupamentos com Reconhecimento de Caracteres Suplementares foram introduzidas no SQL Server 2012 (elas são tratadas como dois caracteres "desconhecidos" individuais), nem poderiam ser diferenciadas entre si em Agrupamentos não binários anteriores ao 100- agrupamentos de nível (introduzidos no SQL Server 2008).

Em relação ao ASCII: codificações de 8 bits não estão sendo usadas aqui, pois todos os identificadores são Unicode / NVARCHAR/ UTF-16 LE. A instrução SELECT ASCII('ツ');retorna um valor 63cujo valor é "?" (try SELECT CHAR(63);:), pois esse caractere, mesmo que prefixado com um "N" maiúsculo, certamente não está na Página de Código 1252. No entanto, esse caractere está na Página de Código Coreana e produz o resultado correto, mesmo sem o "N ", em um banco de dados com um agrupamento padrão coreano:

SELECT UNICODE('ツ'); -- 12484

Em relação à primeira letra que afeta o resultado: isso não é possível, pois a primeira letra para variáveis ​​e parâmetros locais é sempre @. A primeira letra que conseguimos controlar para esses nomes é na verdade o segundo caractere do nome.

A respeito de por que nomes de variáveis ​​locais, nomes de parâmetros e GOTOrótulos não podem ser delimitados: suspeito que isso se deva a esses itens serem parte do próprio idioma e não algo que encontrará o caminho para uma tabela do sistema como dados.

Solomon Rutzky
fonte
Tão incrível, obrigado. Isso me levou a isso, o que vai fazer para um grande post: gist.github.com/BrentOzar/9b08b5ab2b617847dbe4aa0297b4cd5b
Brent Ozar
8
@BrentOzar você fez uma tomografia recentemente?
Ross Presser
Uau, essa é uma resposta impressionante! E eu apóio a observação de Ross Presser.
SQL Nerd
22

Eu não acho que é Unicode que está causando o problema; no caso de nomes de variáveis ​​ou parâmetros locais, é que o caractere não é um caractere ASCII / Unicode 3.2 válido (e não existe uma sequência de escape para variáveis ​​/ parâmetros como existem para outros tipos de entidade).

Este lote funciona bem, ele usa um caractere Unicode que simplesmente não viola as regras para identificadores não delimitados:

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

Assim que você tenta usar uma barra ou um hífen, ambos com caracteres ASCII válidos, ele ataca:

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

A documentação não aborda por que esses identificadores estão sujeitos a regras ligeiramente diferentes das de todos os outros identificadores, ou por que eles não podem ser escapados como os outros.

Aaron Bertrand
fonte
Oi Aaron. Apenas para esclarecer alguns pontos aqui: 1) o primeiro caractere não é um problema, pois o primeiro caractere é na verdade o nome @do var / param. Qualquer um dos caracteres que não estão funcionando não deve funcionar em nenhuma posição, mesmo se precedido por caracteres válidos. 2) o documento declara apenas que caracteres suplementares não podem ser usados ​​em identificadores regulares (o que parece ser o caso de tudo o que tentei), mas não impõe restrições a identificadores delimitados, assim como em espaços incorporados. Além disso, acredito que essas são diferentes porque fazem parte da linguagem T-SQL, não das coisas no banco de dados.
Solomon Rutzky 19/03/19
@SolomonRutzky Sinto que o problema é simples e inteiramente que um nome de parâmetro não pode ser delimitado como outras entidades. Se eu pudesse colocar colchetes ou aspas duplas em torno de um nome de parâmetro, poderia colocar qualquer um desses caracteres em qualquer posição. A pergunta postula que você não pode usar caracteres Unicode em um nome de parâmetro, e esse claramente não é o caso. Existem alguns caracteres Unicode que você pode usar e alguns caracteres ASCII que não podem .
Aaron Bertrand
Sim, concordo que, se os nomes e GOTOrótulos de variáveis ​​/ parâmetros permitirem ser delimitados, a única restrição será o comprimento. Só posso supor que a análise e / ou manipulação desses poucos itens ocorra em um nível diferente ou que tenha outras restrições que permitam a exclusão de valores delimitados. Pelo menos espero que não tenha sido arbitrário ou descuidado.
Solomon Rutzky 19/03/19
(não tinha visto a atualização do seu comentário quando eu respondi um momento atrás). Sim, a pergunta implica que o OP não pode usar caracteres Unicode, mas a formulação da pergunta é tecnicamente incorreta, pois todos os nomes são sempre Unicode / NVARCHAR. Isso não tem nada a ver com ASCII, pois é uma codificação de 8 bits que não está sendo usada aqui. Todos os caracteres aqui são caracteres Unicode, mesmo que alguns também existam em várias páginas de código de 8 bits. Como expliquei na minha resposta, quais caracteres podem ser usados ​​é uma questão de quais foram marcados com um is_alphabeticou outro numeric_type=decimal.
Solomon Rutzky 19/03/19
Eu vi procs armazenados que estavam cheios de cocô, mas nunca o nomearam!
Mitch Wheat