czwartek, 27 czerwca 2013

GO - różnica wykonania między wersją 2000, a wyższymi

Polecenie GO nie jest poleceniem Transact-SQL. Jest używane przez narzędzia serwera SQL, takie, jak:
  • sqlcmd
  • osql
  • SQL Server Management Studio Core
  • Toad for SQL Server
Każde z tych narzędzi interpretuje wystąpienie GO jako sygnał, że powinno wysłać bieżący skrypt z poleceniami Transact-SQL do instancji serwera SQL. Warto nadmienić, że za skrypt uważa się wszystkie polecenia, począwszy od wystąpienia ostatniego polecenia GO, lub od rozpoczęcia sesji ad hoc lub jeżeli jest to pierwsze wystąpienie polecenia GO.
-- początek
-- początek pierwszego skryptu
USE [Healthy];
GO -- wykonanie pierwszego skryptu
-- początek drugiego skryptu
CREATE TABLE dbo.[Hospitals](
     [Id] IDENTITY(0, 1) TINYINT NOT NULL
   , [Name] NVARCHAR(255) NOT NULL);
GO -- wykonanie drugiego skryptu
-- początek trzeciego skryptu
INSERT INTO dbo.[Hospitals]([Name]) VALUES(N'Szpital Bielański');
INSERT INTO dbo.[Hospitals]([Name]) VALUES(N'Centralny Szpital Kliniczny MSWiA');
INSERT INTO dbo.[Hospitals]([Name]) VALUES(N'Instytut Hematologii i Transfuzjologii');
INSERT INTO dbo.[Hospitals]([Name]) VALUES(N'Instytut Kardiologii');
GO -- wykonanie trzeciego skryptu
-- koniec

Polecenie Transact-SQL nie może być w tej samej linii, co GO.
GO select * from dbo.Hospitals; -- nic nie zwróci (wersja 2000)


GO; SELECT * FROM dbo.Hospitals; 
-- Could not find stored procedure 'GO'
-- zwróci dane z polecenia SELECT

SELECT * FROM dbo.Hospitals;GO; -- Incorrect syntax near 'GO'
SELECT * FROM dbo.Hospitals;GO -- Incorrect syntax near 'GO'

Ale komentarz może się znajdować za GO
GO -- komentarz

Zasięg zmiennych, zdefiniowanych przez użytkownika, jest ograniczony do skryptu, co widać poniżej


DECLARE @i TINYINT;
SET @i = 5;
PRINT @i; -- 5
GO -- wykonanie skryptu
PRINT @i; -- Must declare the variable '@i'
GO

Należy również pamiętać, że jeżeli wywołujemy procedurę składowaną nie jako pierwsze polecenie skryptu, lecz następne, musimy dołączyć słowo kluczowe EXECUTE (lub EXEC).


USE [Healthy];
GO
CREATE PROCEDURE [dbo].Greeting 
AS
     SELECT 'Hello';
GO
Greeting; -- Hello
GO
DECLARE @i TINYINT;
EXEC Greeting; -- Hello
GO
DECLARE @i TINYINT;
Greeting; -- Incorrect syntax near 'Greeting'
GO

Rozbieżność między wersją 2000 a wyższą jest taka, że od począwszy od wersji 2005 skrypt można powtarzać.


USE [Healthy];
GO
CREATE TABLE [dbo].[Test](var TINYINT);
GO
INSERT INTO [dbo].[Test](var) VALUES(5);
GO 7 -- wersja 2000 zignoruje 7, od 2005 wykona polecenie INSERT 7 razy

W wersji 2000, w większości przypadków, ten problem obejdziemy przy pomocy pętli WHILE


USE [Healthy];
GO
CREATE TABLE [dbo].[Test](var TINYINT);
GO
DECLARE @count TINYINT;
SET @count = 0;
WHILE @count < 7
BEGIN
  INSERT INTO [dbo].[Test](var) VALUES(5);
  SET @count = @count + 1;
END
GO


niedziela, 5 maja 2013

IF ... ELSE IF ... ELSE

Podczas niedawnego pisania kodu nie mogłem sobie przypomnieć,  czy polecenie IF ma ograniczoną liczbę zagniedżeń w sobie. Po przejrzeniu MSDN okazało się, że to ograniczenie nie występuje, natomiast coś innego przykuło moją uwagę, a mianowicie konstrukcja

IF Boolean_expression
    { sql_statement | statement_block }
[ ELSE
    { sql_statement | statement_block } ] 


Niby nic dziwnego, tylko dlaczego wykonywała mi się także konstrukcja

IF Boolean_expression
    { sql_statement | statement_block }
