Dlaczego nie mogę używać zmiennych w T-SQL, tak jak sobie wyobrażam, że mogę?

18

Wybacz, jestem programistą, który przeniósł się do świata SQL. Myślałem, że mogę poprawić trochę SQL, dodając zmienne, ale nie działało to tak, jak się spodziewałem. Czy ktoś może mi powiedzieć, dlaczego to nie działa? Nie chcę się obejść, chcę poznać powody, dla których to nie działa tak, jak wyobrażam sobie, że powinno, bo jestem pewien, że jest dobry powód, ale obecnie nie wyskakuje na mnie.

DECLARE @DatabaseName varchar(150)
SET @DatabaseName = 'MyAmazingDatabaseName'

CREATE DATABASE @DatabaseName
GO

USE @DatabaseName
GO
Gareth
źródło
Musisz do tego użyć dynamicznego SQL. mssqltips.com/sqlservertip/1160/…
Stijn Wynants
3
Hej, dziękuję za komentowanie, ale zgodnie z moim pytaniem nie szukam odpowiedzi. Chcę wiedzieć, czy ktoś wie, dlaczego nie mogę tego zrobić tak, jak pokazałem.
gareth,
2
Ponieważ nie można użyć USEpolecenia z parametrem.
TT.
2
Oprócz podanych już odpowiedzi, ale niewystarczających do uzyskania odpowiedzi, zmienne mają zakres maksymalnie do bieżącej partii. (Nie wiem od razu, czy możliwe jest zawężenie zakresu zmiennych bardziej niż w SQL Server.) Tak więc w momencie, gdy masz GO, poprzednio zadeklarowana zmienna znika. Możesz zajrzeć do zmiennych SQLCMD, które mogą, ale nie muszą dotyczyć twojego scenariusza.
CVn
1
Nazwy baz danych i nazw kolumn zwykle traktujemy jako wartości łańcuchowe, ale w kontekście SQL są to Identyfikatory. To, co próbujesz, byłoby tym samym, co oczekiwanie x = 7; być tym samym co „x” = 7; w jakimś innym języku. Tak jak można stworzyć język komputerowy, który zajmuje się „x” = 7 tak samo jak x = 7, można stworzyć RDBMS, który traktuje Utwórz tabelę X tak samo jak Utwórz tabelę „X”. Ale to nie byłby SQL.
user1008646,

Odpowiedzi:

20

Na stronie online Książki dla zmiennych

Zmiennych można używać tylko w wyrażeniach, a nie w nazwach obiektów lub słowach kluczowych. Aby zbudować dynamiczne instrukcje SQL, użyj EXECUTE.

Działa to tak, jak się spodziewałeś, jeśli na przykład użyjesz zmiennej w klauzuli where. Jeśli tak, to sądzę, że ma to coś wspólnego z tym, że parser nie jest w stanie ocenić zmiennej, a tym samym sprawdzić istnienia. Podczas wykonywania zapytanie jest najpierw analizowane pod kątem składni i obiektów, a następnie, jeśli parsowanie zakończy się powodzeniem, zapytanie zostanie wykonane w momencie, w którym zmienna zostanie ustawiona.

DECLARE @name varchar(20);
SET @name = 'test';

