Comment créer un nom de paramètre et de variable Unicode


53

Tout cela fonctionne:

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

Mais vous pouvez probablement voir où je veux en venir avec ceci: je ne veux pas de @Shrug, je veux @¯\_(ツ)_/¯.

Aucune de ces versions ne fonctionne sur les versions 2008-2017:

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

Alors, existe-t-il un moyen d'utiliser les noms de paramètres de procédure stockée unicode?

Réponses:


44

Eh bien, les identifiants sont toujours Unicode / NVARCHAR, donc techniquement, vous ne pouvez rien créer sans nom Unicode.

Le problème que vous rencontrez ici est entièrement dû à la classification du ou des personnage (s) utilisé (s). Les règles pour les identifiants normaux (c'est-à-dire non délimités) sont les suivantes:

  • La première lettre doit être:
    • Une lettre telle que définie par la norme Unicode 3.2.
    • trait de soulignement (_), au signe (@) ou au chiffre (#)
  • Les lettres suivantes peuvent être:
    • Lettres telles que définies dans la norme Unicode 3.2.
    • Nombres décimaux issus du Basic Latin ou d’autres scripts nationaux.
    • trait de soulignement (_), arobase (@), chiffre (#) ou dollar ($)
  • Les espaces incorporés ou les caractères spéciaux ne sont pas autorisés.
  • Les caractères supplémentaires ne sont pas autorisés.

J'ai mis en gras les seules règles importantes dans ce contexte. La raison pour laquelle les règles de "première lettre" ne sont pas pertinentes ici est que la première lettre de toutes les variables et de tous les paramètres locaux est toujours le "at sign" @.

Et pour être clair: ce qui est considéré comme une "lettre" et ce qui est considéré comme un "chiffre décimal" est basé sur les propriétés que chaque caractère est assigné dans la base de données de caractères Unicode. Unicode attribue de nombreuses propriétés à chaque caractère, telles que: is_uppercase, is_lowercase, is_digit, is_decimal, is_combining, etc. Cela ne dépend pas de ce que nous, mortels, considérons comme des lettres ou des chiffres décimaux, mais des caractères qui ont été affectés à ces propriétés. Ces propriétés sont souvent utilisées dans les expressions rationnelles pour faire correspondre la "ponctuation", etc. Par exemple, \p{Lu}correspond à toute lettre majuscule (dans toutes les langues / tous les scripts) et \p{IsDingbats}correspond à tout caractère "Dingbats".

Donc, dans votre tentative de faire:

DECLARE @¯\_(ツ)_ INT;

seuls les _caractères (trait de soulignement ou "ligne inférieure") et (lettre Katakana Tu U + 30C4) entrent dans ces règles. Désormais, tous les caractères ¯\_(ツ)_/¯entrant conviennent aux identificateurs délimités, mais malheureusement, il semble que les noms de variable / paramètre et les GOTOétiquettes ne puissent pas être délimités (bien que les noms de curseur puissent l'être).

Ainsi, pour les noms de variable / paramètre, car ils ne peuvent pas être délimités, vous ne pouvez utiliser que des caractères qualifiés de "lettres" ou de "chiffres décimaux" à partir d'Unicode 3.2 (enfin, d'après la documentation; j'ai besoin de tester si les classifications ont été mises à jour pour les versions plus récentes d'Unicode, car les classifications sont traitées différemment des poids de tri).

CEPENDANT # 1 , les choses ne sont pas aussi simples qu'elles devraient l'être. Je suis maintenant en mesure d'achever mes recherches et de constater que la définition indiquée n'est pas tout à fait correcte. La définition précise (et vérifiable) des caractères valides pour les identificateurs habituels est la suivante:

  • Premier personnage:

    • Peut être n'importe quoi classé dans Unicode 3.2 comme "ID_Start" (qui inclut "des lettres" mais aussi des "caractères numériques semblables à des lettres")
    • Peut être _(ligne basse / trait de soulignement) ou _(ligne basse pleine largeur)
    • Peut être @, mais seulement pour les variables / paramètres
    • Peut être #, mais s'il s'agit d'un objet lié au schéma, alors uniquement pour les tables et les procédures stockées (dans ce cas, ils indiquent que l'objet est temporaire)
  • Caractères suivants:

    • Peut être n'importe quoi classé dans Unicode 3.2 en tant que "ID_Continue" (qui inclut des nombres "décimaux", mais aussi "des marques de combinaison d'espacement et de non-remise", et "des signes de ponctuation")
    • Peut - être @, #ou$
    • Peut être n'importe lequel des 26 caractères classés dans Unicode 3.2 en tant que caractères de contrôle de format

(Fait amusant: "ID" dans "ID_Start" et "ID_Continue" signifie "Identifier". Imaginez cela ;-)

Selon "Utilitaires Unicode: UnicodeSet":

  • Caractères de départ valides

    [: Age = 3.2:] & [: ID_Start = Oui:]

    -- 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'.
    */
  • Caractères de continuation valides

    [: Age = 3.2:] & [: ID_Continue = Yes:]

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

CEPENDANT N ° 2 , même la recherche dans la base de données Unicode peut être aussi simple. Ces deux recherches produisent une liste de caractères valides pour ces catégorisations, et ces caractères proviennent de Unicode 3.2, MAIS les définitions des différentes catégorisations changent d'une version à l'autre du standard Unicode. Cela signifie que la définition de "ID_Start" dans Unicode v 10.0 (ce que la recherche utilise aujourd'hui, 2018-03-26) n'est pas ce qu'elle était dans Unicode v 3.2. La recherche en ligne ne peut donc pas fournir une liste exacte. Mais vous pouvez récupérer les fichiers de données Unicode 3.2 et récupérer la liste des caractères "ID_Start" et "ID_Continue" à partir de là pour les comparer à ce que SQL Server utilise réellement. Et j’ai fait cela et confirmé une correspondance exacte avec les règles que j’ai énoncées ci-dessus dans «CEPENDANT N ° 1».

Les deux articles de blog suivants décrivent en détail les étapes à suivre pour trouver la liste exacte des caractères, y compris des liens vers les scripts d'importation:

  1. Uni-code: recherche de la liste vraie de caractères valides pour les identificateurs standard T-SQL, partie 1
  2. Uni-code: recherche de la liste vraie de caractères valides pour les identificateurs standard T-SQL, partie 2

Enfin, pour tous ceux qui veulent seulement voir la liste et ne sont pas concernés par ce qu'il a fallu pour le découvrir et le vérifier, vous pouvez le trouver ici:

Liste complète des caractères d'identification T-SQL valides
(donnez à la page un moment de chargement; 3,5 Mo et presque 47 000 lignes)


Concernant les caractères ASCII "valides", tels que /et -, ne fonctionne pas: le problème n'a rien à voir avec le fait que les caractères sont également définis ou non dans le jeu de caractères ASCII. Pour être valide, le caractère doit avoir soit la ID_Startou des ID_Continuebiens, ou être l' un des rares personnages personnalisés notés séparément. Il existe de nombreux caractères ASCII "valides" (62 sur un total de 128 - principalement des caractères de ponctuation et de contrôle) qui ne sont pas valides dans les identifiants "normaux".

Concernant les caractères supplémentaires: bien qu’ils puissent certainement être utilisés dans des identifiants délimités (et la documentation ne semble pas indiquer le contraire), s’il est vrai qu’ils ne peuvent pas être utilisés dans des identifiants classiques, c’est probablement parce qu’ils ne sont pas entièrement pris en charge. Les fonctions intégrées antérieures aux classements supplémentaires axés sur les caractères ont été introduites dans SQL Server 2012 (elles sont traitées comme deux caractères "inconnus"), et elles ne peuvent même pas être différenciées les unes des autres dans des classements non binaires antérieurs à la centaine. niveau Collations (introduit dans SQL Server 2008).

En ce qui concerne ASCII: les codages à 8 bits ne sont pas utilisés ici car tous les identifiants sont Unicode / NVARCHAR/ UTF-16 LE. L'instruction SELECT ASCII('ツ');retourne une valeur 63qui est un "?" (try:) SELECT CHAR(63);puisque ce caractère, même s’il est précédé d’un "N" majuscule, n’est certainement pas dans la page de code 1252. Cependant, ce caractère se trouve dans la page de code coréenne et produit le résultat correct, même sans le "N". "préfixe, dans une base de données avec un classement coréen par défaut:

SELECT UNICODE('ツ'); -- 12484

Concernant la première lettre affectant le résultat: cela n’est pas possible car la première lettre pour les variables et paramètres locaux l’est toujours @. La première lettre que nous contrôlons pour ces noms est en fait le deuxième caractère du nom.

En ce qui concerne les raisons pour lesquelles les noms de variables locales, les noms de paramètres et les GOTOétiquettes ne peuvent pas être délimités: je suppose que cela est dû au fait que ces éléments font partie du langage lui-même et ne sont pas des éléments qui se retrouveront dans une table système sous forme de données.


Tellement génial, merci. Cela m’a amené à ceci, qui va faire un excellent article de blog: gist.github.com/BrentOzar/9b08b5ab2b617847dbe4aa0297b4cd5b
Brent Ozar

8
@ BrentOzar avez-vous eu un scanner récemment?
Ross Presser

Wow, c'est une réponse assez impressionnante! Et j'appuie la remarque de Ross Presser.
SQL Nerd

22

Je ne pense pas que c'est le Unicode qui cause le problème; dans le cas des noms de variables ou de paramètres locaux, le caractère n'est pas un caractère ASCII / Unicode 3.2 valide (et il n'y a pas de séquence d'échappement pour les variables / paramètres comme il en existe pour les autres types d'entité).

Ce lot fonctionne correctement, il utilise un caractère Unicode qui n'enfreint tout simplement pas les règles applicables aux identificateurs non délimités:

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

Dès que vous essayez d'utiliser une barre oblique ou un tiret, qui sont tous les deux des caractères ASCII valides, le programme explose:

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

La documentation n'indique pas pourquoi ces identifiants sont soumis à des règles légèrement différentes de celles de tous les autres identifiants, ni pourquoi ils ne peuvent pas être échappés comme les autres.


Salut Aaron. Juste pour clarifier quelques points ici: 1) le premier caractère n'est pas un problème puisque le premier caractère est en fait le nom @du var / param. Tous les caractères qui ne fonctionnent pas ne devraient fonctionner dans aucune position, même s'ils sont précédés de caractères valides. 2) la doc indique seulement que les caractères supplémentaires ne peuvent pas être utilisés dans les identifiants habituels (ce qui semble être le cas pour tout ce que j'ai essayé), mais n'impose aucune restriction aux identifiants délimités, comme pour les espaces incorporés. De plus, je pense que ce sont différentes parce qu'elles font partie du langage T-SQL et non de la DB.
Solomon Rutzky

@SolomonRutzky J'ai l'impression que le problème est simplement et totalement qu'un nom de paramètre ne peut pas être délimité comme d'autres entités. Si je pouvais entourer un nom de paramètre de crochets ou de guillemets, je pourrais y placer n'importe lequel de ces caractères, dans n'importe quelle position. La question postule que vous ne pouvez pas utiliser de caractères Unicode dans un nom de paramètre, et ce n'est clairement pas le cas. Il y a quelques caractères Unicode vous pouvez utiliser, et certains caractères ASCII vous ne pouvez pas .
Aaron Bertrand

Oui, je conviens que si les noms de variables / paramètres et les GOTOétiquettes permettaient d'être délimités, la seule restriction serait la longueur. Je ne peux que supposer que l'analyse et / ou la gestion de ces quelques éléments se produisent à un niveau différent ou ont d'autres contraintes qui rendent impossible l'utilisation de valeurs délimitées. Au moins j'espère que ce n'était pas arbitraire ou un oubli.
Solomon Rutzky

(Je n'avais pas vu la mise à jour de votre commentaire lorsque j'ai répondu il y a un instant). Oui, la question implique que l'OP ne peut pas utiliser de caractères Unicode, mais sa formulation est techniquement incorrecte, car tous les noms sont toujours Unicode / NVARCHAR. Cela n'a rien à voir avec ASCII puisqu'il s'agit d'un encodage 8 bits qui n'est pas utilisé ici. Tous les caractères ici sont des caractères Unicode, même si certains existent également dans diverses pages de code 8 bits. Comme je l'ai expliqué dans ma réponse, quels caractères peuvent être utilisés est une question de ceux qui ont été étiquetés avec is_alphabeticou numeric_type=decimal.
Solomon Rutzky

J'ai vu des procs stockés qui étaient pleins de caca mais qui ne l'ont jamais nommé!
Mitch Wheat
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.