Jak utworzyć nazwy parametrów i zmiennych Unicode

53

Wszystko to działa:

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

Ale prawdopodobnie możesz zobaczyć, dokąd zmierzam: nie chcę @Shrug, chcę @¯\_(ツ)_/¯.

Żadne z tych nie działa w żadnej wersji z lat 2008-2017:

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

Czy jest więc sposób na użycie nazw parametrów procedury składowanej Unicode?

Brent Ozar
źródło

Odpowiedzi:

44

Identyfikatory to zawsze Unicode / NVARCHAR, więc technicznie nie można stworzyć niczego, co nie ma nazwy Unicode 🙃.

Problem, który tu masz, wynika całkowicie z klasyfikacji użytej postaci. Reguły dla zwykłych (tzn. Nieograniczonych) identyfikatorów są:

  • Pierwsza litera musi być:
    • Litera zdefiniowana w standardzie Unicode 3.2.
    • znak podkreślenia (_), znak (@) lub znak numeryczny (#)
  • Kolejnymi literami mogą być:
    • Litery zdefiniowane w standardzie Unicode 3.2.
    • Liczby dziesiętne z podstawowego skryptu łacińskiego lub innych skryptów krajowych.
    • znak podkreślenia (_), znak (@), znak liczbowy (#) lub znak dolara ($)
  • Osadzone spacje lub znaki specjalne są niedozwolone.
  • Dodatkowe znaki są niedozwolone.

Pogrubiłem jedyne reguły, które mają znaczenie w tym kontekście. Powodem, dla którego reguły „Pierwsza litera” nie są tutaj istotne, jest to, że pierwszą literą we wszystkich zmiennych lokalnych i parametrach jest zawsze „znak” @.

I dla jasności: to, co jest uważane za „literę”, a co za „cyfrę dziesiętną”, opiera się na właściwościach, które każdy znak jest przypisany w bazie znaków znaków Unicode. Unicode przypisuje wiele właściwości do każdego znaku, takie jak: is_uppercase, is_lowercase, is_digit, is_decimal, is_combining itp. To nie jest kwestia tego, co my, śmiertelnicy, rozważalibyśmy litery lub cyfry dziesiętne, ale które znaki mają przypisane te właściwości. Te właściwości są często używane w wyrażeniach regularnych do dopasowania „interpunkcji” itp. Na przykład \p{Lu}dopasowuje każdą wielką literę (we wszystkich językach / skryptach) i \p{IsDingbats}pasuje do dowolnego znaku „Dingbats”.

Tak więc, próbując zrobić:

DECLARE @¯\_(ツ)_ INT;

tylko znaki _(podkreślenie lub „dolna linia”) i (Katakana Letter Tu U + 30C4) pasują do tych reguł. Teraz wszystkie znaki ¯\_(ツ)_/¯są odpowiednie dla identyfikatorów rozdzielanych, ale niestety wydaje się, że nazwy zmiennych / parametrów i GOTOetykiety nie mogą być rozdzielane (chociaż nazwy kursorów mogą być).

Tak więc, w przypadku nazw zmiennych / parametrów, ponieważ nie można ich rozgraniczać, utkniesz przy użyciu tylko znaków, które kwalifikują się jako „litery” lub „cyfry dziesiętne” od Unicode 3.2 (cóż, zgodnie z dokumentacją; muszę przetestować jeśli klasyfikacje zostały zaktualizowane dla nowszych wersji Unicode, ponieważ klasyfikacje są obsługiwane inaczej niż wagi sortowania).

JEDNAK # 1 , rzeczy nie są tak proste, jak powinny. Byłem teraz w stanie zakończyć moje badania i stwierdziłem, że podana definicja nie jest całkowicie poprawna. Dokładna (i weryfikowalna) definicja, które znaki są prawidłowe dla zwykłych identyfikatorów, to:

  • Pierwsza postać:

    • Może być czymkolwiek sklasyfikowanym w Unicode 3.2 jako „ID_Start” (który obejmuje „Litery”, ale także „literowe znaki numeryczne”)
    • Może być _(niska linia / podkreślenie) lub _(niska linia pełnej szerokości)
    • Może być @, ale tylko dla zmiennych / parametrów
    • Może być #, ale jeśli obiekt związany ze schematem, to tylko dla tabel i procedur przechowywanych (w którym to przypadku wskazują, że obiekt jest tymczasowy)
  • Kolejne znaki:

    • Może być dowolną klasyfikowaną w Unicode 3.2 jako „ID_Continue” (która obejmuje liczby „dziesiętne”, ale także „odstępy i spacje łączące znaki” oraz „łączące znaki interpunkcyjne”)
    • Może być @, #lub$
    • Może być dowolnym z 26 znaków sklasyfikowanych w Unicode 3.2 jako znaki sterujące formatem

(fajny fakt: „ID” w „ID_Start” i „ID_Continue” oznacza „Identyfikator”. Wyobraź to sobie ;-)

Zgodnie z „Unicode Utilities: UnicodeSet”:

  • Prawidłowe znaki początkowe

    [: Wiek = 3,2:] i [: ID_Start = Tak:]

    -- 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'.
    */
    
  • Prawidłowe znaki kontynuacji

    [: Wiek = 3,2:] i [: ID_Continue = Tak:]

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

JEDNAK # 2 , nawet przeszukiwanie bazy danych Unicode może być tak łatwe. Te dwa wyszukiwania dają listę prawidłowych znaków dla tych kategoryzacji, a te znaki pochodzą z Unicode 3.2, ALE definicje różnych zmian kategoryzacji w różnych wersjach standardu Unicode. Oznacza to, że definicja „ID_Start” w Unicode v 10.0 (to, czego używa dziś to wyszukiwanie, 26.03.2018) nie jest taka, jak w Unicode v 3.2. Dlatego wyszukiwanie online nie może dostarczyć dokładnej listy. Ale możesz pobrać pliki danych Unicode 3.2 i stamtąd listę znaków „ID_Start” i „ID_Continue”, aby porównać z tym, co faktycznie używa SQL Server. Zrobiłem to i potwierdziłem dokładne dopasowanie do zasad, które podałem powyżej w „JEDNAKU 1”.

Poniższe dwa posty na blogu szczegółowo opisują kroki podjęte w celu znalezienia dokładnej listy znaków, w tym linków do skryptów importu:

  1. Uni-Code: poszukiwanie prawdziwej listy prawidłowych znaków dla regularnych identyfikatorów T-SQL, część 1
  2. Uni-Code: poszukiwanie prawdziwej listy prawidłowych znaków dla regularnych identyfikatorów T-SQL, część 2

Wreszcie, dla każdego, kto chce tylko zobaczyć listę i nie jest zainteresowany tym, co trzeba, aby ją odkryć i zweryfikować, możesz to znaleźć tutaj:

Całkowicie kompletna lista prawidłowych znaków identyfikatora T-SQL
(daj stronie chwilę na załadowanie; ma 3,5 MB i prawie 47 tys. Linii)


W odniesieniu do „prawidłowych” znaków ASCII, takich jak /i -, nie działa: problem nie ma nic wspólnego z tym, czy znaki są również zdefiniowane w zestawie znaków ASCII. Aby było ważne, musi mieć charakter albo ID_Startczy ID_Continuenieruchomość lub być jednym z niewielu niestandardowych znaków zauważyć oddzielnie. Istnieje sporo „prawidłowych” znaków ASCII (62 ze 128 ogółem - głównie znaków interpunkcyjnych i kontrolnych), które nie są prawidłowe w „Zwykłych” Identyfikatorach.

Odnośnie znaków uzupełniających: chociaż z pewnością można ich używać w identyfikatorach rozdzielanych (a dokumentacja nie wydaje się zawierać innych informacji), jeśli prawdą jest, że nie można ich używać w zwykłych identyfikatorach, najprawdopodobniej dlatego, że nie są w pełni obsługiwane w SQL Server 2012 wprowadzono wbudowane funkcje przed uzupełniającymi zestawieniami ze świadomością znaków (są one traktowane jako dwa indywidualne „nieznane” znaki), ani nie można ich nawet odróżnić w niebinarnych zestawieniach przed 100- Poziomy sortowania (wprowadzone w SQL Server 2008).

W odniesieniu do ASCII: nie stosuje się tutaj kodowania 8-bitowego, ponieważ wszystkie identyfikatory to Unicode / NVARCHAR/ UTF-16 LE. Instrukcja SELECT ASCII('ツ');zwraca wartość, 63której jest „?” (spróbuj SELECT CHAR(63);:), ponieważ ten znak, nawet jeśli poprzedzony wielką literą „N”, z pewnością nie znajduje się na stronie kodowej 1252. Jednak znak ten znajduje się na koreańskiej stronie kodowej i daje poprawny wynik, nawet bez „N „przedrostek w bazie danych z koreańskim domyślnym sortowaniem:

SELECT UNICODE('ツ'); -- 12484

Odnośnie pierwszej litery wpływającej na wynik: nie jest to możliwe, ponieważ pierwsza litera dla zmiennych lokalnych i parametrów jest zawsze @. Pierwszą literą, którą możemy kontrolować dla tych nazw, jest właściwie drugi znak nazwy.

Jeśli chodzi o to, dlaczego nazwy zmiennych lokalnych, nazwy parametrów i GOTOetykiety nie mogą być rozdzielane: Podejrzewam, że jest to spowodowane tym, że te elementy są częścią samego języka, a nie czymś, co trafi do tabeli systemowej jako dane.

Solomon Rutzky
źródło
Tak wspaniale, dzięki. Doprowadziło mnie to do powstania świetnego posta na blogu: gist.github.com/BrentOzar/9b08b5ab2b617847dbe4aa0297b4cd5b
Brent
8
@BrentOzar, czy miałeś ostatnio tomograf komputerowy?
Ross Presser
Wow, to dość imponująca odpowiedź! I popieram uwagę Rossa Pressera.
SQL Nerd
22

Nie sądzę, że to problem powoduje Unicode; w przypadku lokalnych nazw zmiennych lub parametrów oznacza to, że znak nie jest prawidłowym znakiem ASCII / Unicode 3.2 (i nie ma żadnej sekwencji zmiany znaczenia dla zmiennych / parametrów, tak jak w przypadku innych typów jednostek).

Ta partia działa dobrze, używa znaku Unicode, który po prostu nie narusza reguł dla nieograniczonych identyfikatorów:

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

Gdy tylko spróbujesz użyć ukośnika lub myślnika, które są poprawnymi znakami ASCII, bombarduje:

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

Dokumentacja nie wyjaśnia, dlaczego te identyfikatory podlegają nieco innym regułom niż wszystkie inne identyfikatory, ani dlaczego nie można ich uniknąć tak jak inne.

Aaron Bertrand
źródło
Cześć Aaron. Aby wyjaśnić tutaj kilka kwestii: 1) pierwszy znak nie stanowi problemu, ponieważ pierwszy znak jest tak naprawdę @nazwą var / param. Żaden z niedziałających znaków nie powinien działać na żadnej pozycji, nawet jeśli poprzedzony jest prawidłowymi znakami. 2) dokument stwierdza tylko, że dodatkowe znaki nie mogą być używane w zwykłych identyfikatorach (co wydaje się, że tak jest w przypadku wszystkiego, co próbowałem), ale nie nakłada żadnych ograniczeń na identyfikatory rozdzielane, tak jak w przypadku osadzonych spacji. Ponadto uważam, że są one różne, ponieważ są częścią języka T-SQL, a nie rzeczy w DB.
Solomon Rutzky
@ SolomonRutzky Wydaje mi się, że problem polega po prostu na tym, że nazwy parametru nie można rozgraniczać tak jak innych bytów. Gdybym mógł owinąć nawiasy kwadratowe lub podwójne cudzysłowy wokół nazwy parametru, mógłbym wstawić do niej dowolny z tych znaków, w dowolnej pozycji. Pytanie postuluje, że nie można używać znaków Unicode w nazwie parametru, i oczywiście tak nie jest. Istnieje kilka znaków Unicode, których można użyć, a niektórych znaków ASCII nie można .
Aaron Bertrand
Tak, zgadzam się, że jeśli nazwy zmiennych i parametrów oraz GOTOetykiety mogą być ograniczone, wówczas jedynym ograniczeniem będzie długość. Mogę tylko założyć, że parsowanie i / lub obsługa tych kilku elementów dzieje się na innym poziomie lub ma pewne inne ograniczenia, które uniemożliwiły zastosowanie ograniczonych wartości. Przynajmniej mam nadzieję, że nie był to arbitralny ani niedopatrzenie.
Solomon Rutzky
(nie widziałem aktualizacji Twojego komentarza, kiedy odpowiedziałem przed chwilą). Tak, pytanie sugeruje, że OP nie może używać znaków Unicode, ale sformułowanie pytania jest technicznie niepoprawne, ponieważ wszystkie nazwy są zawsze Unicode / NVARCHAR. Nie ma to nic wspólnego z ASCII, ponieważ nie jest tu używane 8-bitowe kodowanie. Wszystkie znaki tutaj są znakami Unicode, nawet jeśli niektóre istnieją również na różnych 8-bitowych stronach kodowych. Jak wyjaśniłem w mojej odpowiedzi, które mogą być używane znaki to kwestia, które z nich zostały oznaczone tagiem albo is_alphabeticalbo numeric_type=decimal.
Solomon Rutzky
Widziałem przechowywane procy, które były pełne kupy, ale nigdy tego nie nazwały!
Mitch Wheat