CREATE TABLE [#tmp]([val] varchar(10));

insert into #tmp
values('test')

SELECT *
FROM [#tmp]
WHERE [val] = @name;
Bob Klimes
źródło
3
Należy pamiętać, że należy w miarę możliwości unikać dynamicznego SQL. Jest to analog SQL do używania evalfunkcji w językach proceduralnych, takich jak JavaScript i Python. Jest to szybki sposób na utworzenie luk bezpieczeństwa.
jpmc26,
1
@ jpmc26: Jaki jest bardziej bezpieczny sposób, który nie wymaga dynamicznego SQL?
Robert Harvey,
1
@RobertHarvey To, że najlepiej tego uniknąć, nie oznacza, że ​​zawsze istnieje alternatywa z dokładnie taką samą funkcjonalnością. ;) Często odpowiedź częściowo brzmi: „Użyj zupełnie innego rozwiązania problemu”. Czasami jest to najlepsza rzecz do zrobienia, ale nie bez sporej ilości rozważań i upewnienia się, że nie zaniedbałeś alternatywnych rozwiązań, a nawet wtedy powinna ona przynieść zdrową dawkę ostrożności.
jpmc26
2
@ jpmc26: Przykład OP wygląda jak to, co ORM „Najpierw kod” może zrobić, aby skonfigurować tabele w bazie danych. Podczas gdy dynamiczny SQL jest z zasady niepewny, użytkownik końcowy nigdy nie dotknie tego konkretnego kodu.
Robert Harvey,
@RobertHarvey Zależy, kogo uważasz za „użytkownika końcowego”. W przypadku skryptu, który wdraża bazę danych, uważam programistę i prawdopodobnie niektórych administratorów sys za „użytkownika końcowego”. Nadal bym zaprojektował system, aby odrzucał niepewne dane wejściowe w tym przypadku, choćby z innego powodu niż unikanie wypadków. Ponadto, jeśli chodzi o „nigdy nie dotykaj”, OP dotyka tego kodu, więc ...
jpmc26,
17

Ograniczenia użycia zmiennych w instrukcjach SQL wynikają z architektury SQL.

Przetwarzanie instrukcji SQL składa się z trzech faz:

  1. Przygotowanie - Instrukcja jest analizowana i kompilowany jest plan wykonania , określający, do których obiektów bazy danych można uzyskać dostęp, w jaki sposób są one dostępne i jak są powiązane. Plan wykonania jest zapisywany w pamięci podręcznej planu .
  2. Wiązanie - wszelkie zmienne w instrukcji są zastępowane rzeczywistymi wartościami.
  3. Wykonanie - buforowany plan jest wykonywany z powiązanymi wartościami.

Serwer SQL ukrywa krok przygotowawczy przed programistą i wykonuje go znacznie szybciej niż bardziej tradycyjne bazy danych, takie jak Oracle i DB2. Ze względów wydajnościowych SQL spędza potencjalnie dużo czasu na określeniu optymalnego planu wykonania, ale robi to tylko przy pierwszym napotkaniu instrukcji po ponownym uruchomieniu.

Tak więc w statycznym SQL zmienne mogą być używane tylko w miejscach, w których nie unieważnią planu wykonania, a więc nie dla nazw tabel, nazw kolumn (w tym nazw kolumn w warunkach GDZIE) itp.

Dynamiczny SQL istnieje w przypadkach, gdy nie można obejść ograniczeń, a programista wie, że jego wykonanie potrwa nieco dłużej. Dynamiczny SQL może być podatny na wstrzykiwanie złośliwego kodu, więc uważaj!

grahamj42
źródło
7

Jak widać, pytanie „dlaczego” wymaga innego rodzaju odpowiedzi, w tym uzasadnienia historycznego i podstawowych założeń dotyczących języka, nie jestem pewien, czy naprawdę mogę oddać sprawiedliwość.

Ten obszerny artykuł autorstwa SQL MVP Erlanda Sommarskoga zawiera uzasadnienie wraz z mechaniką:

Klątwa i błogosławieństwa dynamicznego SQL :

Plany zapytań w pamięci podręcznej

Każde zapytanie uruchamiane w SQL Server wymaga planu zapytań. Gdy uruchamiasz zapytanie po raz pierwszy, SQL Server tworzy dla niego plan zapytań - lub, zgodnie z terminologią - kompiluje zapytanie. Program SQL Server zapisuje plan w pamięci podręcznej, a przy następnym uruchomieniu zapytania plan zostanie ponownie użyty.

To (i bezpieczeństwo, patrz poniżej) jest prawdopodobnie największym powodem.

SQL działa przy założeniu, że zapytania nie są operacjami jednorazowymi, ale będą używane w kółko. Jeśli tabela (lub baza danych!) Nie jest faktycznie określona w zapytaniu, nie ma możliwości wygenerowania i zapisania planu wykonania do wykorzystania w przyszłości.

Tak, nie każde uruchamiane przez nas zapytanie zostanie ponownie użyte, ale jest to domyślna przesłanka działania SQL , więc „wyjątki” mają być wyjątkowe.

Kilka innych powodów, dla których Erland wymienia (zauważ, że wyraźnie wymienia zalety korzystania z procedur przechowywanych , ale wiele z nich to także zalety sparametryzowanych (niedynamicznych) zapytań):

  • System uprawnień : aparat SQL nie jest w stanie przewidzieć, czy masz uprawnienia do uruchomienia zapytania, jeśli nie zna tabeli (lub bazy danych), z którą będziesz działał. „Łańcuchy uprawnień” za pomocą dynamicznego SQL są uciążliwe.
  • Zmniejszenie ruchu w sieci : przekazanie nazwy przechowywanego proc i kilku wartości parametrów przez sieć jest krótsze niż długie zapytanie.
  • Enkapsulacja logiki : powinieneś zapoznać się z zaletami enkapsulacji logiki z innych środowisk programowania.
  • Śledzenie, co jest używane : Jeśli muszę zmienić definicję kolumny, jak mogę znaleźć cały kod, który ją wywołuje? Istnieją procedury systemowe do znajdowania zależności w bazie danych SQL, ale tylko wtedy, gdy kod znajduje się w procedurach przechowywanych.
  • Łatwość pisania kodu SQL : sprawdzanie składni następuje podczas tworzenia lub modyfikowania procedury składowanej, więc mam nadzieję, że będzie mniej błędów.
  • Rozwiązywanie problemów i problemów : DBA może znacznie łatwiej śledzić i mierzyć wydajność poszczególnych procedur przechowywanych niż zmieniający się dynamiczny SQL.

Ponownie, każdy z nich ma sto niuansów, których nie będę tu wchodził.

BradC
źródło
2

Musisz użyć dynamicznego SQL

DECLARE @DatabaseName varchar(150) = 'dbamaint'
declare @sqltext nvarchar(max) = N''

set @sqltext = N'CREATE DATABASE '+quotename(@DatabaseName)+ ';'

print @sqltext 

-- once you are happy .. uncomment below
--exec sp_executesql @sqltext
set @sqltext = ''
set @sqltext = N'use '+quotename(@DatabaseName)+ ';'
print @sqltext 
-- once you are happy .. uncomment below
--exec sp_executesql @sqltext

poniżej znajduje się wynik drukowania. Gdy usuniesz komentarz, exec sp_executesql @sqltextinstrukcje zostaną faktycznie wykonane ...

CREATE DATABASE [dbamaint];
use [dbamaint];
Kin Shah
źródło
1
Tak, dziękuję, wiem o tym, ale chcę wiedzieć, czy ktoś wie, dlaczego nie możesz po prostu użyć zmiennej?
gareth
Parser T-SQL wyrzuci błędy składniowe. Rozpoznawany przez analizator składni nie jest poprawny T-SQL.
Kin Shah,
Dzięki Kin, jestem pewien, że muszą istnieć dobre powody. Może dlatego, że nazwy baz danych mogą zawierać „@” i prawdopodobnie inne bardziej złożone powody.
gareth,
1
Tak, mogą zawierać @ i myślę, że to główny powód. msdn.microsoft.com/en-us/library/ms175874.aspx
Paweł Tajs
1
@gazeranco Uwierz mi, każdy, kto pracuje z SQL Server, bez wątpienia życzy sobie, aby więcej poleceń akceptowało zmienne zamiast stałych identyfikatorów.
db2