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.