use database feltételesen

A probléma

Érdekes problémába futottam bele: use parancs feltételes használata. Valami ilyesmit képzeljetek el, hogy

if exists(select 1 from sys.databases where name='akarmi')
begin
use akarmi
parancsok tömkelege az adott adatbázisra
end

Csak éppen ez így nem működik.

A feladatom azt hiszem egyértelmű: ha egy adott adatbázis létezik, akkor hajtson rajta végre egy rakás parancsot (amiben lehet DML és DDL is), viszont ha nem létezik, akkor ugorja át a parancsokat és ne jelezzen hibát vagy ne csináljon butaságot az éppen aktuális adatbázisban.

Ha megadtok egy olyat, hogy use kakukkmadar le sem kell futtatnunk, elég ha a parse-t megnyomjuk és máris kiadja, hogy ilyen adatbázis nem létezik (már persze ha nálad nincs kakukkmadar nevű adatbázis teljesen véletlenül).

A USE parancs helpjében szerepel is a kitétel, hogy “USE is executed at both compile and execution time and takes effect immediately.” Vagyis már compile-nál végrehajtja és ezért természetes, hogy az adatbázis hiányt egyből jelzi. De mit tehetünk, ha mégis szükségünk van ilyen funkcionalitásra? Hogyan léphetünk tovább?

Természetesen az nem játszik, hogy execute-tal hajtjuk meg, mint sima string parancs, mert az adatbázis context váltás csak azon a végrehajtáson belül érvényes. Egy use master; execute ('use kakukk;'); create table blabla ... a master adatbázisban fogja létrehozni a blabla táblát.

Kell nekünk use?

Megpróbálhatjuk kihagyni a use-t az egész játszmából. Átírhatjuk a scripteket, hogy használjunk adatbázisos prefixeket, így az adott parancsot mindig a megfelelő adatbázison hajtjuk végre.

if exists(select 1 from sys.databases where name='akarmi')
begin
create table akarmi.schema.tablanev...
alter table akarmi.schema.tablanev...
update akarmi.schema.tablanev...
és így tovább...
end

Zseniális, megugrottuk a lécet! Ahogy azt az egyszeri SQL-es elképzeli… Egészen addig örülünk, amíg nem kezd el hibákat dobálni a parser:

Msg 166, Level 15, State 1, Line 25967
'DROP FUNCTION' does not allow specifying the database name as a prefix to the object name.
Msg 166, Level 15, State 1, Line 25987
'CREATE/ALTER FUNCTION' does not allow specifying the database name as a prefix to the object name.

Tehát a function manipuláló parancsok nem tudnak adatbázis prefixet fogadni. Meg egyébként a procedure parancsok sem. Ez egyébként szerepel a dokumentációjukban is: a procedure csak schemaname.procedurename-t ismer fel, a function is csak schemaname.functionname-t ismer, míg mondjuk a table ismer databasename.schemaname.tablename-t is.

A megoldás?

Teljesen zátonyra futottunk, csak pislogunk, mint a béka amit Vuk kavicsnak nézett. Illetve mégsem… az elején említettem, hogy az execute-tal meg tudjuk oldani valóban a context váltást, csak éppen nem elég a use parancsot beletenni a végrehajtandó stringbe, hanem az egészet kellene…

declare @comm nvarchar(max);
set @comm = 'use kakukk; create table blabla...';
execute(@comm);

Ez működik. De ezzel a megoldással nem dicsekednék.

Azon kívül, hogy elég barkács, még elvesztünk egy csomó lehetőséget, amiből a legfájóbb az előzetes parse-olás. Nem tudjuk, hogy a script legalább szintaktikailag helyes-e, csak akkor, amikor lefuttattuk az egészet és elkezd hibákat dobálni.

Végszó

Persze lehet kombinálni a kettőt és a stringes végrehajtást csak akkor használni, ha elengedhetetlen. Meg akár release script generáláskor is figyelembe lehetne ezt venni és kicsit automatizálni.

Vannak opciók, de egyik sem tökéletes.