[ ELSE IF Boolean_expression
    { sql_statement | statement_block } ]
[ ELSE
    { sql_statement | statement_block } ] 


Podobna do instrukcji warunkowej w C++. Przyjrzyjmy się bliżej na poniższym przykładzie

/* 0 */ USE master
/* 1 */ GO
/* 2 */ DECLARE @a TINYINT;
/* 3 */ SET @a = 0;

/* 4 */ IF @a = 1 PRINT 'tak';
/* 5 */ ELSE IF @a = 0 PRINT '?';
/* 6 */ ELSE PRINT 'nie';

Według wzorca, jeżeli mamy więcej, niż jedno polecenie w bloku warunkowym, powinno być ono otoczone BEGIN ... END. W powyższym przykładzie po ELSE (wiersz 5) dalsze polecenia powinny być zamknięte w bloku, aby mogły działać poprawnie. Daleko nie trzeba odbiegać, wystarczy jedna linijka na potwierdzenie

IF 1 = 0 PRINT 'tak'; PRINT 'Poza if';

Wypisze "Poza if", bo polecenia nie zostały ujęte w bloku, drugie wystąpienie PRINT jest poza zasięgiem polecenia warunkowego IF.

Z pomocą przychodzi Nam dopiero analiza planu wykonania. Okazuje się, że wszystko jest w porządku. A raczej nieporządnie zapisane. IF ... ELSE traktowane jest jako jedno polecenie. Zatem

IF Boolean_expression
    { sql_statement | statement_block }
ELSE IF Boolean_expression
    { sql_statement | statement_block
ELSE
    { sql_statement | statement_block }  


zmieniane jest na

IF Boolean_expression
    { sql_statement | statement_block }
ELSE 

BEGIN
     IF Boolean_expression
         { sql_statement | statement_block
    ELSE
         { sql_statement | statement_block }

END

Aby to potwierdzić wystarczy spojrzeć na poniższe plany zapytania.

-- część wspólna wszystkich zapytań
USE master
GO
DECLARE @var TINYINT;
SET @var = 0;

-- ----------------------------------------------------------------------------------------------------------------
IF @var = 0 PRINT 'tak';


-- ----------------------------------------------------------------------------------------------------------------
IF @var = 0 PRINT 'tak';
ELSE SELECT 'nie';


-- ----------------------------------------------------------------------------------------------------------------
IF @var = 0 
BEGIN
     IF @var = 0 PRINT 'tak-tak';

END
ELSE
     PRINT 'nie';

-- ----------------------------------------------------------------------------------------------------------------
IF @var = 0
BEGIN
     IF @var = 0 PRINT 'tak-tak';
     ELSE SELECT 'tak-nie';
END
ELSE
     PRINT 'nie';

-- ----------------------------------------------------------------------------------------------------------------
IF @var = 0
BEGIN
     IF @var = 0 PRINT 'tak-tak';
     ELSE SELECT 'tak-nie';
END
ELSE
BEGIN
     IF @var = 1 PRINT 'nie-tak';
END

-- ----------------------------------------------------------------------------------------------------------------
IF @var = 0
BEGIN
     IF @var = 0 PRINT 'tak-tak';
     ELSE SELECT 'tak-nie';
END
ELSE
BEGIN
     IF @var = 1 PRINT 'nie-tak';
     ELSE SELECT 'nie-nie';
END

-- ----------------------------------------------------------------------------------------------------------------
/* Utwórzmy dodatkowo procedurę składowaną, żeby lepiej zobrazować to, nastąpi  za chwilę */
USE master
GO
CREATE PROCEDURE dbo.test
AS
     RETURN 0;
GO

-- --------
IF @var = 0 PRINT 'tak';
ELSE IF @var = 1 EXEC dbo.test;

ELSE SELECT 'nie';


Widzimy potwierdzenie przetworzenia przez serwer instrukcji warunkowej w tle, bez udziału użytkownika. IF ... ELSE traktuje jako jedno polecenie.

wtorek, 30 kwietnia 2013

2000 - zmiana właściciela bazy danych

Ostatnio zostałem zmuszony do przeniesienia bazy na inny serwer. Baza niewielka. Ponieważ pogubił mapowanie między loginem apiX, przypisanym do roli db_owner, a dbo samej bazy, trzeba było to naprawić. I tu przyszła z pomocą procedura

use nazwaBazy;
go
exec sp_changedbowner 'apiX';
go

Nie tylko zmieniła mapowanie loginu apiX w bazie do dbo, ale także wszystkie aliasy, które wskazywały na poprzedniego użytkownika dbo, też zostały przemapowane.